<?php

namespace Daniv04\HaciendaPackage\Providers\Hacienda;

use Daniv04\HaciendaPackage\Providers\Contracts\ProviderInterface;
use DateTime;
use Daniv04\HaciendaPackage\Providers\Hacienda\Transformer\HaciendaTransformer;
use Daniv04\HaciendaPackage\Traits\DateParserTrait;
use Daniv04\HaciendaPackage\Traits\GenerateSecurityCode;
use Illuminate\Support\Facades\Http;
use Daniv04\HaciendaPackage\Traits\UniqueKeyTrait;
use Daniv04\HaciendaPackage\Services\XmlGenerator\XmlGeneratorService;
use Daniv04\HaciendaPackage\Services\Hacienda\XmlSignerService;
use Daniv04\HaciendaPackage\Services\Hacienda\Idp\HaciendaIdpService;
use Daniv04\HaciendaPackage\Services\Hacienda\Idp\HaciendaIdpException;
use Illuminate\Http\Client\Response;
use Daniv04\HaciendaPackage\Providers\Hacienda\Data\HaciendaDto;
use Daniv04\HaciendaPackage\Providers\Hacienda\Data\IdentificationData;


class HaciendaProvider implements ProviderInterface
{
    use DateParserTrait;
    use GenerateSecurityCode;
    use UniqueKeyTrait;

    public function __construct(
        private XmlGeneratorService $xmlGenerator,
        private XmlSignerService $xmlSigner,
        private HaciendaIdpService $idpService,
    ) {}

    public function sendDocument(array $payload, string $consecutiveNumber): array
    {
        //Vars for consecutive key
        $docType       = $payload['Clave']['TipoComprobante'];
        $establishment = $payload['Clave']['Sucursal'];
        $terminal      = $payload['Clave']['Terminal'];

        //Vars for unique key
        $date         = $payload['Encabezado']['FechaEmision'] ?? (new DateTime())->format('Y-m-d\TH:i:sP');
        $securityCode = $this->generateSecurityCode($consecutiveNumber . $date);
        $dateParsed   = $this->parseDateToArray($date);
        $emisorID     = $payload['Emisor']['Identificacion']['Numero'];
        $idType       = $payload['Emisor']['Identificacion']['Tipo'];
        $situation    = $payload['Encabezado']['SituacionComprobante'];

        $consecutiveKey = $this->generateConsecutiveKey($docType, $consecutiveNumber, $establishment, $terminal);
        $uniqueKey      = $this->generateUniqueKey($dateParsed, $emisorID, $idType, $consecutiveKey, $situation, $securityCode);
        $dealer         = config('hacienda.provider_id');

        $transformedData = HaciendaTransformer::transformDocument($payload, $consecutiveKey, $uniqueKey, $dealer);

        if(config('hacienda.hacienda_idp.environment') === 'prod') {
            $baseUrl   = config('hacienda.api_base_url_prod');
        } else {
            $baseUrl   = config('hacienda.api_base_url_stag');
        }
        $methodUrl = config('hacienda.api_url_recepcion');
        $url       = $baseUrl . $methodUrl;

        try {
            // Generate Hacienda-compliant XML
            $xmlString = $this->xmlGenerator->generateXml($transformedData);

            // Sign XML with XAdES-EPES
            $signedXml = $this->xmlSigner->sign($xmlString);

            // Base64-encode the signed XML
            $comprobanteXml = base64_encode($signedXml);

            // Build the DTO for Hacienda's reception API
            $callbackUrl = route('hacienda.webhook');
            
            $emisorIdentification = new IdentificationData($idType, $emisorID);
            $dto = new HaciendaDto($uniqueKey, $date, $emisorIdentification, $comprobanteXml,$callbackUrl);

            if (isset($payload['Receptor']['Identificacion'])) {
                $receptorId = $payload['Receptor']['Identificacion'];
                $dto->setReceptor(new IdentificationData($receptorId['Tipo'], $receptorId['Numero']));
            }

            // Authenticate and POST to Hacienda
            $token = $this->idpService->getAccessToken();
            /** @var Response $response */
            $response = Http::withToken($token)->post($url, $dto->toArray());

            if (in_array($response->status(), [201, 202])) {
                return [
                    'success' => true,
                    'message' => 'Comprobante recibido por Hacienda',
                    'data'    => [
                        'location' => $response->header('Location'),
                        'clave' => $uniqueKey,
                        'fecha' => $date,
                        'data' => $comprobanteXml],
                ];
            }


            if ($response->status() === 400) {
                return [
                    'success'   => false,
                    'retriable' => false,
                    'message'   => $response->header('X-Error-Cause') ?? 'Validation error',
                    'errors'    => [$response->header('X-Error-Cause') ?? 'Validation error'],
                ];
            }

            return [
                'success'   => false,
                'retriable' => true,
                'message'   => 'Unexpected HTTP status: ' . $response->status(),
                'errors'    => [$response->header('X-Error-Cause') ?? 'Unexpected error'],
            ];

        } catch (HaciendaIdpException $e) {
            return [
                'success'   => false,
                'retriable' => true,
                'message'   => 'Hacienda authentication error: ' . $e->getMessage(),
                'errors'    => [],
            ];
        } catch (\Exception $e) {
            return [
                'success'   => false,
                'retriable' => true,
                'message'   => 'Error sending document: ' . $e->getMessage(),
                'errors'    => [$e->getTraceAsString()],
            ];
        }
    }

    public function getDocumentStatus(string $key): array
    {
        $token    = $this->idpService->getAccessToken();
        $baseUrl  = config('hacienda.hacienda_idp.environment') === 'prod'
            ? config('hacienda.api_base_url_prod')
            : config('hacienda.api_base_url_stag');
        $endpoint = rtrim($baseUrl, '/').'/'.config('hacienda.api_url_recepcion').'/'.$key;

        try {
            $response = Http::timeout(15)->withToken($token)->get($endpoint);
        } catch (\Illuminate\Http\Client\ConnectionException $e) {
            throw new \Daniv04\HaciendaPackage\Exceptions\HaciendaException(
                'Timeout o error de red al consultar Hacienda.',
                0,
                $e
            );
        }

        if ($response->failed()) {
            throw new \Daniv04\HaciendaPackage\Exceptions\HaciendaException(
                "Error al consultar estado en Hacienda: {$response->body()}",
                $response->status()
            );
        }

        return $response->json() ?? [];
    }

    public function getName(): string
    {
        return 'hacienda';
    }
}
