<?php
declare(strict_types=1);

namespace App\Modules\Settings;

use App\Core\Support\StringHelper;
use PDO;
use AppCorexceptionsIntegrationConfigException;
use Throwable;

final class ShopproIntegrationsRepository
{
    private const TYPE = 'shoppro';
    private const DIRECTION_SHOPPRO_TO_ORDERPRO = 'shoppro_to_orderpro';
    private const DIRECTION_ORDERPRO_TO_SHOPPRO = 'orderpro_to_shoppro';

    private readonly IntegrationSecretCipher $cipher;

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

    /**
     * @return array<int, array<string, mixed>>
     */
    public function listIntegrations(): array
    {
        try {
            $statement = $this->pdo->prepare(
                'SELECT *
                FROM integrations
                WHERE type = :type
                ORDER BY is_active DESC, name ASC, id ASC'
            );
            $statement->execute(['type' => self::TYPE]);
            $rows = $statement->fetchAll(PDO::FETCH_ASSOC);
        } catch (Throwable) {
            return [];
        }

        if (!is_array($rows)) {
            return [];
        }

        $result = [];
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }

            $encrypted = trim((string) ($row['api_key_encrypted'] ?? ''));
            $result[] = [
                'id' => (int) ($row['id'] ?? 0),
                'name' => trim((string) ($row['name'] ?? '')),
                'base_url' => trim((string) ($row['base_url'] ?? '')),
                'timeout_seconds' => (int) ($row['timeout_seconds'] ?? 10),
                'is_active' => (int) ($row['is_active'] ?? 0) === 1,
                'orders_fetch_enabled' => (int) ($row['orders_fetch_enabled'] ?? 0) === 1,
                'orders_fetch_start_date' => trim((string) ($row['orders_fetch_start_date'] ?? '')),
                'order_status_sync_direction' => $this->normalizeStatusSyncDirection((string) ($row['order_status_sync_direction'] ?? '')),
                'payment_sync_status_codes' => $this->decodeStatusCodesJson($row['payment_sync_status_codes_json'] ?? null),
                'has_api_key' => $encrypted !== '',
                'last_test_status' => trim((string) ($row['last_test_status'] ?? '')),
                'last_test_http_code' => $row['last_test_http_code'] !== null ? (int) $row['last_test_http_code'] : null,
                'last_test_message' => trim((string) ($row['last_test_message'] ?? '')),
                'last_test_at' => trim((string) ($row['last_test_at'] ?? '')),
            ];
        }

        return $result;
    }

    /**
     * @return array<string, mixed>|null
     */
    public function findIntegration(int $integrationId): ?array
    {
        if ($integrationId <= 0) {
            return null;
        }

        try {
            $statement = $this->pdo->prepare(
                'SELECT *
                FROM integrations
                WHERE id = :id AND type = :type
                LIMIT 1'
            );
            $statement->execute([
                'id' => $integrationId,
                'type' => self::TYPE,
            ]);
            $row = $statement->fetch(PDO::FETCH_ASSOC);
        } catch (Throwable) {
            return null;
        }

        if (!is_array($row)) {
            return null;
        }

        return [
            'id' => (int) ($row['id'] ?? 0),
            'name' => trim((string) ($row['name'] ?? '')),
            'base_url' => trim((string) ($row['base_url'] ?? '')),
            'timeout_seconds' => (int) ($row['timeout_seconds'] ?? 10),
            'is_active' => (int) ($row['is_active'] ?? 0) === 1,
            'orders_fetch_enabled' => (int) ($row['orders_fetch_enabled'] ?? 0) === 1,
            'orders_fetch_start_date' => trim((string) ($row['orders_fetch_start_date'] ?? '')),
            'order_status_sync_direction' => $this->normalizeStatusSyncDirection((string) ($row['order_status_sync_direction'] ?? '')),
            'payment_sync_status_codes' => $this->decodeStatusCodesJson($row['payment_sync_status_codes_json'] ?? null),
            'payment_sync_status_codes_json' => $row['payment_sync_status_codes_json'] ?? null,
            'api_key_encrypted' => trim((string) ($row['api_key_encrypted'] ?? '')),
            'has_api_key' => trim((string) ($row['api_key_encrypted'] ?? '')) !== '',
        ];
    }

    public function getApiKeyDecrypted(int $integrationId): ?string
    {
        $integration = $this->findIntegration($integrationId);
        if ($integration === null) {
            return null;
        }

        $encrypted = trim((string) ($integration['api_key_encrypted'] ?? ''));
        if ($encrypted === '') {
            return null;
        }

        $decrypted = (string) $this->cipher->decrypt($encrypted);
        $trimmed = trim($decrypted);
        return $trimmed === '' ? null : $trimmed;
    }

    /**
     * @param array<string, mixed> $payload
     */
    public function saveIntegration(array $payload): int
    {
        $integrationId = (int) ($payload['integration_id'] ?? 0);
        $name = trim((string) ($payload['name'] ?? ''));
        $baseUrl = rtrim(trim((string) ($payload['base_url'] ?? '')), '/');
        $timeoutSeconds = max(1, (int) ($payload['timeout_seconds'] ?? 10));
        $isActive = !empty($payload['is_active']) ? 1 : 0;
        $ordersFetchEnabled = !empty($payload['orders_fetch_enabled']) ? 1 : 0;
        $ordersFetchStartDate = $this->normalizeDate((string) ($payload['orders_fetch_start_date'] ?? ''));
        $statusSyncDirection = $this->normalizeStatusSyncDirection((string) ($payload['order_status_sync_direction'] ?? ''));
        $paymentSyncStatusCodesJson = $this->encodeStatusCodesJson($payload['payment_sync_status_codes'] ?? []);
        $apiKey = trim((string) ($payload['api_key'] ?? ''));

        if ($integrationId > 0) {
            $existing = $this->findIntegration($integrationId);
            if ($existing === null) {
                throw new IntegrationConfigException('INTEGRATION_NOT_FOUND');
            }

            $encryptedApiKey = trim((string) ($existing['api_key_encrypted'] ?? ''));
            if ($apiKey !== '') {
                $encryptedApiKey = (string) $this->cipher->encrypt($apiKey);
            }

            $statement = $this->pdo->prepare(
                'UPDATE integrations
                SET name = :name,
                    base_url = :base_url,
                    api_key_encrypted = :api_key_encrypted,
                    timeout_seconds = :timeout_seconds,
                    is_active = :is_active,
                    orders_fetch_enabled = :orders_fetch_enabled,
                    orders_fetch_start_date = :orders_fetch_start_date,
                    order_status_sync_direction = :order_status_sync_direction,
                    payment_sync_status_codes_json = :payment_sync_status_codes_json,
                    updated_at = NOW()
                WHERE id = :id AND type = :type'
            );
            $statement->execute([
                'id' => $integrationId,
                'type' => self::TYPE,
                'name' => $name,
                'base_url' => $baseUrl,
                'api_key_encrypted' => StringHelper::nullableString($encryptedApiKey),
                'timeout_seconds' => $timeoutSeconds,
                'is_active' => $isActive,
                'orders_fetch_enabled' => $ordersFetchEnabled,
                'orders_fetch_start_date' => $ordersFetchStartDate,
                'order_status_sync_direction' => $statusSyncDirection,
                'payment_sync_status_codes_json' => $paymentSyncStatusCodesJson,
            ]);

            return $integrationId;
        }

        $encryptedApiKey = $this->cipher->encrypt($apiKey);
        $statement = $this->pdo->prepare(
            'INSERT INTO integrations (
                type, name, base_url, api_key_encrypted, timeout_seconds, is_active, orders_fetch_enabled, orders_fetch_start_date, order_status_sync_direction, payment_sync_status_codes_json, created_at, updated_at
            ) VALUES (
                :type, :name, :base_url, :api_key_encrypted, :timeout_seconds, :is_active, :orders_fetch_enabled, :orders_fetch_start_date, :order_status_sync_direction, :payment_sync_status_codes_json, NOW(), NOW()
            )'
        );
        $statement->execute([
            'type' => self::TYPE,
            'name' => $name,
            'base_url' => $baseUrl,
            'api_key_encrypted' => StringHelper::nullableString((string) $encryptedApiKey),
            'timeout_seconds' => $timeoutSeconds,
            'is_active' => $isActive,
            'orders_fetch_enabled' => $ordersFetchEnabled,
            'orders_fetch_start_date' => $ordersFetchStartDate,
            'order_status_sync_direction' => $statusSyncDirection,
            'payment_sync_status_codes_json' => $paymentSyncStatusCodesJson,
        ]);

        return (int) $this->pdo->lastInsertId();
    }

    /**
     * @return array{status:string,http_code:int|null,message:string}
     */
    public function testConnection(int $integrationId): array
    {
        $integration = $this->findIntegration($integrationId);
        if ($integration === null) {
            return [
                'status' => 'error',
                'http_code' => null,
                'message' => 'Nie znaleziono integracji shopPRO.',
            ];
        }

        $apiKeyEncrypted = trim((string) ($integration['api_key_encrypted'] ?? ''));
        $apiKey = $apiKeyEncrypted !== '' ? (string) $this->cipher->decrypt($apiKeyEncrypted) : '';
        if ($apiKey === '') {
            return [
                'status' => 'error',
                'http_code' => null,
                'message' => 'Brak zapisanego klucza API.',
            ];
        }

        $baseUrl = rtrim((string) ($integration['base_url'] ?? ''), '/');
        $timeout = max(1, min(120, (int) ($integration['timeout_seconds'] ?? 10)));
        $url = $baseUrl . '/api.php?endpoint=dictionaries&action=statuses';

        $curl = curl_init($url);
        if ($curl === false) {
            return [
                'status' => 'error',
                'http_code' => null,
                'message' => 'Nie udalo sie zainicjalizowac polaczenia HTTP.',
            ];
        }

        curl_setopt_array($curl, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => $timeout,
            CURLOPT_CONNECTTIMEOUT => $timeout,
            CURLOPT_HTTPHEADER => [
                'Accept: application/json',
                'X-Api-Key: ' . $apiKey,
            ],
        ]);

        $body = curl_exec($curl);
        $httpCode = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE);
        $curlError = curl_error($curl);
        curl_close($curl);

        if ($body === false) {
            $message = trim($curlError) !== '' ? trim($curlError) : 'Nieznany blad transportu HTTP.';
            $result = [
                'status' => 'error',
                'http_code' => $httpCode > 0 ? $httpCode : null,
                'message' => $message,
            ];
            $this->storeTestResult($integrationId, $url, $result);
            return $result;
        }

        $decoded = json_decode((string) $body, true);
        if (!is_array($decoded)) {
            $result = [
                'status' => 'error',
                'http_code' => $httpCode > 0 ? $httpCode : null,
                'message' => 'Odpowiedz nie jest poprawnym JSON.',
            ];
            $this->storeTestResult($integrationId, $url, $result);
            return $result;
        }

        $apiStatus = trim((string) ($decoded['status'] ?? ''));
        $isOk = $httpCode >= 200 && $httpCode < 300 && $apiStatus === 'ok';
        $message = $isOk
            ? 'Polaczenie z shopPRO dziala poprawnie.'
            : trim((string) ($decoded['message'] ?? 'Blad odpowiedzi API shopPRO.'));

        $result = [
            'status' => $isOk ? 'ok' : 'error',
            'http_code' => $httpCode > 0 ? $httpCode : null,
            'message' => $message,
        ];
        $this->storeTestResult($integrationId, $url, $result);

        return $result;
    }

    /**
     * @return array{ok:bool,http_code:int|null,message:string,statuses:array<int,array{code:string,name:string}>}
     */
    public function fetchOrderStatuses(int $integrationId): array
    {
        $integration = $this->findIntegration($integrationId);
        if ($integration === null) {
            return [
                'ok' => false,
                'http_code' => null,
                'message' => 'Nie znaleziono integracji shopPRO.',
                'statuses' => [],
            ];
        }

        $apiKeyEncrypted = trim((string) ($integration['api_key_encrypted'] ?? ''));
        $apiKey = $apiKeyEncrypted !== '' ? (string) $this->cipher->decrypt($apiKeyEncrypted) : '';
        if ($apiKey === '') {
            return [
                'ok' => false,
                'http_code' => null,
                'message' => 'Brak zapisanego klucza API.',
                'statuses' => [],
            ];
        }

        $baseUrl = rtrim((string) ($integration['base_url'] ?? ''), '/');
        $timeout = max(1, min(120, (int) ($integration['timeout_seconds'] ?? 10)));
        $url = $baseUrl . '/api.php?endpoint=dictionaries&action=statuses';

        $curl = curl_init($url);
        if ($curl === false) {
            return [
                'ok' => false,
                'http_code' => null,
                'message' => 'Nie udalo sie zainicjalizowac polaczenia HTTP.',
                'statuses' => [],
            ];
        }

        curl_setopt_array($curl, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => $timeout,
            CURLOPT_CONNECTTIMEOUT => $timeout,
            CURLOPT_HTTPHEADER => [
                'Accept: application/json',
                'X-Api-Key: ' . $apiKey,
            ],
        ]);

        $body = curl_exec($curl);
        $httpCode = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE);
        $curlError = curl_error($curl);
        curl_close($curl);

        if ($body === false) {
            return [
                'ok' => false,
                'http_code' => $httpCode > 0 ? $httpCode : null,
                'message' => trim($curlError) !== '' ? trim($curlError) : 'Nieznany blad transportu HTTP.',
                'statuses' => [],
            ];
        }

        $decoded = json_decode((string) $body, true);
        if (!is_array($decoded)) {
            return [
                'ok' => false,
                'http_code' => $httpCode > 0 ? $httpCode : null,
                'message' => 'Odpowiedz nie jest poprawnym JSON.',
                'statuses' => [],
            ];
        }

        $payload = isset($decoded['data']) && is_array($decoded['data'])
            ? $decoded['data']
            : $decoded;

        $rawStatuses = [];
        if (isset($payload['statuses']) && is_array($payload['statuses'])) {
            $rawStatuses = $payload['statuses'];
        } elseif (isset($payload['order_statuses']) && is_array($payload['order_statuses'])) {
            $rawStatuses = $payload['order_statuses'];
        } elseif ($payload !== [] && array_keys($payload) === range(0, count($payload) - 1)) {
            $rawStatuses = $payload;
        }

        $statuses = $this->normalizeStatusesPayload($rawStatuses);
        return [
            'ok' => true,
            'http_code' => $httpCode > 0 ? $httpCode : null,
            'message' => '',
            'statuses' => $statuses,
        ];
    }

    /**
     * @param array{status:string,http_code:int|null,message:string} $result
     */
    private function storeTestResult(int $integrationId, string $endpointUrl, array $result): void
    {
        $status = trim((string) ($result['status'] ?? 'error'));
        $httpCode = $result['http_code'] ?? null;
        $message = mb_substr(trim((string) ($result['message'] ?? '')), 0, 255);

        try {
            $statement = $this->pdo->prepare(
                'UPDATE integrations
                SET last_test_status = :last_test_status,
                    last_test_http_code = :last_test_http_code,
                    last_test_message = :last_test_message,
                    last_test_at = NOW(),
                    updated_at = NOW()
                WHERE id = :id AND type = :type'
            );
            $statement->execute([
                'id' => $integrationId,
                'type' => self::TYPE,
                'last_test_status' => StringHelper::nullableString($status),
                'last_test_http_code' => $httpCode,
                'last_test_message' => StringHelper::nullableString($message),
            ]);

            $log = $this->pdo->prepare(
                'INSERT INTO integration_test_logs (
                    integration_id, status, http_code, message, endpoint_url, tested_at
                ) VALUES (
                    :integration_id, :status, :http_code, :message, :endpoint_url, NOW()
                )'
            );
            $log->execute([
                'integration_id' => $integrationId,
                'status' => $status,
                'http_code' => $httpCode,
                'message' => $message,
                'endpoint_url' => $endpointUrl,
            ]);
        } catch (Throwable) {
            return;
        }
    }

    private function normalizeDate(string $value): ?string
    {
        $trimmed = trim($value);
        return $trimmed === '' ? null : $trimmed;
    }

    private function normalizeStatusSyncDirection(string $value): string
    {
        $normalized = trim(mb_strtolower($value));
        if ($normalized === self::DIRECTION_ORDERPRO_TO_SHOPPRO) {
            return self::DIRECTION_ORDERPRO_TO_SHOPPRO;
        }

        return self::DIRECTION_SHOPPRO_TO_ORDERPRO;
    }

    /**
     * @param mixed $value
     * @return array<int, string>
     */
    private function decodeStatusCodesJson(mixed $value): array
    {
        if (!is_string($value) || trim($value) === '') {
            return [];
        }

        $decoded = json_decode($value, true);
        if (!is_array($decoded)) {
            return [];
        }

        $result = [];
        $seen = [];
        foreach ($decoded as $rawCode) {
            $code = strtolower(trim((string) $rawCode));
            if ($code === '' || isset($seen[$code])) {
                continue;
            }
            $seen[$code] = true;
            $result[] = $code;
        }

        return $result;
    }

    private function encodeStatusCodesJson(mixed $rawCodes): ?string
    {
        if (!is_array($rawCodes)) {
            return null;
        }

        $codes = [];
        $seen = [];
        foreach ($rawCodes as $rawCode) {
            $code = strtolower(trim((string) $rawCode));
            if ($code === '' || isset($seen[$code])) {
                continue;
            }
            $seen[$code] = true;
            $codes[] = $code;
        }

        if ($codes === []) {
            return null;
        }

        return json_encode($codes, JSON_UNESCAPED_UNICODE);
    }

    /**
     * @param array<int, mixed> $rawStatuses
     * @return array<int, array{code:string,name:string}>
     */
    private function normalizeStatusesPayload(array $rawStatuses): array
    {
        $result = [];
        $seen = [];

        foreach ($rawStatuses as $key => $item) {
            if (!is_array($item)) {
                $codeFromKey = trim((string) $key);
                $nameFromValue = trim((string) $item);
                if ($codeFromKey !== '') {
                    $normalizedCode = mb_strtolower($codeFromKey);
                    if (!isset($seen[$normalizedCode])) {
                        $seen[$normalizedCode] = true;
                        $result[] = [
                            'code' => $codeFromKey,
                            'name' => $nameFromValue !== '' ? $nameFromValue : $codeFromKey,
                        ];
                    }
                }
                continue;
            }

            if (!is_array($item)) {
                continue;
            }

            $code = trim((string) (
                $item['code']
                ?? $item['status_code']
                ?? $item['status']
                ?? $item['symbol']
                ?? $item['slug']
                ?? $item['id']
                ?? $key
            ));
            $name = trim((string) (
                $item['name']
                ?? $item['status_name']
                ?? $item['label']
                ?? $item['title']
                ?? $item['value']
                ?? $code
            ));
            if ($code === '') {
                continue;
            }

            $normalizedCode = mb_strtolower($code);
            if (isset($seen[$normalizedCode])) {
                continue;
            }

            $seen[$normalizedCode] = true;
            $result[] = [
                'code' => $code,
                'name' => $name !== '' ? $name : $code,
            ];
        }

        return $result;
    }
}
