<?php
declare(strict_types=1);

namespace App\Core\Http;

use RuntimeException;

/**
 * Klient publicznego API "Biala lista podatnikow VAT" Ministerstwa Finansow.
 * Endpoint: https://wl-api.mf.gov.pl/api/search/nip/{nip}?date=YYYY-MM-DD
 * Bez rejestracji, bez klucza. Limit ~10 req/s per IP.
 */
final class MfWhitelistApiClient
{
    private const BASE_URL = 'https://wl-api.mf.gov.pl';

    public function __construct(private readonly int $timeoutSeconds = 10)
    {
    }

    /**
     * @return array{name: string, tax_no: string, regon: string, street: string, postal_code: string, city: string, country: string, status_vat: string, krs: string, is_jdg: bool, raw: array<string, mixed>}
     */
    public function lookupByNip(string $nip, ?string $date = null): array
    {
        $cleanNip = preg_replace('/[\s\-]/', '', trim($nip)) ?? '';
        if (!preg_match('/^\d{10}$/', $cleanNip)) {
            throw new RuntimeException('NIP musi miec 10 cyfr.');
        }

        $dateParam = $date ?? date('Y-m-d');
        if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateParam)) {
            $dateParam = date('Y-m-d');
        }

        $url = self::BASE_URL . '/api/search/nip/' . rawurlencode($cleanNip)
            . '?date=' . rawurlencode($dateParam);

        [$body, $httpCode, $curlError] = $this->httpGet($url);

        if ($curlError !== null) {
            throw new RuntimeException('Blad polaczenia z MF Biala Lista: ' . $curlError);
        }

        if ($httpCode < 200 || $httpCode >= 300) {
            $msg = $this->resolveErrorMessage($body);
            throw new RuntimeException('MF Biala Lista HTTP ' . $httpCode . ($msg !== '' ? ': ' . $msg : ''));
        }

        $decoded = json_decode($body, true);
        if (!is_array($decoded) || !is_array($decoded['result'] ?? null)) {
            throw new RuntimeException('Niepoprawna odpowiedz JSON z MF Biala Lista.');
        }

        $subject = is_array($decoded['result']['subject'] ?? null) ? $decoded['result']['subject'] : null;
        if ($subject === null) {
            throw new RuntimeException('MF Biala Lista nie zwrocila danych dla NIP ' . $cleanNip . ' (brak podmiotu).');
        }

        $name = trim((string) ($subject['name'] ?? ''));
        if ($name === '') {
            throw new RuntimeException('MF Biala Lista nie zwrocila nazwy dla NIP ' . $cleanNip . '.');
        }

        $address = trim((string) ($subject['workingAddress'] ?? $subject['residenceAddress'] ?? ''));
        ['street' => $street, 'postal_code' => $postalCode, 'city' => $city] = $this->parseAddress($address);

        $krs = trim((string) ($subject['krs'] ?? ''));

        return [
            'name' => $name,
            'tax_no' => trim((string) ($subject['nip'] ?? $cleanNip)),
            'regon' => trim((string) ($subject['regon'] ?? '')),
            'street' => $street,
            'postal_code' => $postalCode,
            'city' => $city,
            'country' => 'PL',
            'status_vat' => trim((string) ($subject['statusVat'] ?? '')),
            'krs' => $krs,
            'is_jdg' => $krs === '',
            'raw' => $subject,
        ];
    }

    /**
     * Parse address from MF Biala Lista format: "ul. Krakowska 1, 00-001 Warszawa".
     *
     * @return array{street: string, postal_code: string, city: string}
     */
    private function parseAddress(string $address): array
    {
        if ($address === '') {
            return ['street' => '', 'postal_code' => '', 'city' => ''];
        }

        $lastComma = strrpos($address, ',');
        if ($lastComma === false) {
            return ['street' => $address, 'postal_code' => '', 'city' => ''];
        }

        $street = trim(substr($address, 0, $lastComma));
        $tail = trim(substr($address, $lastComma + 1));

        if (preg_match('/^(\d{2}-\d{3})\s+(.+)$/u', $tail, $m)) {
            return ['street' => $street, 'postal_code' => $m[1], 'city' => trim($m[2])];
        }

        return ['street' => $street, 'postal_code' => '', 'city' => $tail];
    }

    /**
     * @return array{0: string, 1: int, 2: ?string}
     */
    private function httpGet(string $url): array
    {
        $ch = curl_init($url);
        if ($ch === false) {
            return ['', 0, 'Nie udalo sie zainicjowac cURL.'];
        }

        $opts = [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPGET => true,
            CURLOPT_TIMEOUT => $this->timeoutSeconds,
            CURLOPT_CONNECTTIMEOUT => 5,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_HTTPHEADER => [
                'Accept: application/json',
                'User-Agent: orderPRO/1.0',
            ],
        ];

        $caPath = SslCertificateResolver::resolve();
        if ($caPath !== null) {
            $opts[CURLOPT_CAINFO] = $caPath;
        }

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

        if ($rawBody === false) {
            return ['', $httpCode, $curlError !== '' ? $curlError : 'Brak odpowiedzi z API.'];
        }

        return [(string) $rawBody, $httpCode, null];
    }

    private function resolveErrorMessage(string $body): string
    {
        $trimmed = ltrim($body, "\xEF\xBB\xBF \t\n\r\0\x0B");
        if ($trimmed === '') {
            return '';
        }

        $decoded = json_decode($trimmed, true);
        if (is_array($decoded)) {
            foreach (['message', 'code', 'error'] as $key) {
                if (isset($decoded[$key]) && is_string($decoded[$key]) && trim($decoded[$key]) !== '') {
                    return trim($decoded[$key]);
                }
            }
        }

        $snippet = trim(strip_tags($trimmed));
        return substr($snippet, 0, 200);
    }
}
