<?php
declare(strict_types=1);

namespace App\Modules\Settings;

use PDO;
use Throwable;

final class ShopproStatusSyncService
{
    private const DIRECTION_SHOPPRO_TO_ORDERPRO = 'shoppro_to_orderpro';
    private const DIRECTION_ORDERPRO_TO_SHOPPRO = 'orderpro_to_shoppro';

    public function __construct(
        private readonly ShopproIntegrationsRepository $integrations,
        private readonly ShopproOrdersSyncService $ordersSyncService,
        private readonly ShopproApiClient $apiClient,
        private readonly ShopproOrderSyncStateRepository $syncState,
        private readonly ShopproStatusMappingRepository $statusMappings,
        private readonly PDO $pdo
    ) {
    }

    /**
     * @return array<string, mixed>
     */
    public function sync(): array
    {
        $pullIntegrationIds = [];
        $pushResults = [];

        foreach ($this->integrations->listIntegrations() as $integration) {
            $integrationId = (int) ($integration['id'] ?? 0);
            if ($integrationId <= 0 || empty($integration['is_active']) || empty($integration['has_api_key'])) {
                continue;
            }

            $direction = trim((string) ($integration['order_status_sync_direction'] ?? self::DIRECTION_SHOPPRO_TO_ORDERPRO));

            if ($direction === self::DIRECTION_ORDERPRO_TO_SHOPPRO) {
                $pushResults[] = $this->syncPushDirection($integrationId);
                continue;
            }

            $pullIntegrationIds[] = $integrationId;
        }

        $result = $this->buildPullResult($pullIntegrationIds);
        $result['push_results'] = $pushResults;

        return $result;
    }

    /**
     * @param array<int, int> $pullIntegrationIds
     * @return array<string, mixed>
     */
    private function buildPullResult(array $pullIntegrationIds): array
    {
        if ($pullIntegrationIds === []) {
            return [
                'ok' => true,
                'processed' => 0,
                'checked_integrations' => 0,
                'direction' => self::DIRECTION_SHOPPRO_TO_ORDERPRO,
            ];
        }

        $result = $this->ordersSyncService->sync([
            'max_pages' => 3,
            'page_limit' => 50,
            'max_orders' => 200,
            'ignore_orders_fetch_enabled' => true,
            'allowed_integration_ids' => $pullIntegrationIds,
        ]);
        $result['ok'] = true;
        $result['direction'] = self::DIRECTION_SHOPPRO_TO_ORDERPRO;

        return $result;
    }

    /**
     * @return array<string, mixed>
     */
    private function syncPushDirection(int $integrationId): array
    {
        $pushed = 0;
        $skipped = 0;
        $failed = 0;

        try {
            $baseUrl = $this->resolveBaseUrl($integrationId);
            $apiKey = $this->integrations->getApiKeyDecrypted($integrationId);
            $timeout = $this->resolveTimeout($integrationId);

            if ($baseUrl === '' || $apiKey === null || trim($apiKey) === '') {
                return [
                    'ok' => false,
                    'integration_id' => $integrationId,
                    'direction' => self::DIRECTION_ORDERPRO_TO_SHOPPRO,
                    'message' => 'Brak poprawnych danych API dla integracji.',
                    'pushed' => 0,
                    'skipped' => 0,
                    'failed' => 0,
                ];
            }

            $reverseMap = $this->buildReverseStatusMap($integrationId);
            if ($reverseMap === []) {
                return [
                    'ok' => true,
                    'integration_id' => $integrationId,
                    'direction' => self::DIRECTION_ORDERPRO_TO_SHOPPRO,
                    'message' => 'Brak mapowania statusow dla integracji.',
                    'pushed' => 0,
                    'skipped' => 0,
                    'failed' => 0,
                ];
            }

            $lastPushedAt = $this->syncState->getLastStatusPushedAt($integrationId);
            $orders = $this->findOrdersWithManualStatusChanges($integrationId, $lastPushedAt);

            if ($orders === []) {
                return [
                    'ok' => true,
                    'integration_id' => $integrationId,
                    'direction' => self::DIRECTION_ORDERPRO_TO_SHOPPRO,
                    'message' => 'Brak zamowien do synchronizacji.',
                    'pushed' => 0,
                    'skipped' => 0,
                    'failed' => 0,
                ];
            }

            $latestChangeAt = $lastPushedAt;

            foreach ($orders as $order) {
                $sourceOrderId = (int) ($order['source_order_id'] ?? 0);
                $orderproStatus = strtolower(trim((string) ($order['external_status_id'] ?? '')));
                $changeAt = (string) ($order['latest_change'] ?? '');

                if ($sourceOrderId <= 0 || $orderproStatus === '') {
                    $skipped++;
                    continue;
                }

                $shopproStatusCode = $reverseMap[$orderproStatus] ?? null;
                if ($shopproStatusCode === null) {
                    $skipped++;
                    continue;
                }

                $shopproStatusId = (int) $shopproStatusCode;
                if ($shopproStatusId <= 0) {
                    $skipped++;
                    continue;
                }

                $apiResult = $this->apiClient->updateOrderStatus(
                    $baseUrl,
                    $apiKey,
                    $timeout,
                    $sourceOrderId,
                    $shopproStatusId,
                    false
                );

                if (($apiResult['ok'] ?? false) === true) {
                    $pushed++;
                } else {
                    $failed++;
                }

                if ($changeAt !== '' && ($latestChangeAt === null || $changeAt > $latestChangeAt)) {
                    $latestChangeAt = $changeAt;
                }
            }

            if ($latestChangeAt !== null && $latestChangeAt !== $lastPushedAt) {
                $this->syncState->updateLastStatusPushedAt($integrationId, $latestChangeAt);
            }
        } catch (Throwable $e) {
            return [
                'ok' => false,
                'integration_id' => $integrationId,
                'direction' => self::DIRECTION_ORDERPRO_TO_SHOPPRO,
                'message' => $e->getMessage(),
                'pushed' => $pushed,
                'skipped' => $skipped,
                'failed' => $failed,
            ];
        }

        return [
            'ok' => true,
            'integration_id' => $integrationId,
            'direction' => self::DIRECTION_ORDERPRO_TO_SHOPPRO,
            'pushed' => $pushed,
            'skipped' => $skipped,
            'failed' => $failed,
        ];
    }

    /**
     * @return array<string, string> orderpro_status_code => shoppro_status_code
     */
    private function buildReverseStatusMap(int $integrationId): array
    {
        $rows = $this->statusMappings->listByIntegration($integrationId);
        $map = [];
        foreach ($rows as $row) {
            $shopCode = trim((string) ($row['shoppro_status_code'] ?? ''));
            $orderCode = strtolower(trim((string) ($row['orderpro_status_code'] ?? '')));
            if ($shopCode === '' || $orderCode === '') {
                continue;
            }
            if (!isset($map[$orderCode])) {
                $map[$orderCode] = $shopCode;
            }
        }

        return $map;
    }

    /**
     * @return array<int, array{order_id:int,source_order_id:string,external_status_id:string,latest_change:string}>
     */
    private function findOrdersWithManualStatusChanges(int $integrationId, ?string $lastPushedAt): array
    {
        $fallbackDate = date('Y-m-d H:i:s', strtotime('-24 hours'));
        $sinceDate = ($lastPushedAt !== null && $lastPushedAt !== '') ? $lastPushedAt : $fallbackDate;

        try {
            $statement = $this->pdo->prepare(
                'SELECT o.id AS order_id, o.source_order_id, o.external_status_id,
                        MAX(h.changed_at) AS latest_change
                FROM order_status_history h
                JOIN orders o ON o.id = h.order_id
                WHERE o.integration_id = :integration_id
                  AND h.change_source = :change_source
                  AND h.changed_at > :since_date
                GROUP BY o.id, o.source_order_id, o.external_status_id
                ORDER BY latest_change ASC
                LIMIT 50'
            );
            $statement->execute([
                'integration_id' => $integrationId,
                'change_source' => 'manual',
                'since_date' => $sinceDate,
            ]);
            $rows = $statement->fetchAll(PDO::FETCH_ASSOC);

            return is_array($rows) ? $rows : [];
        } catch (Throwable) {
            return [];
        }
    }

    private function resolveBaseUrl(int $integrationId): string
    {
        $integration = $this->integrations->findIntegration($integrationId);
        if ($integration === null) {
            return '';
        }

        return rtrim(trim((string) ($integration['base_url'] ?? '')), '/');
    }

    private function resolveTimeout(int $integrationId): int
    {
        $integration = $this->integrations->findIntegration($integrationId);
        if ($integration === null) {
            return 10;
        }

        return max(1, min(120, (int) ($integration['timeout_seconds'] ?? 10)));
    }
}
