<?php
declare(strict_types=1);

namespace App\Modules\Settings;

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

final class AllegroIntegrationRepository
{
    private const DEFAULT_ENVIRONMENT = 'sandbox';
    private const INTEGRATION_TYPE = 'allegro';

    private readonly IntegrationsRepository $integrations;
    private readonly IntegrationSecretCipher $cipher;
    private ?bool $hasIntegrationIdColumn = null;

    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(?string $environment = null): array
    {
        $env = $this->resolveEnvironment($environment);
        $row = $this->fetchRowByEnv($env);
        if ($row === null) {
            return $this->defaultSettings($env);
        }

        return [
            'environment' => $this->normalizeEnvironment((string) ($row['environment'] ?? self::DEFAULT_ENVIRONMENT)),
            'integration_id' => (int) ($row['integration_id'] ?? 0),
            'client_id' => trim((string) ($row['client_id'] ?? '')),
            'has_client_secret' => trim((string) ($row['client_secret_encrypted'] ?? '')) !== '',
            'redirect_uri' => trim((string) ($row['redirect_uri'] ?? '')),
            'orders_fetch_enabled' => (int) ($row['orders_fetch_enabled'] ?? 0) === 1,
            'orders_fetch_start_date' => $this->normalizeDateOrNull((string) ($row['orders_fetch_start_date'] ?? '')),
            'is_connected' => trim((string) ($row['refresh_token_encrypted'] ?? '')) !== '',
            'token_expires_at' => trim((string) ($row['token_expires_at'] ?? '')),
            'connected_at' => trim((string) ($row['connected_at'] ?? '')),
        ];
    }

    public function getActiveEnvironment(): string
    {
        try {
            $statement = $this->pdo->prepare(
                "SELECT setting_value FROM app_settings WHERE setting_key = 'allegro_active_environment' LIMIT 1"
            );
            $statement->execute();
            $row = $statement->fetch(PDO::FETCH_ASSOC);
        } catch (Throwable) {
            return self::DEFAULT_ENVIRONMENT;
        }

        if (!is_array($row)) {
            return self::DEFAULT_ENVIRONMENT;
        }

        return $this->normalizeEnvironment(trim((string) ($row['setting_value'] ?? self::DEFAULT_ENVIRONMENT)));
    }

    public function getActiveIntegrationId(): int
    {
        $environment = $this->getActiveEnvironment();
        $row = $this->fetchRowByEnv($environment);
        $rowIntegrationId = (int) ($row['integration_id'] ?? 0);
        if ($rowIntegrationId > 0) {
            return $rowIntegrationId;
        }

        $integrationId = $this->ensureBaseIntegration($environment);
        $this->assignIntegrationIdIfPossible($environment, $integrationId);

        return $integrationId;
    }

    public function setActiveEnvironment(string $environment): void
    {
        $env = $this->normalizeEnvironment($environment);
        $statement = $this->pdo->prepare(
            "INSERT INTO app_settings (setting_key, setting_value, updated_at)
            VALUES ('allegro_active_environment', :env, NOW())
            ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value), updated_at = NOW()"
        );
        $statement->execute(['env' => $env]);
    }

    /**
     * @param array<string, mixed> $payload
     */
    public function saveSettings(array $payload): void
    {
        $env = $this->normalizeEnvironment((string) ($payload['environment'] ?? self::DEFAULT_ENVIRONMENT));
        $this->ensureRow($env);
        $current = $this->fetchRowByEnv($env);
        if ($current === null) {
            throw new IntegrationConfigException('Brak rekordu konfiguracji Allegro.');
        }

        $clientSecret = trim((string) ($payload['client_secret'] ?? ''));
        $clientSecretEncrypted = trim((string) ($current['client_secret_encrypted'] ?? ''));
        if ($clientSecret !== '') {
            $clientSecretEncrypted = (string) $this->cipher->encrypt($clientSecret);
        }

        $statement = $this->pdo->prepare(
            'UPDATE allegro_integration_settings
            SET client_id = :client_id,
                client_secret_encrypted = :client_secret_encrypted,
                redirect_uri = :redirect_uri,
                orders_fetch_enabled = :orders_fetch_enabled,
                orders_fetch_start_date = :orders_fetch_start_date,
                updated_at = NOW()
            WHERE environment = :environment'
        );
        $statement->execute([
            'environment' => $env,
            'client_id' => StringHelper::nullableString((string) ($payload['client_id'] ?? '')),
            'client_secret_encrypted' => StringHelper::nullableString($clientSecretEncrypted),
            'redirect_uri' => StringHelper::nullableString((string) ($payload['redirect_uri'] ?? '')),
            'orders_fetch_enabled' => ((bool) ($payload['orders_fetch_enabled'] ?? false)) ? 1 : 0,
            'orders_fetch_start_date' => StringHelper::nullableString((string) ($payload['orders_fetch_start_date'] ?? '')),
        ]);

        $this->setActiveEnvironment($env);
    }

    /**
     * @return array<string, string>|null
     */
    public function getOAuthCredentials(): ?array
    {
        $env = $this->getActiveEnvironment();
        $row = $this->fetchRowByEnv($env);
        if ($row === null) {
            return null;
        }

        $clientId = trim((string) ($row['client_id'] ?? ''));
        $clientSecret = (string) $this->cipher->decrypt((string) ($row['client_secret_encrypted'] ?? ''));
        $redirectUri = trim((string) ($row['redirect_uri'] ?? ''));
        if ($clientId === '' || $clientSecret === '' || $redirectUri === '') {
            return null;
        }

        return [
            'environment' => $this->normalizeEnvironment((string) ($row['environment'] ?? self::DEFAULT_ENVIRONMENT)),
            'client_id' => $clientId,
            'client_secret' => $clientSecret,
            'redirect_uri' => $redirectUri,
        ];
    }

    public function saveTokens(
        string $accessToken,
        string $refreshToken,
        string $tokenType,
        string $scope,
        ?string $tokenExpiresAt
    ): void {
        $env = $this->getActiveEnvironment();
        $this->ensureRow($env);

        $statement = $this->pdo->prepare(
            'UPDATE allegro_integration_settings
            SET access_token_encrypted = :access_token_encrypted,
                refresh_token_encrypted = :refresh_token_encrypted,
                token_type = :token_type,
                token_scope = :token_scope,
                token_expires_at = :token_expires_at,
                connected_at = NOW(),
                updated_at = NOW()
            WHERE environment = :environment'
        );
        $statement->execute([
            'environment' => $env,
            'access_token_encrypted' => $this->cipher->encrypt($accessToken),
            'refresh_token_encrypted' => $this->cipher->encrypt($refreshToken),
            'token_type' => StringHelper::nullableString($tokenType),
            'token_scope' => StringHelper::nullableString($scope),
            'token_expires_at' => StringHelper::nullableString((string) $tokenExpiresAt),
        ]);
    }

    /**
     * @return array<string, string>|null
     */
    public function getRefreshTokenCredentials(): ?array
    {
        $env = $this->getActiveEnvironment();
        $row = $this->fetchRowByEnv($env);
        if ($row === null) {
            return null;
        }

        $clientId = trim((string) ($row['client_id'] ?? ''));
        $clientSecret = (string) $this->cipher->decrypt((string) ($row['client_secret_encrypted'] ?? ''));
        $refreshToken = (string) $this->cipher->decrypt((string) ($row['refresh_token_encrypted'] ?? ''));
        if ($clientId === '' || $clientSecret === '' || $refreshToken === '') {
            return null;
        }

        return [
            'environment' => $this->normalizeEnvironment((string) ($row['environment'] ?? self::DEFAULT_ENVIRONMENT)),
            'client_id' => $clientId,
            'client_secret' => $clientSecret,
            'refresh_token' => $refreshToken,
        ];
    }

    /**
     * @return array<string, string>|null
     */
    public function getTokenCredentials(): ?array
    {
        $env = $this->getActiveEnvironment();
        $row = $this->fetchRowByEnv($env);
        if ($row === null) {
            return null;
        }

        $clientId = trim((string) ($row['client_id'] ?? ''));
        $clientSecret = (string) $this->cipher->decrypt((string) ($row['client_secret_encrypted'] ?? ''));
        $refreshToken = (string) $this->cipher->decrypt((string) ($row['refresh_token_encrypted'] ?? ''));
        $accessToken = (string) $this->cipher->decrypt((string) ($row['access_token_encrypted'] ?? ''));
        if ($clientId === '' || $clientSecret === '' || $refreshToken === '') {
            return null;
        }

        return [
            'environment' => $this->normalizeEnvironment((string) ($row['environment'] ?? self::DEFAULT_ENVIRONMENT)),
            'client_id' => $clientId,
            'client_secret' => $clientSecret,
            'refresh_token' => $refreshToken,
            'access_token' => $accessToken,
            'token_expires_at' => trim((string) ($row['token_expires_at'] ?? '')),
        ];
    }

    private function ensureRow(string $environment): void
    {
        $env = $this->normalizeEnvironment($environment);
        $integrationId = $this->ensureBaseIntegration($env);

        if ($this->hasIntegrationIdColumn()) {
            $statement = $this->pdo->prepare(
                'INSERT INTO allegro_integration_settings (
                    integration_id, environment, orders_fetch_enabled, created_at, updated_at
                ) VALUES (
                    :integration_id, :environment, 0, NOW(), NOW()
                )
                ON DUPLICATE KEY UPDATE
                    updated_at = updated_at'
            );
            $statement->execute([
                'integration_id' => $integrationId,
                'environment' => $env,
            ]);
            $this->assignIntegrationIdIfPossible($env, $integrationId);
            return;
        }

        $statement = $this->pdo->prepare(
            'INSERT INTO allegro_integration_settings (
                environment, orders_fetch_enabled, created_at, updated_at
            ) VALUES (
                :environment, 0, NOW(), NOW()
            )
            ON DUPLICATE KEY UPDATE
                updated_at = updated_at'
        );
        $statement->execute([
            'environment' => $env,
        ]);
    }

    private function ensureBaseIntegration(string $environment): int
    {
        $env = $this->normalizeEnvironment($environment);
        return $this->integrations->ensureIntegration(
            self::INTEGRATION_TYPE,
            $this->integrationNameForEnvironment($env),
            $this->integrationBaseUrlForEnvironment($env),
            20,
            true
        );
    }

    private function assignIntegrationIdIfPossible(string $environment, int $integrationId): void
    {
        if ($integrationId <= 0 || !$this->hasIntegrationIdColumn()) {
            return;
        }

        try {
            $statement = $this->pdo->prepare(
                'UPDATE allegro_integration_settings
                SET integration_id = :integration_id,
                    updated_at = NOW()
                WHERE environment = :environment
                  AND (integration_id IS NULL OR integration_id = 0)'
            );
            $statement->execute([
                'integration_id' => $integrationId,
                'environment' => $this->normalizeEnvironment($environment),
            ]);
        } catch (Throwable) {
            return;
        }
    }

    private function hasIntegrationIdColumn(): bool
    {
        if ($this->hasIntegrationIdColumn !== null) {
            return $this->hasIntegrationIdColumn;
        }

        try {
            $statement = $this->pdo->prepare(
                "SELECT 1
                FROM information_schema.COLUMNS
                WHERE TABLE_SCHEMA = DATABASE()
                  AND TABLE_NAME = 'allegro_integration_settings'
                  AND COLUMN_NAME = 'integration_id'
                LIMIT 1"
            );
            $statement->execute();
            $value = $statement->fetchColumn();
        } catch (Throwable) {
            $this->hasIntegrationIdColumn = false;
            return false;
        }

        $this->hasIntegrationIdColumn = $value !== false;
        return $this->hasIntegrationIdColumn;
    }

    private function integrationNameForEnvironment(string $environment): string
    {
        return $environment === 'production' ? 'Allegro Production' : 'Allegro Sandbox';
    }

    private function integrationBaseUrlForEnvironment(string $environment): string
    {
        return $environment === 'production'
            ? 'https://api.allegro.pl'
            : 'https://api.allegro.pl.allegrosandbox.pl';
    }

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

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

    private function resolveEnvironment(?string $environment): string
    {
        if ($environment !== null) {
            return $this->normalizeEnvironment($environment);
        }

        return $this->getActiveEnvironment();
    }

    /**
     * @return array<string, mixed>
     */
    private function defaultSettings(string $environment = self::DEFAULT_ENVIRONMENT): array
    {
        return [
            'environment' => $this->normalizeEnvironment($environment),
            'integration_id' => 0,
            'client_id' => '',
            'has_client_secret' => false,
            'redirect_uri' => '',
            'orders_fetch_enabled' => false,
            'orders_fetch_start_date' => null,
            'is_connected' => false,
            'token_expires_at' => '',
            'connected_at' => '',
        ];
    }

    private function normalizeEnvironment(string $environment): string
    {
        $value = trim(strtolower($environment));
        if ($value === 'production') {
            return 'production';
        }

        return 'sandbox';
    }

    private function normalizeDateOrNull(string $value): ?string
    {
        $trimmed = trim($value);
        if ($trimmed === '') {
            return null;
        }
        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $trimmed) !== 1) {
            return null;
        }

        return $trimmed;
    }

}
