<?php

namespace Freefactu_Vendor\josemmo\Verifactu\Services;

use DateTimeImmutable;
use Freefactu_Vendor\GuzzleHttp\Client;
use Freefactu_Vendor\GuzzleHttp\Promise\PromiseInterface;
use InvalidArgumentException;
use Freefactu_Vendor\josemmo\Verifactu\Exceptions\AeatException;
use Freefactu_Vendor\josemmo\Verifactu\Models\ComputerSystem;
use Freefactu_Vendor\josemmo\Verifactu\Models\Records\CancellationRecord;
use Freefactu_Vendor\josemmo\Verifactu\Models\Records\FiscalIdentifier;
use Freefactu_Vendor\josemmo\Verifactu\Models\Records\Record;
use Freefactu_Vendor\josemmo\Verifactu\Models\Records\RegistrationRecord;
use Freefactu_Vendor\josemmo\Verifactu\Models\Responses\AeatResponse;
use Freefactu_Vendor\Psr\Http\Client\ClientExceptionInterface;
use Freefactu_Vendor\Psr\Http\Message\ResponseInterface;
use SensitiveParameter;
use Freefactu_Vendor\UXML\UXML;
/**
 * Class to communicate with the AEAT web service endpoint for VERI*FACTU
 */
class AeatClient
{
    /** SOAP envelope XML namespace */
    public const NS_SOAPENV = 'http://schemas.xmlsoap.org/soap/envelope/';
    /** Client XML namespace */
    public const NS_AEAT = 'https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd';
    private readonly ComputerSystem $system;
    private readonly FiscalIdentifier $taxpayer;
    private readonly Client $client;
    private ?string $certificatePath = null;
    private ?string $certificatePassword = null;
    private ?FiscalIdentifier $representative = null;
    private ?DateTimeImmutable $voluntaryRemissionEndDate = null;
    private bool $isVoluntaryRemissionAffectedByIncident = \false;
    private ?string $requirementReference = null;
    private bool $isLastRequirementSubmission = \false;
    private bool $isProduction = \true;
    private bool $isEntitySeal = \false;
    /**
     * Class constructor
     *
     * @param ComputerSystem   $system     Computer system details
     * @param FiscalIdentifier $taxpayer   Taxpayer details (party that issues the invoices)
     * @param Client|null      $httpClient Custom HTTP client, leave empty to create a new one
     */
    public function __construct(ComputerSystem $system, FiscalIdentifier $taxpayer, ?Client $httpClient = null)
    {
        $this->system = $system;
        $this->taxpayer = $taxpayer;
        $this->client = $httpClient ?? new Client();
    }
    /**
     * Set certificate
     *
     * NOTE: The certificate path must have the ".p12" extension to be recognized as a PFX bundle.
     *
     * @param string      $certificatePath     Path to encrypted PEM certificate or PKCS#12 (PFX) bundle
     * @param string|null $certificatePassword Certificate password or `null` for none
     *
     * @return $this This instance
     */
    public function setCertificate(#[SensitiveParameter] string $certificatePath, #[SensitiveParameter] ?string $certificatePassword = null): static
    {
        $this->certificatePath = $certificatePath;
        $this->certificatePassword = $certificatePassword;
        return $this;
    }
    /**
     * Set representative
     *
     * NOTE: Requires the represented fiscal entity to fill the "GENERALLEY58" form at AEAT.
     *
     * @param FiscalIdentifier|null $representative Representative details (party that sends the invoices)
     *
     * @return $this This instance
     */
    public function setRepresentative(?FiscalIdentifier $representative): static
    {
        $this->representative = $representative;
        return $this;
    }
    /**
     * Set end date of voluntary remission
     *
     * @param DateTimeImmutable|null $endDate              End date (time part will be ignored) or `null` to clear
     * @param bool                   $isAffectedByIncident Whether voluntary remission was at some point affected by a technical incident
     *
     * @return $this This instance
     */
    public function setVoluntaryRemissionEndDate(?DateTimeImmutable $endDate, bool $isAffectedByIncident = \false): static
    {
        $this->voluntaryRemissionEndDate = $endDate;
        $this->isVoluntaryRemissionAffectedByIncident = $isAffectedByIncident;
        return $this;
    }
    /**
     * Set requirement reference
     *
     * Mandatory in case a of a non-voluntary remission upon request by the AEAT ("remisión por requerimiento").
     * Otherwise must be unset.
     *
     * @param string|null $requirementReference        Requirement reference or `null` to clear
     * @param boolean     $isLastRequirementSubmission Whether there are no more records to submit after this remission
     *
     * @return $this This instance
     */
    public function setRequirementReference(?string $requirementReference, bool $isLastRequirementSubmission = \false): static
    {
        $this->requirementReference = $requirementReference;
        $this->isLastRequirementSubmission = $isLastRequirementSubmission;
        return $this;
    }
    /**
     * Set production environment
     *
     * @param bool $production Pass `true` for production, `false` for testing
     *
     * @return $this This instance
     */
    public function setProduction(bool $production): static
    {
        $this->isProduction = $production;
        return $this;
    }
    /**
     * Set entity seal
     *
     * @param bool $entitySeal Pass `true` for entity seal certificate, `false` for regular certificate
     *
     * @return $this This instance
     */
    public function setEntitySeal(bool $entitySeal): static
    {
        $this->isEntitySeal = $entitySeal;
        return $this;
    }
    /**
     * Send invoicing records
     *
     * @param (RegistrationRecord|CancellationRecord)[] $records Invoicing records
     *
     * @return PromiseInterface<AeatResponse> Response from service
     *
     * @throws AeatException            if AEAT server returned an error
     * @throws ClientExceptionInterface if request sending failed
     */
    public function send(array $records): PromiseInterface
    {
        /** @phpstan-ignore generics.notGeneric */
        // Build initial request
        $xml = UXML::newInstance('soapenv:Envelope', null, ['xmlns:soapenv' => self::NS_SOAPENV, 'xmlns:sum' => self::NS_AEAT, 'xmlns:sum1' => Record::NS]);
        $xml->add('soapenv:Header');
        $baseElement = $xml->add('soapenv:Body')->add('sum:RegFactuSistemaFacturacion');
        // Add header
        $cabeceraElement = $baseElement->add('sum:Cabecera');
        $obligadoEmisionElement = $cabeceraElement->add('sum1:ObligadoEmision');
        $obligadoEmisionElement->add('sum1:NombreRazon', $this->taxpayer->name);
        $obligadoEmisionElement->add('sum1:NIF', $this->taxpayer->nif);
        if ($this->representative !== null) {
            $representanteElement = $cabeceraElement->add('sum1:Representante');
            $representanteElement->add('sum1:NombreRazon', $this->representative->name);
            $representanteElement->add('sum1:NIF', $this->representative->nif);
        }
        if ($this->voluntaryRemissionEndDate !== null) {
            $remisionVoluntariaElement = $cabeceraElement->add('sum1:RemisionVoluntaria');
            $remisionVoluntariaElement->add('sum1:FechaFinVeriFactu', $this->voluntaryRemissionEndDate->format('d-m-Y'));
            $remisionVoluntariaElement->add('sum1:Incidencia', $this->isVoluntaryRemissionAffectedByIncident ? 'S' : 'N');
        }
        if ($this->requirementReference !== null) {
            $remisionRequerimientoElement = $cabeceraElement->add('sum1:RemisionRequerimiento');
            $remisionRequerimientoElement->add('sum1:RefRequerimiento', $this->requirementReference);
            $remisionRequerimientoElement->add('sum1:FinRequerimiento', $this->isLastRequirementSubmission ? 'S' : 'N');
        }
        // Add registration records
        foreach ($records as $record) {
            $record->export($baseElement->add('sum:RegistroFactura'), $this->system);
        }
        // Send request
        $options = ['base_uri' => $this->getBaseUri(), 'http_errors' => \false, 'headers' => ['Content-Type' => 'text/xml', 'User-Agent' => "Mozilla/5.0 (compatible; {$this->system->name}/{$this->system->version})"], 'body' => $xml->asXML()];
        if ($this->certificatePath !== null) {
            $options['cert'] = $this->certificatePassword === null ? $this->certificatePath : [$this->certificatePath, $this->certificatePassword];
        }
        $responsePromise = $this->client->postAsync('/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP', $options);
        // Parse and return response
        return $responsePromise->then(fn(ResponseInterface $response): string => $response->getBody()->getContents())->then(function (string $response): UXML {
            try {
                return UXML::fromString($response);
            } catch (InvalidArgumentException $e) {
                throw new AeatException('Failed to parse XML response', previous: $e);
            }
        })->then(fn(UXML $xml): AeatResponse => AeatResponse::from($xml));
    }
    /**
     * Get base URI of web service
     *
     * @return string Base URI
     */
    private function getBaseUri(): string
    {
        if ($this->isEntitySeal) {
            return $this->isProduction ? 'https://www10.agenciatributaria.gob.es' : 'https://prewww10.aeat.es';
        }
        return $this->isProduction ? 'https://www1.agenciatributaria.gob.es' : 'https://prewww1.aeat.es';
    }
}
