<?php
declare(strict_types=1);

namespace App\Modules\Settings;

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

final class AllegroOAuthClient
{
    public const ORDERS_READ_SCOPE = 'allegro:api:orders:read';
    public const ORDERS_WRITE_SCOPE = 'allegro:api:orders:write';
    public const SALE_OFFERS_READ_SCOPE = 'allegro:api:sale:offers:read';
    public const SHIPMENTS_READ_SCOPE = 'allegro:api:shipments:read';
    public const SHIPMENTS_WRITE_SCOPE = 'allegro:api:shipments:write';

    /**
     * @param array<int, string> $scopes
     */
    public function buildAuthorizeUrl(
        string $environment,
        string $clientId,
        string $redirectUri,
        string $state,
        array $scopes
    ): string {
        $scopeValue = trim(implode(' ', array_values(array_filter(
            $scopes,
            static fn (mixed $scope): bool => is_string($scope) && trim($scope) !== ''
        ))));

        $query = http_build_query([
            'response_type' => 'code',
            'client_id' => $clientId,
            'redirect_uri' => $redirectUri,
            'scope' => $scopeValue,
            'state' => $state,
        ]);

        return $this->authorizeBaseUrl($environment) . '?' . $query;
    }

    /**
     * @return array{access_token:string, refresh_token:string, token_type:string, scope:string, expires_in:int}
     */
    public function exchangeAuthorizationCode(
        string $environment,
        string $clientId,
        string $clientSecret,
        string $redirectUri,
        string $authorizationCode
    ): array {
        $payload = $this->requestToken(
            $this->tokenUrl($environment),
            $clientId,
            $clientSecret,
            [
                'grant_type' => 'authorization_code',
                'code' => $authorizationCode,
                'redirect_uri' => $redirectUri,
            ]
        );

        $accessToken = trim((string) ($payload['access_token'] ?? ''));
        $refreshToken = trim((string) ($payload['refresh_token'] ?? ''));
        if ($accessToken === '' || $refreshToken === '') {
            throw new AllegroOAuthException('Allegro nie zwrocilo kompletu tokenow OAuth.');
        }

        return [
            'access_token' => $accessToken,
            'refresh_token' => $refreshToken,
            'token_type' => trim((string) ($payload['token_type'] ?? 'Bearer')),
            'scope' => trim((string) ($payload['scope'] ?? '')),
            'expires_in' => max(0, (int) ($payload['expires_in'] ?? 0)),
        ];
    }

    /**
     * @return array{access_token:string, refresh_token:string, token_type:string, scope:string, expires_in:int}
     */
    public function refreshAccessToken(
        string $environment,
        string $clientId,
        string $clientSecret,
        string $refreshToken
    ): array {
        $payload = $this->requestToken(
            $this->tokenUrl($environment),
            $clientId,
            $clientSecret,
            [
                'grant_type' => 'refresh_token',
                'refresh_token' => $refreshToken,
            ]
        );

        $accessToken = trim((string) ($payload['access_token'] ?? ''));
        if ($accessToken === '') {
            throw new AllegroOAuthException('Allegro nie zwrocilo access_token po odswiezeniu.');
        }

        return [
            'access_token' => $accessToken,
            'refresh_token' => trim((string) ($payload['refresh_token'] ?? '')),
            'token_type' => trim((string) ($payload['token_type'] ?? 'Bearer')),
            'scope' => trim((string) ($payload['scope'] ?? '')),
            'expires_in' => max(0, (int) ($payload['expires_in'] ?? 0)),
        ];
    }

    private function authorizeBaseUrl(string $environment): string
    {
        return $this->normalizeEnvironment($environment) === 'production'
            ? 'https://allegro.pl/auth/oauth/authorize'
            : 'https://allegro.pl.allegrosandbox.pl/auth/oauth/authorize';
    }

    private function tokenUrl(string $environment): string
    {
        return $this->normalizeEnvironment($environment) === 'production'
            ? 'https://allegro.pl/auth/oauth/token'
            : 'https://allegro.pl.allegrosandbox.pl/auth/oauth/token';
    }

    private function normalizeEnvironment(string $environment): string
    {
        return trim(strtolower($environment)) === 'production' ? 'production' : 'sandbox';
    }

    /**
     * @param array<string, string> $formData
     * @return array<string, mixed>
     */
    private function requestToken(
        string $url,
        string $clientId,
        string $clientSecret,
        array $formData
    ): array {
        $ch = curl_init($url);
        if ($ch === false) {
            throw new AllegroOAuthException('Nie udalo sie zainicjowac polaczenia OAuth z Allegro.');
        }

        $sslOpts = [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_TIMEOUT => 20,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_HTTPHEADER => [
                'Accept: application/json',
                'Content-Type: application/x-www-form-urlencoded',
                'Authorization: Basic ' . base64_encode($clientId . ':' . $clientSecret),
            ],
            CURLOPT_POSTFIELDS => http_build_query($formData),
        ];
        $caPath = SslCertificateResolver::resolve();
        if ($caPath !== null) {
            $sslOpts[CURLOPT_CAINFO] = $caPath;
        }
        curl_setopt_array($ch, $sslOpts);

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

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

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

        if ($httpCode < 200 || $httpCode >= 300) {
            $error = trim((string) ($json['error'] ?? 'oauth_error'));
            $description = trim((string) ($json['error_description'] ?? 'Brak szczegolow bledu OAuth.'));
            throw new AllegroOAuthException('OAuth Allegro [' . $error . ']: ' . $description);
        }

        return $json;
    }
}
