<?php
declare(strict_types=1);

namespace App\Modules\Settings;

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

final class AllegroApiClient
{
    /**
     * @return array<string, mixed>
     */
    public function getCheckoutForm(string $environment, string $accessToken, string $checkoutFormId): array
    {
        $safeId = rawurlencode(trim($checkoutFormId));
        if ($safeId === '') {
            throw new AllegroApiException('Brak ID zamowienia Allegro do pobrania.');
        }

        $url = rtrim($this->apiBaseUrl($environment), '/') . '/order/checkout-forms/' . $safeId;
        return $this->requestJson($url, $accessToken);
    }

    /**
     * @return array<string, mixed>
     */
    public function listCheckoutForms(string $environment, string $accessToken, int $limit, int $offset): array
    {
        $safeLimit = max(1, min(100, $limit));
        $safeOffset = max(0, $offset);
        $query = http_build_query([
            'limit' => $safeLimit,
            'offset' => $safeOffset,
            'sort' => '-updatedAt',
        ]);

        $url = rtrim($this->apiBaseUrl($environment), '/') . '/order/checkout-forms?' . $query;
        return $this->requestJson($url, $accessToken);
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public function getCheckoutFormShipments(string $environment, string $accessToken, string $checkoutFormId): array
    {
        $safeId = rawurlencode(trim($checkoutFormId));
        if ($safeId === '') {
            throw new AllegroApiException('Brak ID zamowienia Allegro do pobrania przesylek.');
        }

        $url = rtrim($this->apiBaseUrl($environment), '/') . '/order/checkout-forms/' . $safeId . '/shipments';
        $response = $this->requestJson($url, $accessToken);

        return is_array($response['shipments'] ?? null) ? $response['shipments'] : [];
    }

    /**
     * @return array<string, mixed>
     */
    public function getProductOffer(string $environment, string $accessToken, string $offerId): array
    {
        $safeId = rawurlencode(trim($offerId));
        if ($safeId === '') {
            throw new AllegroApiException('Brak ID oferty Allegro do pobrania.');
        }

        $url = rtrim($this->apiBaseUrl($environment), '/') . '/sale/product-offers/' . $safeId;
        return $this->requestJson($url, $accessToken);
    }

    /**
     * @return array<string, mixed>
     */
    public function getDeliveryServices(string $environment, string $accessToken): array
    {
        $url = rtrim($this->apiBaseUrl($environment), '/') . '/shipment-management/delivery-services';
        return $this->requestJson($url, $accessToken);
    }

    /**
     * @param array<string, mixed> $payload
     * @return array<string, mixed>
     */
    public function createShipment(string $environment, string $accessToken, array $payload): array
    {
        $url = rtrim($this->apiBaseUrl($environment), '/') . '/shipment-management/shipments/create-commands';
        return $this->postJson($url, $accessToken, $payload);
    }

    /**
     * @return array<string, mixed>
     */
    public function getShipmentCreationStatus(string $environment, string $accessToken, string $commandId): array
    {
        $safeId = rawurlencode(trim($commandId));
        $url = rtrim($this->apiBaseUrl($environment), '/') . '/shipment-management/shipments/create-commands/' . $safeId;
        return $this->requestJson($url, $accessToken);
    }

    /**
     * @return array<string, mixed>
     */
    public function getShipmentDetails(string $environment, string $accessToken, string $shipmentId): array
    {
        $safeId = rawurlencode(trim($shipmentId));
        $url = rtrim($this->apiBaseUrl($environment), '/') . '/shipment-management/shipments/' . $safeId;
        return $this->requestJson($url, $accessToken);
    }

    /**
     * @param array<string, string> $shipmentIds
     */
    public function getShipmentLabel(string $environment, string $accessToken, array $shipmentIds, string $pageSize = 'A6'): string
    {
        $url = rtrim($this->apiBaseUrl($environment), '/') . '/shipment-management/label';
        $payload = [
            'shipmentIds' => array_values($shipmentIds),
            'pageSize' => $pageSize,
        ];
        return $this->postBinary($url, $accessToken, $payload);
    }

    /**
     * @param array<string, mixed> $lineItems
     * @return array<string, mixed>
     */
    public function addShipmentToOrder(
        string $environment,
        string $accessToken,
        string $checkoutFormId,
        string $waybill,
        string $carrierId,
        string $carrierName,
        array $lineItems = []
    ): array {
        $safeId = rawurlencode(trim($checkoutFormId));
        $url = rtrim($this->apiBaseUrl($environment), '/') . '/order/checkout-forms/' . $safeId . '/shipments';
        $body = [
            'waybill' => $waybill,
            'carrierId' => $carrierId,
            'carrierName' => $carrierName,
        ];
        if ($lineItems !== []) {
            $body['lineItems'] = $lineItems;
        }
        return $this->postJson($url, $accessToken, $body);
    }

    /**
     * @return array<string, mixed>
     */
    public function updateCheckoutFormFulfillment(
        string $environment,
        string $accessToken,
        string $checkoutFormId,
        string $status
    ): array {
        $safeId = rawurlencode(trim($checkoutFormId));
        if ($safeId === '') {
            throw new AllegroApiException('Brak ID zamowienia Allegro do aktualizacji statusu.');
        }

        $normalizedStatus = strtoupper(trim($status));
        if ($normalizedStatus === '') {
            throw new AllegroApiException('Brak statusu Allegro do aktualizacji.');
        }

        $url = rtrim($this->apiBaseUrl($environment), '/') . '/order/checkout-forms/' . $safeId . '/fulfillment';
        return $this->putJson($url, $accessToken, [
            'status' => $normalizedStatus,
        ]);
    }

    /**
     * @param array<int, mixed> $opts
     * @return array<int, mixed>
     */
    private function withSslOptions(array $opts): array
    {
        $opts[CURLOPT_SSL_VERIFYPEER] = true;
        $opts[CURLOPT_SSL_VERIFYHOST] = 2;
        $caPath = SslCertificateResolver::resolve();
        if ($caPath !== null) {
            $opts[CURLOPT_CAINFO] = $caPath;
        }
        return $opts;
    }

    private function apiBaseUrl(string $environment): string
    {
        return trim(strtolower($environment)) === 'production'
            ? 'https://api.allegro.pl'
            : 'https://api.allegro.pl.allegrosandbox.pl';
    }

    /**
     * @param array<string, mixed> $body
     * @return array<string, mixed>
     */
    private function postJson(string $url, string $accessToken, array $body): array
    {
        $jsonBody = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);

        $ch = curl_init($url);
        if ($ch === false) {
            throw new AllegroApiException('Nie udalo sie zainicjowac polaczenia z API Allegro.');
        }

        curl_setopt_array($ch, $this->withSslOptions([
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $jsonBody,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_HTTPHEADER => [
                'Accept: application/vnd.allegro.public.v1+json',
                'Content-Type: application/vnd.allegro.public.v1+json',
                'Authorization: Bearer ' . $accessToken,
            ],
        ]));

        $responseBody = curl_exec($ch);
        $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlError = curl_error($ch);
        $ch = null;

        if ($responseBody === false) {
            throw new AllegroApiException('Blad polaczenia z API Allegro: ' . $curlError);
        }

        $json = json_decode((string) $responseBody, true);
        if (!is_array($json)) {
            throw new AllegroApiException('Nieprawidlowy JSON odpowiedzi API Allegro.');
        }

        if ($httpCode === 401) {
            throw new AllegroApiException('ALLEGRO_HTTP_401');
        }

        if ($httpCode < 200 || $httpCode >= 300) {
            $message = trim((string) ($json['message'] ?? ''));
            $errors = is_array($json['errors'] ?? null) ? $json['errors'] : [];
            if ($message === '' && $errors !== []) {
                $parts = [];
                foreach ($errors as $err) {
                    if (is_array($err)) {
                        $parts[] = trim((string) ($err['message'] ?? ($err['userMessage'] ?? '')));
                    }
                }
                $message = implode('; ', array_filter($parts));
            }
            if ($message === '') {
                $message = 'Blad API Allegro.';
            }
            throw new AllegroApiException('API Allegro HTTP ' . $httpCode . ': ' . $message);
        }

        return $json;
    }

    /**
     * @param array<string, mixed> $body
     * @return array<string, mixed>
     */
    private function putJson(string $url, string $accessToken, array $body): array
    {
        $jsonBody = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);

        $ch = curl_init($url);
        if ($ch === false) {
            throw new AllegroApiException('Nie udalo sie zainicjowac polaczenia z API Allegro.');
        }

        curl_setopt_array($ch, $this->withSslOptions([
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CUSTOMREQUEST => 'PUT',
            CURLOPT_POSTFIELDS => $jsonBody,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_HTTPHEADER => [
                'Accept: application/vnd.allegro.public.v1+json',
                'Content-Type: application/vnd.allegro.public.v1+json',
                'Authorization: Bearer ' . $accessToken,
            ],
        ]));

        $responseBody = curl_exec($ch);
        $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlError = curl_error($ch);
        $ch = null;

        if ($responseBody === false) {
            throw new AllegroApiException('Blad polaczenia z API Allegro: ' . $curlError);
        }

        $json = json_decode((string) $responseBody, true);
        if (!is_array($json)) {
            throw new AllegroApiException('Nieprawidlowy JSON odpowiedzi API Allegro.');
        }

        if ($httpCode === 401) {
            throw new AllegroApiException('ALLEGRO_HTTP_401');
        }

        if ($httpCode < 200 || $httpCode >= 300) {
            $message = trim((string) ($json['message'] ?? ''));
            $errors = is_array($json['errors'] ?? null) ? $json['errors'] : [];
            if ($message === '' && $errors !== []) {
                $parts = [];
                foreach ($errors as $err) {
                    if (is_array($err)) {
                        $parts[] = trim((string) ($err['message'] ?? ($err['userMessage'] ?? '')));
                    }
                }
                $message = implode('; ', array_filter($parts));
            }
            if ($message === '') {
                $message = 'Blad API Allegro.';
            }
            throw new AllegroApiException('API Allegro HTTP ' . $httpCode . ': ' . $message);
        }

        return $json;
    }

    /**
     * @param array<string, mixed> $body
     */
    private function postBinary(string $url, string $accessToken, array $body): string
    {
        $jsonBody = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);

        $ch = curl_init($url);
        if ($ch === false) {
            throw new AllegroApiException('Nie udalo sie zainicjowac polaczenia z API Allegro.');
        }

        curl_setopt_array($ch, $this->withSslOptions([
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $jsonBody,
            CURLOPT_TIMEOUT => 60,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_HTTPHEADER => [
                'Accept: application/octet-stream',
                'Content-Type: application/vnd.allegro.public.v1+json',
                'Authorization: Bearer ' . $accessToken,
            ],
        ]));

        $responseBody = curl_exec($ch);
        $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlError = curl_error($ch);
        $ch = null;

        if ($responseBody === false) {
            throw new AllegroApiException('Blad polaczenia z API Allegro: ' . $curlError);
        }

        if ($httpCode === 401) {
            throw new AllegroApiException('ALLEGRO_HTTP_401');
        }

        if ($httpCode === 204) {
            throw new AllegroApiException('Brak etykiety dla podanej przesylki.');
        }

        if ($httpCode < 200 || $httpCode >= 300) {
            $json = json_decode((string) $responseBody, true);
            $message = is_array($json) ? trim((string) ($json['message'] ?? 'Blad API Allegro.')) : 'Blad API Allegro.';
            throw new AllegroApiException('API Allegro HTTP ' . $httpCode . ': ' . $message);
        }

        return (string) $responseBody;
    }

    /**
     * @return array<string, mixed>
     */
    private function requestJson(string $url, string $accessToken): array
    {
        $ch = curl_init($url);
        if ($ch === false) {
            throw new AllegroApiException('Nie udalo sie zainicjowac polaczenia z API Allegro.');
        }

        curl_setopt_array($ch, $this->withSslOptions([
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPGET => true,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_HTTPHEADER => [
                'Accept: application/vnd.allegro.public.v1+json',
                'Authorization: Bearer ' . $accessToken,
            ],
        ]));

        $responseBody = curl_exec($ch);
        $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlError = curl_error($ch);
        $ch = null;

        if ($responseBody === false) {
            throw new AllegroApiException('Blad polaczenia z API Allegro: ' . $curlError);
        }

        $json = json_decode((string) $responseBody, true);
        if (!is_array($json)) {
            throw new AllegroApiException('Nieprawidlowy JSON odpowiedzi API Allegro.');
        }

        if ($httpCode === 401) {
            throw new AllegroApiException('ALLEGRO_HTTP_401');
        }

        if ($httpCode < 200 || $httpCode >= 300) {
            $message = trim((string) ($json['message'] ?? 'Blad API Allegro.'));
            throw new AllegroApiException('API Allegro HTTP ' . $httpCode . ': ' . $message);
        }

        return $json;
    }
}
