<?php
declare(strict_types=1);

namespace App\Modules\Settings;

use App\Core\Support\StringHelper;
use DateTimeImmutable;
use RuntimeException;
use AppCoreExceptionsIntegrationConfigException;
use Throwable;

final class AllegroOrdersSyncService
{
    public function __construct(
        private readonly AllegroIntegrationRepository $integrationRepository,
        private readonly AllegroOrderSyncStateRepository $syncStateRepository,
        private readonly AllegroTokenManager $tokenManager,
        private readonly AllegroApiClient $apiClient,
        private readonly AllegroOrderImportService $orderImportService
    ) {
    }

    /**
     * @param array<string, mixed> $options
     * @return array<string, mixed>
     */
    public function sync(array $options = []): array
    {
        $settings = $this->integrationRepository->getSettings();
        if (empty($settings['orders_fetch_enabled'])) {
            return [
                'enabled' => false,
                'processed' => 0,
                'imported_created' => 0,
                'imported_updated' => 0,
                'failed' => 0,
                'skipped' => 0,
                'cursor_before' => null,
                'cursor_after' => null,
                'errors' => [],
            ];
        }

        $integrationId = $this->integrationRepository->getActiveIntegrationId();
        if ($integrationId <= 0) {
            throw new IntegrationConfigException('Brak aktywnej integracji bazowej Allegro.');
        }

        $now = new DateTimeImmutable('now');
        $state = $this->syncStateRepository->getState($integrationId);
        $this->syncStateRepository->markRunStarted($integrationId, $now);

        $maxPages = max(1, min(20, (int) ($options['max_pages'] ?? 5)));
        $pageLimit = max(1, min(100, (int) ($options['page_limit'] ?? 50)));
        $maxOrders = max(1, min(1000, (int) ($options['max_orders'] ?? 200)));

        $startDateRaw = trim((string) ($settings['orders_fetch_start_date'] ?? ''));
        $startDate = $this->normalizeStartDate($startDateRaw);

        $cursorUpdatedAt = StringHelper::nullableString((string) ($state['last_synced_updated_at'] ?? ''));
        $cursorSourceOrderId = StringHelper::nullableString((string) ($state['last_synced_source_order_id'] ?? ''));

        $result = [
            'enabled' => true,
            'processed' => 0,
            'imported_created' => 0,
            'imported_updated' => 0,
            'failed' => 0,
            'skipped' => 0,
            'cursor_before' => $cursorUpdatedAt,
            'cursor_after' => $cursorUpdatedAt,
            'errors' => [],
        ];

        $latestProcessedUpdatedAt = $cursorUpdatedAt;
        $latestProcessedSourceOrderId = $cursorSourceOrderId;

        try {
            [$accessToken, $env] = $this->tokenManager->resolveToken();
            $offset = 0;
            $shouldStop = false;

            for ($page = 0; $page < $maxPages; $page++) {
                try {
                    $response = $this->apiClient->listCheckoutForms($env, $accessToken, $pageLimit, $offset);
                } catch (RuntimeException $exception) {
                    if (trim($exception->getMessage()) !== 'ALLEGRO_HTTP_401') {
                        throw $exception;
                    }

                    [$accessToken, $env] = $this->tokenManager->resolveToken();
                    $response = $this->apiClient->listCheckoutForms($env, $accessToken, $pageLimit, $offset);
                }

                $forms = is_array($response['checkoutForms'] ?? null) ? $response['checkoutForms'] : [];
                if ($forms === []) {
                    break;
                }

                foreach ($forms as $form) {
                    if (!is_array($form)) {
                        continue;
                    }

                    $sourceOrderId = trim((string) ($form['id'] ?? ''));
                    $sourceUpdatedAt = StringHelper::normalizeDateTime((string) ($form['updatedAt'] ?? $form['boughtAt'] ?? ''));
                    if ($sourceOrderId === '' || $sourceUpdatedAt === null) {
                        $result['skipped'] = (int) $result['skipped'] + 1;
                        continue;
                    }

                    if ($startDate !== null && $sourceUpdatedAt < $startDate) {
                        $shouldStop = true;
                        break;
                    }

                    if (!$this->isAfterCursor($sourceUpdatedAt, $sourceOrderId, $cursorUpdatedAt, $cursorSourceOrderId)) {
                        $shouldStop = true;
                        break;
                    }

                    if (((int) $result['processed']) >= $maxOrders) {
                        $shouldStop = true;
                        break;
                    }

                    $result['processed'] = (int) $result['processed'] + 1;

                    try {
                        $importResult = $this->orderImportService->importSingleOrder($sourceOrderId, 'orders_sync');
                        if (!empty($importResult['created'])) {
                            $result['imported_created'] = (int) $result['imported_created'] + 1;
                        } else {
                            $result['imported_updated'] = (int) $result['imported_updated'] + 1;
                        }
                    } catch (Throwable $exception) {
                        $result['failed'] = (int) $result['failed'] + 1;
                        $errors = is_array($result['errors']) ? $result['errors'] : [];
                        if (count($errors) < 20) {
                            $errors[] = [
                                'source_order_id' => $sourceOrderId,
                                'error' => $exception->getMessage(),
                            ];
                        }
                        $result['errors'] = $errors;
                    }

                    if ($this->isAfterCursor(
                        $sourceUpdatedAt,
                        $sourceOrderId,
                        $latestProcessedUpdatedAt,
                        $latestProcessedSourceOrderId
                    )) {
                        $latestProcessedUpdatedAt = $sourceUpdatedAt;
                        $latestProcessedSourceOrderId = $sourceOrderId;
                    }
                }

                if ($shouldStop || count($forms) < $pageLimit) {
                    break;
                }

                $offset += $pageLimit;
            }

            $this->syncStateRepository->markRunSuccess(
                $integrationId,
                new DateTimeImmutable('now'),
                $latestProcessedUpdatedAt,
                $latestProcessedSourceOrderId
            );
            $result['cursor_after'] = $latestProcessedUpdatedAt;

            return $result;
        } catch (Throwable $exception) {
            $this->syncStateRepository->markRunFailed(
                $integrationId,
                new DateTimeImmutable('now'),
                $exception->getMessage()
            );
            throw $exception;
        }
    }

    private function normalizeStartDate(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 . ' 00:00:00';
    }

    private function isAfterCursor(
        string $sourceUpdatedAt,
        string $sourceOrderId,
        ?string $cursorUpdatedAt,
        ?string $cursorSourceOrderId
    ): bool {
        if ($cursorUpdatedAt === null || $cursorUpdatedAt === '') {
            return true;
        }

        if ($sourceUpdatedAt > $cursorUpdatedAt) {
            return true;
        }
        if ($sourceUpdatedAt < $cursorUpdatedAt) {
            return false;
        }

        if ($cursorSourceOrderId === null || $cursorSourceOrderId === '') {
            return true;
        }

        return strcmp($sourceOrderId, $cursorSourceOrderId) > 0;
    }

}
