<?php
declare(strict_types=1);

namespace App\Modules\Settings;

use App\Core\Http\Request;
use App\Core\Http\Response;
use App\Core\I18n\Translator;
use App\Core\Security\Csrf;
use App\Core\Support\Flash;
use App\Core\View\Template;
use App\Modules\Auth\AuthService;
use App\Modules\Cron\CronRepository;
use DateInterval;
use DateTimeImmutable;
use App\Core\Constants\IntegrationSources;
use App\Core\Constants\RedirectPaths;
use RuntimeException;
use Throwable;

final class ShopproIntegrationsController
{
    private const ORDERS_IMPORT_JOB_TYPE = 'shoppro_orders_import';
    private const ORDERS_IMPORT_DEFAULT_INTERVAL_SECONDS = 300;
    private const ORDERS_IMPORT_DEFAULT_PRIORITY = 90;
    private const ORDERS_IMPORT_DEFAULT_MAX_ATTEMPTS = 3;
    private const ORDER_STATUS_SYNC_JOB_TYPE = 'shoppro_order_status_sync';
    private const ORDER_STATUS_SYNC_DEFAULT_INTERVAL_SECONDS = 900;
    private const ORDER_STATUS_SYNC_DEFAULT_PRIORITY = 100;
    private const ORDER_STATUS_SYNC_DEFAULT_MAX_ATTEMPTS = 3;
    private const PAYMENT_SYNC_JOB_TYPE = 'shoppro_payment_status_sync';
    private const PAYMENT_SYNC_DEFAULT_INTERVAL_SECONDS = 600;
    private const PAYMENT_SYNC_DEFAULT_PRIORITY = 105;
    private const PAYMENT_SYNC_DEFAULT_MAX_ATTEMPTS = 3;

    public function __construct(
        private readonly Template $template,
        private readonly Translator $translator,
        private readonly AuthService $auth,
        private readonly ShopproIntegrationsRepository $repository,
        private readonly ShopproStatusMappingRepository $statusMappings,
        private readonly OrderStatusRepository $orderStatuses,
        private readonly CronRepository $cronRepository,
        private readonly CarrierDeliveryMethodMappingRepository $deliveryMappings,
        private readonly AllegroIntegrationRepository $allegroIntegrationRepository,
        private readonly AllegroOAuthClient $allegroOAuthClient,
        private readonly AllegroApiClient $allegroApiClient,
        private readonly ?ApaczkaIntegrationRepository $apaczkaRepository = null,
        private readonly ?ApaczkaApiClient $apaczkaApiClient = null
    ) {
    }

    public function index(Request $request): Response
    {
        $integrations = $this->repository->listIntegrations();
        $forceNewMode = trim((string) $request->input('new', '')) === '1';
        $selectedId = max(0, (int) $request->input('id', 0));
        $selectedIntegration = $selectedId > 0 ? $this->repository->findIntegration($selectedId) : null;
        if (!$forceNewMode && $selectedIntegration === null && $integrations !== []) {
            $firstId = (int) ($integrations[0]['id'] ?? 0);
            if ($firstId > 0) {
                $selectedIntegration = $this->repository->findIntegration($firstId);
            }
        }
        $this->ensureImportScheduleExists();
        $this->ensureStatusSyncScheduleExists();
        $this->ensurePaymentSyncScheduleExists();
        $activeTab = $this->resolveTab((string) $request->input('tab', 'integration'));
        $discoveredStatuses = $this->readDiscoveredStatuses();
        $statusRows = $selectedIntegration !== null
            ? $this->buildStatusRows((int) ($selectedIntegration['id'] ?? 0), $discoveredStatuses)
            : [];
        $deliveryServicesData = $activeTab === 'delivery'
            ? $this->loadDeliveryServices()
            : [[], [], ''];
        $deliveryMappings = $selectedIntegration !== null
            ? $this->deliveryMappings->listMappings(IntegrationSources::SHOPPRO, (int) ($selectedIntegration['id'] ?? 0))
            : [];
        $orderDeliveryMethods = $selectedIntegration !== null
            ? $this->deliveryMappings->getDistinctOrderDeliveryMethods(IntegrationSources::SHOPPRO, (int) ($selectedIntegration['id'] ?? 0))
            : [];

        $html = $this->template->render('settings/shoppro', [
            'title' => $this->translator->get('settings.integrations.title'),
            'activeMenu' => 'settings',
            'activeSettings' => 'shoppro',
            'user' => $this->auth->user(),
            'csrfToken' => Csrf::token(),
            'activeTab' => $activeTab,
            'rows' => $integrations,
            'selectedIntegration' => $selectedIntegration,
            'form' => $this->buildFormValues($selectedIntegration),
            'ordersImportIntervalMinutes' => $this->currentImportIntervalMinutes(),
            'statusSyncIntervalMinutes' => $this->currentStatusSyncIntervalMinutes(),
            'paymentSyncIntervalMinutes' => $this->currentPaymentSyncIntervalMinutes(),
            'statusRows' => $statusRows,
            'orderproStatuses' => $this->orderStatuses->listStatuses(),
            'deliveryMappings' => $deliveryMappings,
            'orderDeliveryMethods' => $orderDeliveryMethods,
            'allegroDeliveryServices' => $deliveryServicesData[0],
            'apaczkaDeliveryServices' => $deliveryServicesData[1],
            'allegroDeliveryServicesError' => $deliveryServicesData[2],
            'inpostDeliveryServices' => array_values(array_filter(
                $deliveryServicesData[0],
                static fn (array $svc): bool => stripos((string) ($svc['carrierId'] ?? ''), 'inpost') !== false
            )),
            'errorMessage' => (string) Flash::get('settings_error', ''),
            'successMessage' => (string) Flash::get('settings_success', ''),
        ], 'layouts/app');

        return Response::html($html);
    }

    public function save(Request $request): Response
    {
        $integrationId = max(0, (int) $request->input('integration_id', 0));
        $tab = $this->resolveTab((string) $request->input('tab', 'integration'));
        $redirectBase = RedirectPaths::SHOPPRO_INTEGRATION;
        $redirectTo = $this->buildRedirectUrl($integrationId, $tab);

        $existing = $integrationId > 0 ? $this->repository->findIntegration($integrationId) : null;
        $accessError = $this->validateSaveAccess((string) $request->input('_token', ''), $integrationId, $existing, $tab);
        if ($accessError !== null) {
            return $accessError;
        }

        $name = trim((string) $request->input('name', ''));
        $baseUrl = rtrim(trim((string) $request->input('base_url', '')), '/');
        $apiKey = trim((string) $request->input('api_key', ''));
        $hasExistingApiKey = (bool) ($existing['has_api_key'] ?? false);
        $ordersFetchStartDate = trim((string) $request->input('orders_fetch_start_date', ''));

        $validationError = $this->validateSaveInput($name, $baseUrl, $apiKey, $hasExistingApiKey, $tab, $ordersFetchStartDate, $integrationId);
        if ($validationError !== null) {
            Flash::set('settings_error', $validationError);
            return Response::redirect($redirectTo);
        }

        try {
            $statusSyncDirectionInput = $request->input('order_status_sync_direction', null);
            $statusSyncDirection = $statusSyncDirectionInput !== null
                ? trim((string) $statusSyncDirectionInput)
                : (string) ($existing['order_status_sync_direction'] ?? 'shoppro_to_orderpro');
            $paymentSyncStatusCodesInput = $request->input('payment_sync_status_codes', null);
            if (is_array($paymentSyncStatusCodesInput)) {
                $paymentSyncStatusCodes = $paymentSyncStatusCodesInput;
            } elseif ($tab === 'settings') {
                $paymentSyncStatusCodes = [];
            } else {
                $paymentSyncStatusCodes = $existing['payment_sync_status_codes'] ?? [];
            }

            $savedId = $this->repository->saveIntegration([
                'integration_id' => $integrationId,
                'name' => $name,
                'base_url' => $baseUrl,
                'api_key' => $apiKey,
                'timeout_seconds' => max(1, min(120, (int) $request->input('timeout_seconds', 10))),
                'is_active' => $request->input('is_active', ''),
                'orders_fetch_enabled' => $request->input('orders_fetch_enabled', ''),
                'orders_fetch_start_date' => $ordersFetchStartDate,
                'order_status_sync_direction' => $statusSyncDirection,
                'payment_sync_status_codes' => $paymentSyncStatusCodes,
            ]);
            $this->saveImportIntervalIfRequested($request);
            $this->saveStatusSyncIntervalIfRequested($request);
            $this->savePaymentSyncIntervalIfRequested($request);

            $flashKey = $integrationId > 0
                ? 'settings.integrations.flash.updated'
                : 'settings.integrations.flash.created';
            Flash::set('settings_success', $this->translator->get($flashKey));
            $redirectTo = $this->buildRedirectUrl($savedId, $tab);
        } catch (Throwable) {
            Flash::set('settings_error', $this->translator->get('settings.integrations.flash.failed'));
        }

        return Response::redirect($redirectTo);
    }

    public function test(Request $request): Response
    {
        $integrationId = max(0, (int) $request->input('integration_id', 0));
        $tab = $this->resolveTab((string) $request->input('tab', 'integration'));
        $redirectBase = RedirectPaths::SHOPPRO_INTEGRATION;
        $redirectTo = $this->buildRedirectUrl($integrationId, $tab);

        if (!Csrf::validate((string) $request->input('_token', ''))) {
            Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired'));
            return Response::redirect($redirectTo);
        }

        if ($integrationId <= 0) {
            Flash::set('settings_error', $this->translator->get('settings.integrations.flash.not_found'));
            return Response::redirect($this->buildRedirectUrl(0, $tab));
        }

        $result = $this->repository->testConnection($integrationId);
        $isOk = (string) ($result['status'] ?? 'error') === 'ok';
        $message = trim((string) ($result['message'] ?? ''));
        $httpCode = $result['http_code'] ?? null;

        if ($isOk) {
            Flash::set('settings_success', $this->translator->get('settings.integrations.flash.test_ok'));
        } else {
            $suffix = $message !== '' ? ' ' . $message : '';
            $httpPart = $httpCode !== null ? ' (HTTP ' . (string) $httpCode . ')' : '';
            Flash::set(
                'settings_error',
                $this->translator->get('settings.integrations.flash.test_failed') . $httpPart . $suffix
            );
        }

        return Response::redirect($redirectTo);
    }

    public function saveStatusMappings(Request $request): Response
    {
        $integrationId = max(0, (int) $request->input('integration_id', 0));
        $redirectTo = $this->buildRedirectUrl($integrationId, 'statuses');

        $accessError = $this->validateCsrfAndIntegrationAccess((string) $request->input('_token', ''), $integrationId, 'statuses');
        if ($accessError !== null) {
            return $accessError;
        }

        $shopCodes = $request->input('shoppro_status_code', []);
        $shopNames = $request->input('shoppro_status_name', []);
        $orderCodes = $request->input('orderpro_status_code', []);
        if (!is_array($shopCodes) || !is_array($shopNames) || !is_array($orderCodes)) {
            Flash::set('settings_error', $this->translator->get('settings.integrations.statuses.flash.invalid_payload'));
            return Response::redirect($redirectTo);
        }

        $allowedOrderpro = $this->resolveAllowedOrderproStatusCodes();
        $rowsCount = min(count($shopCodes), count($shopNames), count($orderCodes));
        $mappings = [];
        for ($index = 0; $index < $rowsCount; $index++) {
            $shopCode = trim((string) ($shopCodes[$index] ?? ''));
            $shopName = trim((string) ($shopNames[$index] ?? ''));
            $orderCode = strtolower(trim((string) ($orderCodes[$index] ?? '')));

            if ($shopCode === '') {
                continue;
            }

            if ($orderCode === '') {
                continue;
            }

            if (!isset($allowedOrderpro[$orderCode])) {
                continue;
            }

            $mappings[] = [
                'shoppro_status_code' => $shopCode,
                'shoppro_status_name' => $shopName,
                'orderpro_status_code' => $orderCode,
            ];
        }

        try {
            $this->statusMappings->replaceForIntegration($integrationId, $mappings);
            Flash::set('settings_success', $this->translator->get('settings.integrations.statuses.flash.saved'));
        } catch (Throwable $exception) {
            Flash::set(
                'settings_error',
                $this->translator->get('settings.integrations.statuses.flash.save_failed') . ' ' . $exception->getMessage()
            );
        }

        return Response::redirect($redirectTo);
    }

    public function syncStatuses(Request $request): Response
    {
        $integrationId = max(0, (int) $request->input('integration_id', 0));
        $redirectTo = $this->buildRedirectUrl($integrationId, 'statuses');

        $accessError = $this->validateCsrfAndIntegrationAccess((string) $request->input('_token', ''), $integrationId, 'statuses');
        if ($accessError !== null) {
            return $accessError;
        }

        $result = $this->repository->fetchOrderStatuses($integrationId);
        if (($result['ok'] ?? false) !== true) {
            $message = trim((string) ($result['message'] ?? ''));
            Flash::set(
                'settings_error',
                $this->translator->get('settings.integrations.statuses.flash.sync_failed') . ($message !== '' ? ' ' . $message : '')
            );
            return Response::redirect($redirectTo);
        }

        $statuses = $result['statuses'] ?? [];
        Flash::set('shoppro_discovered_statuses', is_array($statuses) ? $statuses : []);
        Flash::set(
            'settings_success',
            $this->translator->get('settings.integrations.statuses.flash.sync_ok', [
                'count' => (string) (is_array($statuses) ? count($statuses) : 0),
            ])
        );

        return Response::redirect($redirectTo);
    }

    public function saveDeliveryMappings(Request $request): Response
    {
        $integrationId = max(0, (int) $request->input('integration_id', 0));
        $redirectTo = $this->buildRedirectUrl($integrationId, 'delivery');

        if (!Csrf::validate((string) $request->input('_token', ''))) {
            Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired'));
            return Response::redirect($redirectTo);
        }

        if ($integrationId <= 0 || $this->repository->findIntegration($integrationId) === null) {
            Flash::set('settings_error', $this->translator->get('settings.integrations.flash.not_found'));
            return Response::redirect($this->buildRedirectUrl(0, 'delivery'));
        }

        $orderMethods = (array) $request->input('order_delivery_method', []);
        $carriers = (array) $request->input('carrier', []);
        $allegroMethodIds = (array) $request->input('allegro_delivery_method_id', []);
        $apaczkaMethodIds = (array) $request->input('apaczka_delivery_method_id', []);
        $credentialsIds = (array) $request->input('allegro_credentials_id', []);
        $carrierIds = (array) $request->input('allegro_carrier_id', []);
        $serviceNames = (array) $request->input('allegro_service_name', []);


        $mappings = [];
        foreach ($orderMethods as $index => $orderMethod) {
            $orderMethodValue = trim((string) $orderMethod);
            $carrier = trim((string) ($carriers[$index] ?? 'allegro'));
            $provider = $carrier === 'apaczka' ? 'apaczka' : 'allegro_wza';
            $providerServiceId = $provider === 'apaczka'
                ? trim((string) ($apaczkaMethodIds[$index] ?? ''))
                : trim((string) ($allegroMethodIds[$index] ?? ''));
            if ($orderMethodValue === '' || $providerServiceId === '') {
                continue;
            }

            $mappings[] = [
                'order_delivery_method' => $orderMethodValue,
                'provider' => $provider,
                'provider_service_id' => $providerServiceId,
                'provider_account_id' => $provider === 'allegro_wza' ? trim((string) ($credentialsIds[$index] ?? '')) : '',
                'provider_carrier_id' => $provider === 'allegro_wza' ? trim((string) ($carrierIds[$index] ?? '')) : '',
                'provider_service_name' => trim((string) ($serviceNames[$index] ?? '')),
            ];
        }

        try {
            $this->deliveryMappings->saveMappings(IntegrationSources::SHOPPRO, $integrationId, $mappings);
            Flash::set('settings_success', $this->translator->get('settings.integrations.delivery.flash.saved'));
        } catch (Throwable $exception) {
            Flash::set(
                'settings_error',
                $this->translator->get('settings.integrations.delivery.flash.save_failed') . ' ' . $exception->getMessage()
            );
        }

        return Response::redirect($redirectTo);
    }

    /**
     * @param array<string, mixed>|null $integration
     * @return array<string, mixed>
     */
    private function buildFormValues(?array $integration): array
    {
        if ($integration === null) {
            return [
                'integration_id' => 0,
                'name' => '',
                'base_url' => '',
                'timeout_seconds' => 10,
                'is_active' => 1,
                'orders_fetch_enabled' => 0,
                'orders_fetch_start_date' => '',
                'order_status_sync_direction' => 'shoppro_to_orderpro',
                'payment_sync_status_codes' => [],
            ];
        }

        return [
            'integration_id' => (int) ($integration['id'] ?? 0),
            'name' => (string) ($integration['name'] ?? ''),
            'base_url' => (string) ($integration['base_url'] ?? ''),
            'timeout_seconds' => (int) ($integration['timeout_seconds'] ?? 10),
            'is_active' => !empty($integration['is_active']) ? 1 : 0,
            'orders_fetch_enabled' => !empty($integration['orders_fetch_enabled']) ? 1 : 0,
            'orders_fetch_start_date' => (string) ($integration['orders_fetch_start_date'] ?? ''),
            'order_status_sync_direction' => (string) ($integration['order_status_sync_direction'] ?? 'shoppro_to_orderpro'),
            'payment_sync_status_codes' => is_array($integration['payment_sync_status_codes'] ?? null)
                ? $integration['payment_sync_status_codes']
                : [],
        ];
    }

    /**
     * @param array<string, mixed>|null $existing
     */
    private function validateSaveAccess(string $token, int $integrationId, ?array $existing, string $tab): ?Response
    {
        if (!Csrf::validate($token)) {
            Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired'));
            return Response::redirect($this->buildRedirectUrl($integrationId, $tab));
        }
        if ($integrationId > 0 && $existing === null) {
            Flash::set('settings_error', $this->translator->get('settings.integrations.flash.not_found'));
            return Response::redirect($this->buildRedirectUrl(0, $tab));
        }
        return null;
    }

    private function validateSaveInput(
        string $name,
        string $baseUrl,
        string $apiKey,
        bool $hasExistingApiKey,
        string $tab,
        string $ordersFetchStartDate,
        int $integrationId
    ): ?string {
        if (mb_strlen($name) < 2) {
            return $this->translator->get('settings.integrations.validation.name_min');
        }
        if (!$this->isValidHttpUrl($baseUrl)) {
            return $this->translator->get('settings.integrations.validation.base_url_invalid');
        }
        if ($tab === 'integration' && $apiKey === '' && !$hasExistingApiKey) {
            return $this->translator->get('settings.integrations.validation.api_key_required');
        }
        if ($ordersFetchStartDate !== '' && !$this->isValidYmdDate($ordersFetchStartDate)) {
            return $this->translator->get('settings.integrations.validation.orders_fetch_start_date_invalid');
        }
        if ($this->isDuplicateName($integrationId, $name)) {
            return $this->translator->get('settings.integrations.validation.name_taken');
        }
        return null;
    }

    private function validateCsrfAndIntegrationAccess(string $token, int $integrationId, string $tab): ?Response
    {
        if (!Csrf::validate($token)) {
            Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired'));
            return Response::redirect($this->buildRedirectUrl($integrationId, $tab));
        }
        if ($integrationId <= 0 || $this->repository->findIntegration($integrationId) === null) {
            Flash::set('settings_error', $this->translator->get('settings.integrations.flash.not_found'));
            return Response::redirect($this->buildRedirectUrl(0, $tab));
        }
        return null;
    }

    private function isDuplicateName(int $currentId, string $name): bool
    {
        $needle = mb_strtolower(trim($name));
        if ($needle === '') {
            return false;
        }

        $rows = $this->repository->listIntegrations();
        foreach ($rows as $row) {
            $rowId = (int) ($row['id'] ?? 0);
            if ($rowId === $currentId) {
                continue;
            }

            $rowName = mb_strtolower(trim((string) ($row['name'] ?? '')));
            if ($rowName === $needle) {
                return true;
            }
        }

        return false;
    }

    private function isValidHttpUrl(string $value): bool
    {
        if (filter_var($value, FILTER_VALIDATE_URL) === false) {
            return false;
        }

        $scheme = strtolower((string) parse_url($value, PHP_URL_SCHEME));
        return in_array($scheme, ['http', 'https'], true);
    }

    private function isValidYmdDate(string $value): bool
    {
        $date = DateTimeImmutable::createFromFormat('Y-m-d', $value);
        return $date !== false && $date->format('Y-m-d') === $value;
    }

    /**
     * @return array<string, array{shoppro_status_code:string,shoppro_status_name:string,orderpro_status_code:string}>
     */
    private function buildStatusRows(int $integrationId, array $discoveredStatuses): array
    {
        $mappedRows = $this->statusMappings->listByIntegration($integrationId);
        $result = [];

        foreach ($mappedRows as $row) {
            $code = trim((string) ($row['shoppro_status_code'] ?? ''));
            if ($code === '') {
                continue;
            }

            $key = mb_strtolower($code);
            $result[$key] = [
                'shoppro_status_code' => $code,
                'shoppro_status_name' => trim((string) ($row['shoppro_status_name'] ?? '')),
                'orderpro_status_code' => strtolower(trim((string) ($row['orderpro_status_code'] ?? ''))),
            ];
        }

        foreach ($discoveredStatuses as $status) {
            if (!is_array($status)) {
                continue;
            }

            $code = trim((string) ($status['code'] ?? ''));
            if ($code === '') {
                continue;
            }

            $key = mb_strtolower($code);
            if (!isset($result[$key])) {
                $result[$key] = [
                    'shoppro_status_code' => $code,
                    'shoppro_status_name' => trim((string) ($status['name'] ?? '')),
                    'orderpro_status_code' => '',
                ];
            }
        }

        uasort($result, static function (array $left, array $right): int {
            return strcmp(
                strtolower((string) ($left['shoppro_status_code'] ?? '')),
                strtolower((string) ($right['shoppro_status_code'] ?? ''))
            );
        });

        return array_values($result);
    }

    /**
     * @return array<string, true>
     */
    private function resolveAllowedOrderproStatusCodes(): array
    {
        $allowed = [];
        foreach ($this->orderStatuses->listStatuses() as $status) {
            if (!is_array($status)) {
                continue;
            }

            $code = strtolower(trim((string) ($status['code'] ?? '')));
            if ($code === '') {
                continue;
            }

            $allowed[$code] = true;
        }

        return $allowed;
    }

    /**
     * @return array<int, array{code:string,name:string}>
     */
    private function readDiscoveredStatuses(): array
    {
        $raw = Flash::get('shoppro_discovered_statuses', []);
        if (!is_array($raw)) {
            return [];
        }

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

            $code = trim((string) ($item['code'] ?? ''));
            if ($code === '') {
                continue;
            }

            $result[] = [
                'code' => $code,
                'name' => trim((string) ($item['name'] ?? $code)),
            ];
        }

        return $result;
    }

    private function resolveTab(string $candidate): string
    {
        $value = trim($candidate);
        $allowed = ['integration', 'statuses', 'settings', 'delivery'];
        if (!in_array($value, $allowed, true)) {
            return 'integration';
        }

        return $value;
    }

    private function buildRedirectUrl(int $integrationId, string $tab): string
    {
        $url = RedirectPaths::SHOPPRO_INTEGRATION;
        $query = [];
        if ($integrationId > 0) {
            $query['id'] = (string) $integrationId;
        }
        if ($tab !== 'integration') {
            $query['tab'] = $tab;
        }

        if ($query === []) {
            return $url;
        }

        return $url . '?' . http_build_query($query);
    }

    private function currentImportIntervalMinutes(): int
    {
        $schedule = $this->findImportSchedule();
        $seconds = (int) ($schedule['interval_seconds'] ?? self::ORDERS_IMPORT_DEFAULT_INTERVAL_SECONDS);
        return max(1, min(1440, (int) floor(max(60, $seconds) / 60)));
    }

    /**
     * @return array<string, mixed>
     */
    private function findImportSchedule(): array
    {
        foreach ($this->cronRepository->listSchedules() as $schedule) {
            if ((string) ($schedule['job_type'] ?? '') !== self::ORDERS_IMPORT_JOB_TYPE) {
                continue;
            }

            return $schedule;
        }

        return [];
    }

    /**
     * @return array<string, mixed>
     */
    private function findStatusSyncSchedule(): array
    {
        foreach ($this->cronRepository->listSchedules() as $schedule) {
            if ((string) ($schedule['job_type'] ?? '') !== self::ORDER_STATUS_SYNC_JOB_TYPE) {
                continue;
            }

            return $schedule;
        }

        return [];
    }

    /**
     * @return array<string, mixed>
     */
    private function findPaymentSyncSchedule(): array
    {
        foreach ($this->cronRepository->listSchedules() as $schedule) {
            if ((string) ($schedule['job_type'] ?? '') !== self::PAYMENT_SYNC_JOB_TYPE) {
                continue;
            }

            return $schedule;
        }

        return [];
    }

    private function ensureImportScheduleExists(): void
    {
        try {
            if ($this->findImportSchedule() !== []) {
                return;
            }
            $this->cronRepository->upsertSchedule(
                self::ORDERS_IMPORT_JOB_TYPE,
                self::ORDERS_IMPORT_DEFAULT_INTERVAL_SECONDS,
                self::ORDERS_IMPORT_DEFAULT_PRIORITY,
                self::ORDERS_IMPORT_DEFAULT_MAX_ATTEMPTS,
                null,
                true
            );
        } catch (Throwable) {
            return;
        }
    }

    private function ensureStatusSyncScheduleExists(): void
    {
        try {
            if ($this->findStatusSyncSchedule() !== []) {
                return;
            }
            $this->cronRepository->upsertSchedule(
                self::ORDER_STATUS_SYNC_JOB_TYPE,
                self::ORDER_STATUS_SYNC_DEFAULT_INTERVAL_SECONDS,
                self::ORDER_STATUS_SYNC_DEFAULT_PRIORITY,
                self::ORDER_STATUS_SYNC_DEFAULT_MAX_ATTEMPTS,
                null,
                true
            );
        } catch (Throwable) {
            return;
        }
    }

    private function ensurePaymentSyncScheduleExists(): void
    {
        try {
            if ($this->findPaymentSyncSchedule() !== []) {
                return;
            }
            $this->cronRepository->upsertSchedule(
                self::PAYMENT_SYNC_JOB_TYPE,
                self::PAYMENT_SYNC_DEFAULT_INTERVAL_SECONDS,
                self::PAYMENT_SYNC_DEFAULT_PRIORITY,
                self::PAYMENT_SYNC_DEFAULT_MAX_ATTEMPTS,
                null,
                true
            );
        } catch (Throwable) {
            return;
        }
    }

    private function saveImportIntervalIfRequested(Request $request): void
    {
        if ($request->input('orders_import_interval_minutes', null) === null) {
            return;
        }

        $this->ensureImportScheduleExists();
        $minutes = max(1, min(1440, (int) $request->input('orders_import_interval_minutes', 5)));
        $schedule = $this->findImportSchedule();
        $priority = max(1, min(255, (int) ($schedule['priority'] ?? self::ORDERS_IMPORT_DEFAULT_PRIORITY)));
        $maxAttempts = max(1, min(20, (int) ($schedule['max_attempts'] ?? self::ORDERS_IMPORT_DEFAULT_MAX_ATTEMPTS)));
        $payload = is_array($schedule['payload'] ?? null) ? $schedule['payload'] : null;
        $enabled = array_key_exists('enabled', $schedule) ? !empty($schedule['enabled']) : true;

        $this->cronRepository->upsertSchedule(
            self::ORDERS_IMPORT_JOB_TYPE,
            $minutes * 60,
            $priority,
            $maxAttempts,
            $payload,
            $enabled
        );
    }

    private function currentStatusSyncIntervalMinutes(): int
    {
        $schedule = $this->findStatusSyncSchedule();
        $seconds = (int) ($schedule['interval_seconds'] ?? self::ORDER_STATUS_SYNC_DEFAULT_INTERVAL_SECONDS);
        return max(1, min(1440, (int) floor(max(60, $seconds) / 60)));
    }

    private function saveStatusSyncIntervalIfRequested(Request $request): void
    {
        if ($request->input('status_sync_interval_minutes', null) === null) {
            return;
        }

        $this->ensureStatusSyncScheduleExists();
        $minutes = max(1, min(1440, (int) $request->input('status_sync_interval_minutes', 15)));
        $schedule = $this->findStatusSyncSchedule();
        $priority = max(1, min(255, (int) ($schedule['priority'] ?? self::ORDER_STATUS_SYNC_DEFAULT_PRIORITY)));
        $maxAttempts = max(1, min(20, (int) ($schedule['max_attempts'] ?? self::ORDER_STATUS_SYNC_DEFAULT_MAX_ATTEMPTS)));
        $payload = is_array($schedule['payload'] ?? null) ? $schedule['payload'] : null;
        $enabled = array_key_exists('enabled', $schedule) ? !empty($schedule['enabled']) : true;

        $this->cronRepository->upsertSchedule(
            self::ORDER_STATUS_SYNC_JOB_TYPE,
            $minutes * 60,
            $priority,
            $maxAttempts,
            $payload,
            $enabled
        );
    }

    private function currentPaymentSyncIntervalMinutes(): int
    {
        $schedule = $this->findPaymentSyncSchedule();
        $seconds = (int) ($schedule['interval_seconds'] ?? self::PAYMENT_SYNC_DEFAULT_INTERVAL_SECONDS);
        return max(1, min(1440, (int) floor(max(60, $seconds) / 60)));
    }

    private function savePaymentSyncIntervalIfRequested(Request $request): void
    {
        if ($request->input('payment_sync_interval_minutes', null) === null) {
            return;
        }

        $this->ensurePaymentSyncScheduleExists();
        $minutes = max(1, min(1440, (int) $request->input('payment_sync_interval_minutes', 10)));
        $schedule = $this->findPaymentSyncSchedule();
        $priority = max(1, min(255, (int) ($schedule['priority'] ?? self::PAYMENT_SYNC_DEFAULT_PRIORITY)));
        $maxAttempts = max(1, min(20, (int) ($schedule['max_attempts'] ?? self::PAYMENT_SYNC_DEFAULT_MAX_ATTEMPTS)));
        $payload = is_array($schedule['payload'] ?? null) ? $schedule['payload'] : null;
        $enabled = array_key_exists('enabled', $schedule) ? !empty($schedule['enabled']) : true;

        $this->cronRepository->upsertSchedule(
            self::PAYMENT_SYNC_JOB_TYPE,
            $minutes * 60,
            $priority,
            $maxAttempts,
            $payload,
            $enabled
        );
    }

    /**
     * @return array{0: array<int, array<string, mixed>>, 1: array<int, array<string, mixed>>, 2: string}
     */
    private function loadDeliveryServices(): array
    {
        [$allegroServices, $errorMessage] = $this->loadAllegroDeliveryServices();
        [$apaczkaServices, $apaczkaError] = $this->loadApaczkaServices();

        if ($errorMessage === '' && $apaczkaError !== '') {
            $errorMessage = $apaczkaError;
        }

        return [$allegroServices, $apaczkaServices, $errorMessage];
    }

    /**
     * @return array{0: array<int, array<string, mixed>>, 1: string}
     */
    private function loadAllegroDeliveryServices(): array
    {
        try {
            $oauth = $this->allegroIntegrationRepository->getTokenCredentials();
            if (!is_array($oauth)) {
                return [[], $this->translator->get('settings.integrations.delivery.not_connected')];
            }

            $env = (string) ($oauth['environment'] ?? 'sandbox');
            $accessToken = trim((string) ($oauth['access_token'] ?? ''));
            if ($accessToken === '') {
                return [[], $this->translator->get('settings.integrations.delivery.not_connected')];
            }

            [$response, $fetchError] = $this->fetchAllegroDeliveryResponse($env, $accessToken, $oauth);
            $services = is_array($response) && is_array($response['services'] ?? null) ? $response['services'] : [];
            return [$services, $fetchError];
        } catch (Throwable $exception) {
            return [[], $exception->getMessage()];
        }
    }

    /**
     * @param array<string, mixed> $oauth
     * @return array{0: array<string, mixed>, 1: string}
     */
    private function fetchAllegroDeliveryResponse(string $env, string $accessToken, array $oauth): array
    {
        try {
            $response = $this->allegroApiClient->getDeliveryServices($env, $accessToken);
            return [is_array($response) ? $response : [], ''];
        } catch (RuntimeException $exception) {
            if (trim($exception->getMessage()) !== 'ALLEGRO_HTTP_401') {
                throw $exception;
            }
            $refreshedToken = $this->refreshAllegroAccessToken($oauth);
            if ($refreshedToken === null) {
                return [[], $this->translator->get('settings.integrations.delivery.not_connected')];
            }
            $response = $this->allegroApiClient->getDeliveryServices($env, $refreshedToken);
            return [is_array($response) ? $response : [], ''];
        }
    }

    /**
     * @return array{0: array<int, array<string, mixed>>, 1: string}
     */
    private function loadApaczkaServices(): array
    {
        if ($this->apaczkaRepository === null || $this->apaczkaApiClient === null) {
            return [[], ''];
        }

        try {
            $credentials = $this->apaczkaRepository->getApiCredentials();
            if (!is_array($credentials)) {
                return [[], ''];
            }
            $services = $this->apaczkaApiClient->getServiceStructure(
                (string) ($credentials['app_id'] ?? ''),
                (string) ($credentials['app_secret'] ?? '')
            );
            return [$services, ''];
        } catch (Throwable $exception) {
            return [[], $exception->getMessage()];
        }
    }

    /**
     * @param array<string, mixed> $oauth
     */
    private function refreshAllegroAccessToken(array $oauth): ?string
    {
        try {
            $token = $this->allegroOAuthClient->refreshAccessToken(
                (string) ($oauth['environment'] ?? 'sandbox'),
                (string) ($oauth['client_id'] ?? ''),
                (string) ($oauth['client_secret'] ?? ''),
                (string) ($oauth['refresh_token'] ?? '')
            );

            $expiresIn = max(0, (int) ($token['expires_in'] ?? 0));
            $expiresAt = $expiresIn > 0
                ? (new DateTimeImmutable('now'))->add(new DateInterval('PT' . $expiresIn . 'S'))->format('Y-m-d H:i:s')
                : null;
            $refreshToken = trim((string) ($token['refresh_token'] ?? ''));
            if ($refreshToken === '') {
                $refreshToken = (string) ($oauth['refresh_token'] ?? '');
            }

            $this->allegroIntegrationRepository->saveTokens(
                (string) ($token['access_token'] ?? ''),
                $refreshToken,
                (string) ($token['token_type'] ?? ''),
                (string) ($token['scope'] ?? ''),
                $expiresAt
            );

            $accessToken = trim((string) ($token['access_token'] ?? ''));
            return $accessToken !== '' ? $accessToken : null;
        } catch (Throwable) {
            return null;
        }
    }
}
