<?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\Constants\IntegrationSources;
use App\Core\Constants\RedirectPaths;
use App\Core\Security\Csrf;
use App\Core\Support\Flash;
use DateInterval;
use DateTimeImmutable;
use RuntimeException;
use Throwable;

final class AllegroDeliveryMappingController
{
    public function __construct(
        private readonly Translator $translator,
        private readonly AllegroIntegrationRepository $repository,
        private readonly AllegroOAuthClient $oauthClient,
        private readonly ?AllegroApiClient $apiClient = null,
        private readonly ?CarrierDeliveryMethodMappingRepository $deliveryMappings = null,
        private readonly ?ApaczkaIntegrationRepository $apaczkaRepository = null,
        private readonly ?ApaczkaApiClient $apaczkaApiClient = null
    ) {
    }

    public function getDeliveryMappingsRepository(): ?CarrierDeliveryMethodMappingRepository
    {
        return $this->deliveryMappings;
    }

    public function saveDeliveryMappings(Request $request): Response
    {
        $csrfError = $this->validateCsrf((string) $request->input('_token', ''));
        if ($csrfError !== null) {
            return $csrfError;
        }

        if ($this->deliveryMappings === null) {
            Flash::set('settings_error', 'Delivery mappings not configured.');
            return Response::redirect(RedirectPaths::ALLEGRO_DELIVERY_TAB);
        }

        $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 $idx => $orderMethod) {
            $orderMethod = trim((string) $orderMethod);
            $carrier = trim((string) ($carriers[$idx] ?? 'allegro'));
            $provider = $carrier === 'apaczka' ? 'apaczka' : 'allegro_wza';
            $providerServiceId = $provider === 'apaczka'
                ? trim((string) ($apaczkaMethodIds[$idx] ?? ''))
                : trim((string) ($allegroMethodIds[$idx] ?? ''));
            if ($orderMethod === '' || $providerServiceId === '') {
                continue;
            }
            $mappings[] = [
                'order_delivery_method' => $orderMethod,
                'provider' => $provider,
                'provider_service_id' => $providerServiceId,
                'provider_account_id' => $provider === 'allegro_wza' ? trim((string) ($credentialsIds[$idx] ?? '')) : '',
                'provider_carrier_id' => $provider === 'allegro_wza' ? trim((string) ($carrierIds[$idx] ?? '')) : '',
                'provider_service_name' => trim((string) ($serviceNames[$idx] ?? '')),
            ];
        }

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

        return Response::redirect(RedirectPaths::ALLEGRO_DELIVERY_TAB);
    }

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

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

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

    /**
     * @param array<string, mixed> $settings
     * @return array{0: array<int, array<string, mixed>>, 1: string}
     */
    private function loadAllegroDeliveryServices(array $settings): array
    {
        if ($this->apiClient === null) {
            return [[], ''];
        }

        $isConnected = (bool) ($settings['is_connected'] ?? false);
        if (!$isConnected) {
            return [[], $this->translator->get('settings.allegro.delivery.not_connected')];
        }

        try {
            $oauth = $this->repository->getTokenCredentials();
            if ($oauth === null) {
                return [[], $this->translator->get('settings.allegro.delivery.not_connected')];
            }

            $env = (string) ($oauth['environment'] ?? 'sandbox');
            $accessToken = trim((string) ($oauth['access_token'] ?? ''));
            if ($accessToken === '') {
                return [[], $this->translator->get('settings.allegro.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 $e) {
            return [[], $e->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->apiClient->getDeliveryServices($env, $accessToken);
            return [is_array($response) ? $response : [], ''];
        } catch (RuntimeException $ex) {
            if (trim($ex->getMessage()) !== 'ALLEGRO_HTTP_401') {
                throw $ex;
            }
            $refreshed = $this->refreshOAuthToken($oauth);
            if ($refreshed === null) {
                return [[], $this->translator->get('settings.allegro.delivery.not_connected')];
            }
            $response = $this->apiClient->getDeliveryServices($env, $refreshed);
            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 refreshOAuthToken(array $oauth): ?string
    {
        try {
            $token = $this->oauthClient->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->repository->saveTokens(
                (string) ($token['access_token'] ?? ''),
                $refreshToken,
                (string) ($token['token_type'] ?? ''),
                (string) ($token['scope'] ?? ''),
                $expiresAt
            );
            return trim((string) ($token['access_token'] ?? '')) ?: null;
        } catch (Throwable) {
            return null;
        }
    }

    private function validateCsrf(string $token): ?Response
    {
        if (Csrf::validate($token)) {
            return null;
        }

        Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired'));
        return Response::redirect(RedirectPaths::ALLEGRO_INTEGRATION);
    }
}
