<?php
declare(strict_types=1);

namespace App\Modules\Settings;

use App\Core\Exceptions\IntegrationConfigException;
use App\Core\Support\StringHelper;
use PDO;
use Throwable;

final class FakturowniaIntegrationRepository
{
    private const INTEGRATION_TYPE = 'fakturownia';
    private const INTEGRATION_NAME = 'Fakturownia';
    private const INTEGRATION_BASE_URL = 'https://app.fakturownia.pl';

    private readonly IntegrationsRepository $integrations;
    private readonly IntegrationSecretCipher $cipher;

    public function __construct(
        private readonly PDO $pdo,
        private readonly string $secret
    ) {
        $this->integrations = new IntegrationsRepository($this->pdo);
        $this->cipher = new IntegrationSecretCipher($this->secret);
    }

    /**
     * @return array<string, mixed>
     */
    public function getSettings(): array
    {
        $this->ensureRow();
        $integrationId = $this->getIntegrationId();
        $row = $this->fetchSettingsRow();
        $integration = $this->integrations->findById($integrationId);
        $encryptedToken = $this->resolveApiTokenEncrypted($row, $integration);

        return [
            'integration_id' => $integrationId,
            'settings_id' => isset($row['id']) ? (int) $row['id'] : null,
            'name' => (string) ($integration['name'] ?? self::INTEGRATION_NAME),
            'is_active' => (int) ($integration['is_active'] ?? 1) === 1,
            'account_prefix' => trim((string) ($row['account_prefix'] ?? '')),
            'api_token_encrypted' => $encryptedToken,
            'has_api_token' => $encryptedToken !== null && $encryptedToken !== '',
            'department_id' => trim((string) ($row['department_id'] ?? '')),
            'default_kind' => trim((string) ($row['default_kind'] ?? 'vat')),
            'default_payment_to_days' => (int) ($row['default_payment_to_days'] ?? 7),
            'last_test_status' => trim((string) ($integration['last_test_status'] ?? '')),
            'last_test_http_code' => isset($integration['last_test_http_code'])
                ? (int) $integration['last_test_http_code']
                : null,
            'last_test_message' => trim((string) ($integration['last_test_message'] ?? '')),
            'last_test_at' => trim((string) ($integration['last_test_at'] ?? '')),
        ];
    }

    /**
     * Backward-compatible list API for callers that still expect accounts collection.
     *
     * @return array<int, array<string, mixed>>
     */
    public function findAll(): array
    {
        return [$this->getSettings()];
    }

    /**
     * @return array<string, mixed>|null
     */
    public function findByIntegrationId(int $integrationId): ?array
    {
        $globalId = $this->getIntegrationId();
        if ($integrationId <= 0 || $integrationId !== $globalId) {
            return null;
        }

        return $this->getSettings();
    }

    /**
     * @param array<string, mixed> $payload
     */
    public function saveSettings(array $payload): void
    {
        $this->ensureRow();
        $integrationId = $this->getIntegrationId();
        $row = $this->fetchSettingsRow();
        if ($row === null) {
            throw new IntegrationConfigException('Brak rekordu konfiguracji Fakturowni.');
        }

        $prefix = strtolower(trim((string) ($payload['account_prefix'] ?? '')));
        if (!preg_match('/^[a-z0-9][a-z0-9-]{1,62}$/', $prefix)) {
            throw new IntegrationConfigException('Prefix konta (subdomena) ma niepoprawny format.');
        }

        $defaultKind = trim((string) ($payload['default_kind'] ?? 'vat'));
        if ($defaultKind === '') {
            $defaultKind = 'vat';
        }
        if (mb_strlen($defaultKind) > 32) {
            throw new IntegrationConfigException('Typ dokumentu jest za dlugi (max 32 znaki).');
        }

        $defaultPaymentDays = (int) ($payload['default_payment_to_days'] ?? 7);
        if ($defaultPaymentDays < 0) {
            $defaultPaymentDays = 0;
        }
        if ($defaultPaymentDays > 120) {
            $defaultPaymentDays = 120;
        }

        $departmentId = StringHelper::nullableString(trim((string) ($payload['department_id'] ?? '')));
        $currentEncrypted = $this->resolveApiTokenEncrypted($row, $this->integrations->findById($integrationId));
        $apiToken = trim((string) ($payload['api_token'] ?? ''));
        $nextEncrypted = $currentEncrypted;
        if ($apiToken !== '') {
            $nextEncrypted = $this->cipher->encrypt($apiToken);
        }

        if ($nextEncrypted === null || $nextEncrypted === '') {
            throw new IntegrationConfigException('Podaj token API Fakturowni.');
        }

        $statement = $this->pdo->prepare(
            'UPDATE fakturownia_integration_settings
             SET account_prefix = :account_prefix,
                 api_token_encrypted = :api_token_encrypted,
                 department_id = :department_id,
                 default_kind = :default_kind,
                 default_payment_to_days = :default_payment_to_days,
                 updated_at = NOW()
             WHERE id = 1'
        );
        $statement->execute([
            'account_prefix' => $prefix,
            'api_token_encrypted' => $nextEncrypted,
            'department_id' => $departmentId,
            'default_kind' => $defaultKind,
            'default_payment_to_days' => $defaultPaymentDays,
        ]);

        $this->updateIntegrationActive($integrationId, !empty($payload['is_active']));
        $this->integrations->updateApiKeyEncrypted($integrationId, $nextEncrypted);
    }

    /**
     * Compatibility wrapper for old callers.
     *
     * @param array<string, mixed> $payload
     */
    public function save(?int $integrationId, array $payload): int
    {
        $this->saveSettings($payload);

        return $this->getIntegrationId();
    }

    /**
     * @return array{integration_id: int, account_prefix: string, api_token: string, department_id: string, default_kind: string, default_payment_to_days: int}|null
     */
    public function getCredentials(): ?array
    {
        $settings = $this->getSettings();
        $integrationId = (int) ($settings['integration_id'] ?? 0);
        $prefix = trim((string) ($settings['account_prefix'] ?? ''));
        $encrypted = $settings['api_token_encrypted'] ?? null;
        if ($integrationId <= 0 || $prefix === '' || !is_string($encrypted) || trim($encrypted) === '') {
            return null;
        }

        $token = trim($this->cipher->decrypt($encrypted));
        if ($token === '') {
            return null;
        }

        return [
            'integration_id' => $integrationId,
            'account_prefix' => $prefix,
            'api_token' => $token,
            'department_id' => trim((string) ($settings['department_id'] ?? '')),
            'default_kind' => trim((string) ($settings['default_kind'] ?? 'vat')),
            'default_payment_to_days' => (int) ($settings['default_payment_to_days'] ?? 7),
        ];
    }

    public function getIntegrationId(): int
    {
        $existing = $this->integrations->findFirstByType(self::INTEGRATION_TYPE);
        if ($existing !== null) {
            return (int) ($existing['id'] ?? 0);
        }

        return $this->integrations->ensureIntegration(
            self::INTEGRATION_TYPE,
            self::INTEGRATION_NAME,
            self::INTEGRATION_BASE_URL,
            15,
            true
        );
    }

    public function getDecryptedToken(int $integrationId): ?string
    {
        $globalId = $this->getIntegrationId();
        if ($integrationId !== $globalId) {
            return null;
        }

        $credentials = $this->getCredentials();

        return $credentials['api_token'] ?? null;
    }

    public function delete(int $integrationId): void
    {
        throw new IntegrationConfigException('Fakturownia ma jedna globalna konfiguracje i nie moze byc usunieta z UI.');
    }

    private function ensureRow(): void
    {
        $integrationId = $this->getIntegrationId();
        $statement = $this->pdo->prepare(
            'INSERT INTO fakturownia_integration_settings
                (id, integration_id, account_prefix, default_kind, default_payment_to_days, created_at, updated_at)
             VALUES
                (1, :integration_id, "", "vat", 7, NOW(), NOW())
             ON DUPLICATE KEY UPDATE integration_id = VALUES(integration_id), updated_at = VALUES(updated_at)'
        );
        $statement->execute(['integration_id' => $integrationId]);
    }

    /**
     * @return array<string, mixed>|null
     */
    private function fetchSettingsRow(): ?array
    {
        try {
            $statement = $this->pdo->prepare('SELECT * FROM fakturownia_integration_settings WHERE id = 1 LIMIT 1');
            $statement->execute();
            $row = $statement->fetch(PDO::FETCH_ASSOC);
        } catch (Throwable) {
            return null;
        }

        return is_array($row) ? $row : null;
    }

    /**
     * @param array<string, mixed>|null $row
     * @param array<string, mixed>|null $integration
     */
    private function resolveApiTokenEncrypted(?array $row, ?array $integration): ?string
    {
        $settingsValue = trim((string) ($row['api_token_encrypted'] ?? ''));
        if ($settingsValue !== '') {
            return $settingsValue;
        }

        $baseValue = trim((string) ($integration['api_key_encrypted'] ?? ''));
        return StringHelper::nullableString($baseValue);
    }

    private function updateIntegrationActive(int $integrationId, bool $isActive): void
    {
        $statement = $this->pdo->prepare(
            'UPDATE integrations
             SET name = :name,
                 base_url = :base_url,
                 timeout_seconds = :timeout_seconds,
                 is_active = :is_active,
                 updated_at = NOW()
             WHERE id = :id AND type = :type'
        );
        $statement->execute([
            'id' => $integrationId,
            'type' => self::INTEGRATION_TYPE,
            'name' => self::INTEGRATION_NAME,
            'base_url' => self::INTEGRATION_BASE_URL,
            'timeout_seconds' => 15,
            'is_active' => $isActive ? 1 : 0,
        ]);
    }
}
