<?php
declare(strict_types=1);

namespace App\Modules\Settings;

use App\Core\Exceptions\ApaczkaApiException;
use App\Core\Http\SslCertificateResolver;

final class ApaczkaApiClient
{
    private const API_BASE_URL = 'https://www.apaczka.pl/api/v2';

    /**
     * @return array<int, array<string, mixed>>
     */
    public function getServiceStructure(string $appId, string $appSecret): array
    {
        $response = $this->request('/service_structure/', 'service_structure', $appId, $appSecret, []);
        $services = $response['response']['services'] ?? $response['services'] ?? [];
        return is_array($services) ? $services : [];
    }

    /**
     * @param array<string, mixed> $orderPayload
     * @return array<string, mixed>
     */
    public function sendOrder(string $appId, string $appSecret, array $orderPayload): array
    {
        return $this->request('/order_send/', 'order_send', $appId, $appSecret, [
            'order' => $orderPayload,
        ]);
    }

    /**
     * @return array<string, mixed>
     */
    public function getOrderDetails(string $appId, string $appSecret, int $orderId): array
    {
        $safeOrderId = max(1, $orderId);
        return $this->request('/order/' . $safeOrderId . '/', 'order/' . $safeOrderId, $appId, $appSecret, []);
    }

    /**
     * @return array<string, mixed>
     */
    public function getWaybill(string $appId, string $appSecret, int $orderId): array
    {
        $safeOrderId = max(1, $orderId);
        return $this->request('/waybill/' . $safeOrderId . '/', 'waybill/' . $safeOrderId, $appId, $appSecret, []);
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public function getPoints(string $appId, string $appSecret, string $type = 'parcel_locker'): array
    {
        $safeType = trim($type) !== '' ? trim($type) : 'parcel_locker';
        $route = 'points/' . $safeType;
        $response = $this->request('/' . $route . '/', $route, $appId, $appSecret, []);
        $points = $response['response']['points'] ?? $response['points'] ?? [];
        return is_array($points) ? $points : [];
    }

    /**
     * @param array<string, mixed> $request
     * @return array<string, mixed>
     */
    private function request(string $endpointPath, string $route, string $appId, string $appSecret, array $request): array
    {
        $normalizedRoute = trim($route, " \t\n\r\0\x0B/");
        if ($normalizedRoute === '') {
            throw new ApaczkaApiException('Nie podano endpointu API Apaczka.');
        }
        $routeWithTrailingSlash = $normalizedRoute . '/';

        $expires = (string) (time() + 60);
        $requestJson = json_encode($request);
        if (!is_string($requestJson)) {
            throw new ApaczkaApiException('Nie mozna zakodowac payloadu Apaczka.');
        }

        $basePayload = [
            'app_id' => trim($appId),
            'request' => $requestJson,
            'expires' => $expires,
        ];
        $signatureVariants = $this->buildSignatureVariants(
            trim((string) $basePayload['app_id']),
            $endpointPath,
            $routeWithTrailingSlash,
            $requestJson,
            $expires,
            $appSecret
        );

        $lastSignatureError = null;
        foreach ($signatureVariants as $signature) {
            $payload = $basePayload;
            $payload['signature'] = $signature;
            [$decoded, $httpCode] = $this->executeRequest($endpointPath, $payload);

            $status = (int) ($decoded['status'] ?? 0);
            $message = $this->resolveErrorMessage($decoded);
            $isSignatureMismatch = $status === 400 && stripos($message, 'signature') !== false;
            if ($isSignatureMismatch) {
                $lastSignatureError = [$decoded, $status, $message];
                continue;
            }

            if ($httpCode < 200 || $httpCode >= 300) {
                throw new ApaczkaApiException('API Apaczka HTTP ' . $httpCode . ': ' . $message);
            }
            if ($status !== 200) {
                $responsePreview = substr(json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '', 0, 1000);
                throw new ApaczkaApiException(
                    'Blad API Apaczka (status ' . $status . '): ' . $message . '. Odpowiedz: ' . $responsePreview
                );
            }

            return $decoded;
        }

        if ($lastSignatureError !== null) {
            [$decoded, $status, $message] = $lastSignatureError;
            $responsePreview = substr(json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '', 0, 1000);
            throw new ApaczkaApiException(
                'Blad API Apaczka (status ' . $status . '): ' . $message . '. Odpowiedz: ' . $responsePreview
            );
        }

        throw new ApaczkaApiException('Blad API Apaczka.');
    }

    private function buildSignature(
        string $appId,
        string $route,
        string $requestJson,
        string $expires,
        string $appSecret
    ): string
    {
        return hash_hmac('sha256', $appId . ':' . $route . ':' . $requestJson . ':' . $expires, trim($appSecret));
    }

    /**
     * @return array<int, string>
     */
    private function buildSignatureVariants(
        string $appId,
        string $endpointPath,
        string $route,
        string $requestJson,
        string $expires,
        string $appSecret
    ): array {
        $endpointTrimmed = trim($endpointPath, " \t\n\r\0\x0B/");
        $endpointWithSlashes = '/' . $endpointTrimmed . '/';
        $endpointWithTrailingSlash = $endpointTrimmed . '/';

        $variants = [];
        $variants[] = hash_hmac('sha256', $appId . ':' . $endpointWithTrailingSlash . ':' . $requestJson . ':' . $expires, trim($appSecret));
        $variants[] = hash_hmac('sha256', $appId . ':' . $route . ':' . $requestJson . ':' . $expires, trim($appSecret));
        $variants[] = hash_hmac('sha256', $appId . ':' . $endpointWithSlashes . ':' . $requestJson . ':' . $expires, trim($appSecret));
        $variants[] = hash_hmac('sha256', $appId . ':' . $endpointTrimmed . ':' . $requestJson . ':' . $expires, trim($appSecret));
        $variants[] = hash('sha256', $appId . ':' . $endpointWithTrailingSlash . ':' . $requestJson . ':' . $expires . ':' . trim($appSecret));
        $variants[] = hash('sha256', $appId . ':' . $route . ':' . $requestJson . ':' . $expires . ':' . trim($appSecret));
        $variants[] = hash('sha256', $appId . ':' . $endpointWithSlashes . ':' . $requestJson . ':' . $expires . ':' . trim($appSecret));

        return array_values(array_unique($variants));
    }

    /**
     * @param array<string, string> $payload
     * @return array{0: array<string, mixed>, 1: int}
     */
    private function executeRequest(string $endpointPath, array $payload): array
    {
        $url = rtrim(self::API_BASE_URL, '/') . '/' . ltrim($endpointPath, '/');
        $ch = curl_init($url);
        if ($ch === false) {
            throw new ApaczkaApiException('Nie udalo sie zainicjowac polaczenia z API Apaczka.');
        }

        $sslOpts = [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => http_build_query($payload),
            CURLOPT_TIMEOUT => 30,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_HTTPHEADER => [
                'Accept: application/json',
                'Content-Type: application/x-www-form-urlencoded',
                'User-Agent: orderPRO/1.0',
            ],
        ];
        $caPath = SslCertificateResolver::resolve();
        if ($caPath !== null) {
            $sslOpts[CURLOPT_CAINFO] = $caPath;
        }
        curl_setopt_array($ch, $sslOpts);

        $rawBody = curl_exec($ch);
        $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlError = curl_error($ch);
        unset($ch);

        if ($rawBody === false) {
            throw new ApaczkaApiException('Blad polaczenia z API Apaczka: ' . $curlError);
        }

        $normalizedBody = ltrim((string) $rawBody, "\xEF\xBB\xBF \t\n\r\0\x0B");
        $decoded = json_decode($normalizedBody, true);
        if (!is_array($decoded)) {
            $snippet = substr(trim(strip_tags($normalizedBody)), 0, 180);
            throw new ApaczkaApiException(
                'Nieprawidlowa odpowiedz API Apaczka (brak JSON, HTTP ' . $httpCode . '). Fragment: ' . $snippet
            );
        }

        return [$decoded, $httpCode];
    }

    /**
     * @param array<string, mixed> $decoded
     */
    private function resolveErrorMessage(array $decoded): string
    {
        $topMessage = trim((string) ($decoded['message'] ?? ''));
        if ($topMessage !== '') {
            return $topMessage;
        }

        $responseMessage = trim((string) ($decoded['response']['message'] ?? ''));
        if ($responseMessage !== '') {
            return $responseMessage;
        }

        $errorMessage = trim((string) ($decoded['error']['message'] ?? ''));
        if ($errorMessage !== '') {
            return $errorMessage;
        }

        return 'Blad API Apaczka.';
    }
}
