<?php

namespace App\Services\Facturapi;

use Facturapi\Exceptions\Facturapi_Exception;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\Log;

use App\Enums\Invoicing\InvoiceStatus;
use App\Enums\Invoicing\GlobalPeriodicity;

use App\Models\Hotel\Client;
use App\Models\Invoicing\TaxInvoice;
use App\Models\Invoicing\TaxFormat;
use App\Models\Invoicing\TaxBranch;

use App\Helpers\Database\SatCatalogsHelper;

use App\Services\Facturapi\FacturapiInvoiceSynchronizer;

class FacturapiInvoiceService extends FacturapiResourceService
{
  //* General Configuration
    protected FacturapiInvoiceSynchronizer $synchronizer;

    public function __construct(
        FacturapiService $facturapi,
        FacturapiInvoiceSynchronizer $synchronizer
    ){
        parent::__construct($facturapi);
        $this->synchronizer = $synchronizer;
    }

  //* CRUD Functions

    /**
     * Crear factura (timbrar CFDI de ingreso)
     */
    public function create(array $invoice_data, array $items_data, bool $return_payload = false): array
    {
        try {
            $payload = $this->parsePayload(["invoice_data" => $invoice_data, "items_data" => $items_data]);

            $result = $this->client->Invoices->create($payload);
            $parsed_result = $this->toArray($result);

            if($return_payload) {
                return [
                    "response" => $parsed_result,
                    "payload" => $payload,
                ];
            }

            return $parsed_result;
        } catch (Facturapi_Exception $e) {
            $this->handleFacturapiException($e);
        }
    }

    /**
     * Obtener factura por ID de Facturapi
     */
    public function retrieve(string $facturapiId): array
    {
        $result = $this->client->Invoices->retrieve($facturapiId);

        return $this->toArray($result);
    }

    /**
     * Cancelar factura
     */
    public function cancel(string $facturapiId, array $options = []): array
    {
        try {
            $result = $this->client->Invoices->cancel($facturapiId, $options);

            return $this->toArray($result);
        } catch (Facturapi_Exception $e) {
            $this->handleFacturapiException($e);
        }
    }

    /**
     * Enviar Factura por Email
     */
    public function send_by_email(string $facturapiId, array $receivers = []): array
    {
        try {
            $result = $this->client->Invoices->send_by_email(
                $facturapiId,
                empty($receivers) ? null : $receivers
            );

            return $this->toArray($result);
        } catch (Facturapi_Exception $e) {
            $this->handleFacturapiException($e);
        }
    }

    /**
     * Descargar el XML de la Factura indicada
     */
    public function download_xml(string $facturapiId, array $options = [])
    {
        try {
            $result = $this->client->Invoices->download_xml($facturapiId, $options);

            return $result;
        } catch (Facturapi_Exception $e) {
            $this->handleFacturapiException($e);
        }
    }

    /**
     * Crear factura y, en el mismo flujo, descargar su XML.
     *
     * Retorna algo así como:
     *
     * [
     *   'invoice' => [ ...datos de la factura timbrada... ],
     *   'xml'     => [ ...respuesta de download_xml... ],
     * ]
     */
    public function createWithXml(array $invoice_data, array $items_data, array $downloadOptions = []): array
    {
      // Stamp Invoice
        $invoice = $this->create($invoice_data, $items_data, true);

            if (empty($invoice['response']['id'])) {
                Log::error('La respuesta de Facturapi al crear la factura no incluye ID.', ['invoice_response' => $invoice['response']]);

                throw ValidationException::withMessages([
                    'facturapi' => [ "La factura se timbró pero la respuesta de Facturapi no incluye el ID. Notifica este error."],
                ]);
            }

      // Download XML
        $xml = $this->download_xml($invoice['response']['id'], $downloadOptions);

      // Return data
        return [
            'payload' => $invoice['payload'],
            'invoice' => $invoice['response'],
            'xml'     => $xml,
        ];
    }


  //* Inherited Functions

    protected function parsePayload(array $form_data): array
    {
        $invoice_data = (object) $form_data['invoice_data'];
        $items_data = $form_data['items_data'];

      // Support Data
        $costumer = Client::findOrFail($invoice_data->client_id);
        $branch = TaxBranch::findOrFail($invoice_data->tax_branch_id);
        $format = TaxFormat::find($invoice_data->tax_format_id);

        // Series
        $invoice_serie = $format ? $format->serie : $invoice_data->invoice_type;

      // Other Structures
        // Related documents
        $related_documents = collect($invoice_data->related_documents)
            ->groupBy('relation_type')
            ->map(function ($items, $relationType) {
                return [
                    'relationship' => $relationType,
                    'documents' => $items->pluck('uuid')->values()->toArray()
                ];
            })->values()->toArray();

        // Global Invoice Data
        $global_data = [];
        if (!empty($invoice_data->global) && is_array($invoice_data->global)) {
            $periodicity_key = $invoice_data->global['periodicity'];
            $periodicity = GlobalPeriodicity::fromSatClave($periodicity_key)->value;

            $global_data = ["global" => [
                "periodicity" => $periodicity,
                "months" => $invoice_data->global["months"],
                "year" => $invoice_data->global["year"],
            ]];
        }

        // Items
        $invoice_items = [];
        foreach ($items_data as $item){
          // Third Account
            $third_party = [
                "legal_name" => $item->third_party_account->legal_name,
                "tax_id" => $item->third_party_account->tax_identifier,
                "tax_system" => $item->third_party_account->tax_system,
                "zip" => $item->third_party_account->zip,
            ];
            $third_party = in_array(null, $third_party, true) ? null : $third_party;

          // Build Item Payload
            $invoice_items[] = array_filter([
                "product" => [
                    "description" => $item->description,

                    "product_key" => $item->prod_serv_key,
                    "unit_key" => $item->unit_key,
                    "unit_name" => $this->sliceUnitName(SatCatalogsHelper::getUnit($item->unit_key)),

                    "price" => $item->unit_price,
                    "tax_included" => $item->unit_price_includes_tax,
                    "taxability" => $item->object_tax,

                    "taxes" => $this->parseFederalTaxes((array) $item->tax_profile),
                    "local_taxes" => $this->parseLocalTaxes((array)$item->local_tax_profile),
                ],
                "quantity" => $item->quantity,
                "discount" => $item->discount_total,
                "third_party" => $third_party,
                "property_tax_account" => $item->property_tax_account,
            ], fn ($value) => $value !== null);
        }

      // Build Payload
        return [
            "customer" => [
                "legal_name" => $costumer->legal_name,
                "tax_id" => $costumer->tax_id,
                "tax_system" => $costumer->tax_system,
                "address" => array_filter([
                    "zip" => $costumer->zip,
                    ... $costumer->tax_address
                ], fn ($value) => $value !== null),

                "email" => $costumer->email_billing,
                "phone" => $costumer->client_phone,
                "default_invoice_use" => $invoice_data->cfdi_use ?? $costumer->cfdi_use,
            ],

            ...$global_data, // Solo aplicable en Facturas Genéricas/Globales

            'address' => array_filter([
                'zip'          => $branch->zip,
                'street'       => $branch->tax_address['street'],
                'exterior'     => $branch->tax_address['exterior'],
                'interior'     => $branch->tax_address['interior'],
                'neighborhood' => $branch->tax_address['neighborhood'],
                'city'         => $branch->tax_address['city'],
                'municipality' => $branch->tax_address['municipality'],
                'state'        => $branch->tax_address['state'],
            ], fn ($value) => $value !== null), // Retrieves only non-null fields

            "items" => $invoice_items,

            "payment_form" => $invoice_data->payment_form,
            "payment_method" => $invoice_data->payment_method,
            "use" => $invoice_data->cfdi_use ?? $costumer->cfdi_use,
            "type" => $invoice_data->invoice_type,
            "currency" => $invoice_data->currency,
            "exchange" => $invoice_data->exchange_rate,
            "related_documents" => $related_documents,

            "export" => $invoice_data->export,
            "type" => $invoice_data->invoice_type,
            "status" => InvoiceStatus::Pending,
            "date" => $invoice_data->date,

            'series' => $invoice_serie,
            "folio_number" => $invoice_data->serial_consecutive,
        ];
    }


  //* Polling
    public function syncInvoiceFromFacturapi(TaxInvoice $invoice){
        try{
          // Obtenemos los datos desde Facturapi
            $facturapi_invoice = $this->client->Invoices->retrieve(
                $invoice->facturapi_invoice_id
            );

            // Parse data as Objet=ct
            $facturapi_invoice = json_decode(json_encode($facturapi_invoice), true);

            if(!$facturapi_invoice || !isset($facturapi_invoice['id'])){
                Log::channel('facturapi_polling')->warning('Respuesta invalida de Facturapi', [
                    'invoice_id' => $invoice->id
                ]);
                return;
            }

            // Depuración: ¿Qúe datos se actualizaron?
            Log::channel('facturapi_polling')->info('Respuesta obtenida de Facturapi', [
                'invoice_id' => $invoice->id,
                'facturapi_status' => data_get($facturapi_invoice, 'status'),
                'facturapi_cancellation_status' => data_get($facturapi_invoice, 'cancellation_status'),
            ]);

          // Sincronización y guardado
            $original_updated_at = $invoice->updated_at; // Guardamos timestamp original
            $this->synchronizer->sync($invoice, $facturapi_invoice); // Sincronizamos datos
            $current_db_invoice = $invoice->fresh(); // Consultamos el registro antes de actualizar

            $old_status = $invoice->status?->value;
            $old_cancellation_status = $invoice->cancellation_status?->value;

            // No se debe sincronizar si hubo un cambio en el mismo momento en otro lado
            if(!$current_db_invoice->updated_at->eq($original_updated_at)){
                Log::channel('facturapi_polling')->warning("Factura modificada durante sincronización", [
                    'invoice_id' => $invoice->id,
                    'original_updated_at' => $original_updated_at,
                    'current_updated_at' => $current_db_invoice->updated_at,
                ]);
                return;
            }

            $invoice->save();

            // Depuración: ¿Realmente cambiaron datos?
            Log::channel('facturapi_polling')->info('Polling sincronizo factura', [
                'invoice_id' => $invoice->id,

                'status_before' => $old_status,
                'status_after' => $invoice->status?->value,

                'cancellation_status_before' => $old_cancellation_status,
                'cancellation_status_after' => $invoice->cancellation_status?->value,
            ]);

        }catch(\Exception $e){
            Log::channel('facturapi_polling')->error('Error durante conciliación', [
                'invoice_id' => $invoice->id,
                'message'      => $e->getMessage(),
            ]);
        }
    }
}
