<?php

namespace Daniv04\HaciendaPackage\Services;

use Illuminate\Support\Facades\Validator;
use Daniv04\HaciendaPackage\Services\DbService\DbService;
use Daniv04\HaciendaPackage\Services\Providers\ProviderFactoryService;
use Daniv04\HaciendaPackage\Jobs\SendReceiptToProviderJob;
use Daniv04\HaciendaPackage\Jobs\SendSentReceiptToProviderJob;
use Daniv04\HaciendaPackage\Models\Receipt;
use Daniv04\HaciendaPackage\Exceptions\HaciendaException;
use Daniv04\HaciendaPackage\Exceptions\InvalidReceiptException;
use Daniv04\HaciendaPackage\Services\Validators\ElectronicInvoiceValidatorService;
use Daniv04\HaciendaPackage\Services\Validators\ElectronicExportInvoiceValidatorService;
use Daniv04\HaciendaPackage\Services\Validators\ElectronicPurchaseInvoiceValidatorService;
use Daniv04\HaciendaPackage\Services\Validators\ElectronicTicketValidatorService;
use Daniv04\HaciendaPackage\Services\Validators\DebitNoteValidatorService;
use Daniv04\HaciendaPackage\Services\Validators\CreditNoteValidatorService;
use Daniv04\HaciendaPackage\Services\Validators\ElectronicPaymentReceiptValidatorService;
use Daniv04\HaciendaPackage\Services\Validators\ReceptionDocumentValidatorService;
use Daniv04\HaciendaPackage\Services\Validators\CalculationValidatorService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;

class InvoicingService
{
    /** Supported document type codes */
    public const TYPE_FE  = 'FE';
    public const TYPE_FEE = 'FEE';
    public const TYPE_FEC = 'FEC';
    public const TYPE_TE  = 'TE';
    public const TYPE_ND  = 'ND';
    public const TYPE_NC  = 'NC';
    public const TYPE_REP = 'REP';

    /** Reception message type codes */
    public const TYPE_CA  = 'CA';
    public const TYPE_CAP = 'CAP';
    public const TYPE_CR  = 'CR';

    /** Human-readable labels per document type */
    private const DOCUMENT_TYPE_LABELS = [
        self::TYPE_FE  => 'Electronic Invoice',
        self::TYPE_FEE => 'Electronic Export Invoice',
        self::TYPE_FEC => 'Electronic Purchase Invoice',
        self::TYPE_TE  => 'Electronic Ticket',
        self::TYPE_ND  => 'Electronic Debit Note',
        self::TYPE_NC  => 'Electronic Credit Note',
        self::TYPE_REP => 'Electronic Payment Receipt',
    ];

    /** Validator class per document type */
    private const VALIDATOR_MAP = [
        self::TYPE_FE  => ElectronicInvoiceValidatorService::class,
        self::TYPE_FEE => ElectronicExportInvoiceValidatorService::class,
        self::TYPE_FEC => ElectronicPurchaseInvoiceValidatorService::class,
        self::TYPE_TE  => ElectronicTicketValidatorService::class,
        self::TYPE_ND  => DebitNoteValidatorService::class,
        self::TYPE_NC  => CreditNoteValidatorService::class,
        self::TYPE_REP => ElectronicPaymentReceiptValidatorService::class,
    ];

    protected DbService $dbService;
    protected ProviderFactoryService $providerFactory;
    protected CalculationValidatorService $calculationValidator;

    public function __construct(DbService $dbService, ProviderFactoryService $providerFactory)
    {
        $this->dbService = $dbService;
        $this->providerFactory = $providerFactory;
    }

    // ========================= Create & Send =========================

    /**
     * Validate, persist, and queue a document for sending to the provider.
     *
     * @param string $type Document type code (FE, FEE, FEC, TE, ND, NC, REP)
     * @param array  $data Raw input data
     * @throws InvalidReceiptException|\Illuminate\Validation\ValidationException
     */
    public function createAndSend(string $type, array $data): array
    {
        $label = self::resolveLabel($type);

        $validatedData = $this->validateByType($type, $data);

        // Validate all mathematical calculations before persisting
        //$this->calculationValidator->validate($validatedData, $type);
        $date = now();

        $data['Encabezado']['FechaEmision'] = $date->toIso8601String();

        $receipt = $this->dbService->saveReceipt($validatedData, $type);

        $this->sendToProvider($receipt);

        return self::successResult([
            'receipt_id'         => $receipt->id,
            'consecutive_number' => $receipt->consecutive_number,
            'status'             => 'queued',
        ], "{$label} created successfully and queued for sending to provider");
    }

    // Convenience wrappers — delegate to createAndSend
    public function createAndSendFE(array $data): array  { return $this->createAndSend(self::TYPE_FE, $data); }
    public function createAndSendFEE(array $data): array { return $this->createAndSend(self::TYPE_FEE, $data); }
    public function createAndSendFEC(array $data): array { return $this->createAndSend(self::TYPE_FEC, $data); }
    public function createAndSendTE(array $data): array  { return $this->createAndSend(self::TYPE_TE, $data); }
    public function createAndSendND(array $data): array  { return $this->createAndSend(self::TYPE_ND, $data); }
    public function createAndSendNC(array $data): array  { return $this->createAndSend(self::TYPE_NC, $data); }
    public function createAndSendREP(array $data): array { return $this->createAndSend(self::TYPE_REP, $data); }

    /**
     * Validate, persist, and queue a reception message (CA, CAP, CR) for sending to the provider.
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    public function createAndSendReception(array $data): array
    {
        $validator = Validator::make($data, ReceptionDocumentValidatorService::rules($data));

        try {
            $validatedData = $validator->validate();
        } catch (\Illuminate\Validation\ValidationException $e) {
            $firstError = collect($e->errors())->flatten()->first() ?? 'Error de validación del documento de recepción.';
            throw new InvalidReceiptException($firstError, 0, $e->errors(), $e);
        }

        $documentStatus = self::mapReceptionStatus($validatedData['Detalle']['CodigoMensaje'] ?? null);
        $documentType   = self::mapReceptionType($validatedData['Clave']['TipoComprobante'] ?? null);

        $receipt  = $this->dbService->saveSentReceipt($validatedData, $documentStatus, $documentType);
        $provider = config('invoicing.electronic_invoicing_provider');

        SendSentReceiptToProviderJob::dispatch($receipt, $validatedData, $provider);

        return self::successResult([
            'receipt_id'         => $receipt->id,
            'consecutive_number' => $receipt->consecutive_number,
            'status'             => 'queued',
            'message'            => 'Receipt queued for processing',
        ], 'Document created successfully and queued for sending to provider');
    }

    // ========================= Queries =========================

    /**
     * Get all electronic documents regardless of type.
     */
    public function getAllDocuments(): LengthAwarePaginator
    {
        return $this->dbService->getAllReceipts();
    }

    /**
     * Get all documents of a given type.
     *
     * @throws InvalidReceiptException
     */
    public function getAllByType(string $type): LengthAwarePaginator
    {
        self::assertValidType($type);

        return $this->dbService->getAllReceiptsByType($type);
    }

    // Convenience wrappers
    public function getAllInvoices(): LengthAwarePaginator        { return $this->getAllByType(self::TYPE_FE); }
    public function getAllExportInvoices(): LengthAwarePaginator  { return $this->getAllByType(self::TYPE_FEE); }
    public function getAllCreditNotes(): LengthAwarePaginator     { return $this->getAllByType(self::TYPE_NC); }
    public function getAllDebitNotes(): LengthAwarePaginator      { return $this->getAllByType(self::TYPE_ND); }
    public function getAllPaymentReceipts(): LengthAwarePaginator { return $this->getAllByType(self::TYPE_REP); }
    public function getAllTickets(): LengthAwarePaginator         { return $this->getAllByType(self::TYPE_TE); }

    /**
     * Find all documents by their status.
     */
    public function getDocumentsByStatus(string $status): ?LengthAwarePaginator
    {
        return $this->dbService->getReceiptsByStatus($status);
    }

    /**
     * Find all documents by their Hacienda processing status.
     */
    public function getDocumentsByHaciendaStatus(string $haciendaStatus): ?LengthAwarePaginator
    {
        return $this->dbService->getReceiptsByHaciendaStatus($haciendaStatus);
    }

    /**
     * Find all documents by their Hacienda status.
     */
    public function getDocumentById(int|string $id): ?Receipt
    {
        return $this->dbService->getReceiptById($id);
    }

    /**
     * Find a single document by its UI key (clave).
     */
    public function getDocumentByUiKey(string $uiKey): ?Receipt
    {
        return $this->dbService->getReceiptByUiKey($uiKey);
    }

    /**
     * Consulta el estado de un comprobante en Hacienda y actualiza la DB si cambió.
     *
     * @throws HaciendaException
     */
    public function getDocumentStatus(string $uiKey): array
    {
        $provider = $this->providerFactory->getProviderInstance(
            config('invoicing.electronic_invoicing_provider')
        );

        $status    = $provider->getDocumentStatus($uiKey);
        $indEstado = strtolower($status['ind-estado'] ?? '');

        $isAccepted = in_array($indEstado, ['1', 'aceptado']);
        $isRejected = in_array($indEstado, ['2', '3', 'rechazado']);

        $receipt = $this->dbService->getReceiptStatusByUiKey($uiKey);

        if ($receipt) {
            if ($isAccepted) {
                $receipt->update(['receipt_status' => 'accepted', 'hacienda_status' => 'accepted']);
            } elseif ($isRejected) {
                $receipt->update(['receipt_status' => 'rejected', 'hacienda_status' => 'rejected']);
            }
        }

        return $status;
    }

    // ========================= Provider =========================

    /**
     * Dispatch a receipt to the configured electronic invoicing provider.
     */
    protected function sendToProvider(Receipt $receipt): void
    {
        $provider = config('invoicing.electronic_invoicing_provider');
        SendReceiptToProviderJob::dispatch($receipt, $provider);
    }

    /**
     * Resolve the configured provider instance.
     */
    public function getProvider()
    {
        $provider = config('invoicing.electronic_invoicing_provider');
        return $this->providerFactory->getProviderInstance($provider);
    }

    // ========================= Validation =========================

    /**
     * Validate input data using the validator service mapped to the given document type.
     *
     * @throws InvalidReceiptException|\Illuminate\Validation\ValidationException
     */
    public function validateByType(string $type, array $data): array
    {
        $validatorClass = self::resolveValidator($type);

        $validator = Validator::make($data, $validatorClass::rules($data));

        $validatorClass::after($validator, $data);

        try {
            return $validator->validate();
        } catch (\Illuminate\Validation\ValidationException $e) {
            $firstError = collect($e->errors())->flatten()->first() ?? 'Error de validación del comprobante.';
            throw new InvalidReceiptException($firstError, 0, $e->errors(), $e);
        }
    }

    // ========================= Internal helpers =========================

    private static function resolveLabel(string $type): string
    {
        return self::DOCUMENT_TYPE_LABELS[$type]
            ?? throw new InvalidReceiptException("Unsupported document type: {$type}");
    }

    private static function resolveValidator(string $type): string
    {
        return self::VALIDATOR_MAP[$type]
            ?? throw new InvalidReceiptException("No validator registered for document type: {$type}");
    }

    private static function assertValidType(string $type): void
    {
        if (!isset(self::DOCUMENT_TYPE_LABELS[$type])) {
            throw new InvalidReceiptException("Unsupported document type: {$type}");
        }
    }

    private static function mapReceptionStatus(?string $code): string
    {
        return match ($code) {
            '1'     => 'accepted',
            '2'     => 'partially_accepted',
            '3'     => 'rejected',
            default => 'unknown',
        };
    }

    private static function mapReceptionType(?string $code): string
    {
        return match ($code) {
            '05'    => self::TYPE_CA,
            '06'    => self::TYPE_CAP,
            '07'    => self::TYPE_CR,
            default => 'unknown',
        };
    }

    private static function successResult(array $data, string $message): array
    {
        return [
            'success' => true,
            'data'    => $data,
            'message' => $message,
        ];
    }
}
