<?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\Rules\ValidateId;
use Illuminate\Database\Eloquent\Collection;
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 Daniv04\HaciendaPackage\Services\Validators\PayloadStructureValidator;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\Log;
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;
    protected PayloadStructureValidator $payloadStructureValidator;

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

    // ========================= 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
     * @return array Standardised result with 'success', 'data'|'errors', 'message', and optional 'status_code'
     */
    public function createAndSend(string $type, array $data): array
    {
        $label = self::resolveLabel($type);

        try {
            // Fast-fail on top-level payload structure
            $this->payloadStructureValidator->validate($data, $type);

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

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

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

            $this->sendToProvider($receipt, $validatedData);

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

        } catch (ValidationException $e) {
            Log::warning("Validation error in InvoicingService::createAndSend ({$type})", [
                'errors' => $e->errors(),
            ]);
        
            return self::errorResult('Validation Error', 422, $e->errors());

        } catch (\Exception $e) {
            Log::error("Error in InvoicingService::createAndSend ({$type})", [
                'message' => $e->getMessage(),
                'file'    => $e->getFile(),
                'line'    => $e->getLine(),
                'trace'   => $e->getTraceAsString(),
            ]);

            return self::errorResult("Error saving {$label}: " . $e->getMessage(), 500);
        }
    }

    // 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.
     *
     * Reception messages are used to accept, partially accept, or reject electronic documents
     * received from suppliers. The document type and reception status are derived automatically
     * from the Clave.TipoComprobante and Detalle.CodigoMensaje fields in the payload.
     *
     * @param array $data Raw input data
     * @return array Standardised result with 'success', 'data'|'errors', 'message', and optional 'status_code'
     */
    public function createAndSendReception(array $data): array
    {
        try {
            $validator     = Validator::make($data, ReceptionDocumentValidatorService::rules($data));
            $validatedData = $validator->validate();

            $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');

        } catch (ValidationException $e) {
            Log::warning('Validation error in InvoicingService::createAndSendReception', [
                'errors' => $e->errors(),
            ]);

            return self::errorResult('Validation Error', 422, $e->errors());

        } catch (\Exception $e) {
            Log::error('Error in InvoicingService::createAndSendReception', [
                'message' => $e->getMessage(),
                'file'    => $e->getFile(),
                'line'    => $e->getLine(),
                'trace'   => $e->getTraceAsString(),
            ]);

            return self::errorResult('Error saving reception document: ' . $e->getMessage(), 500);
        }
    }

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

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

    /**
     * Get all documents of a given type.
     *
     * @param string $type Document type code
     */
    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);
    }

    /**
     * Get the Hacienda processing status for a document.
     */
    public function getDocumentStatus(string $uiKey): ?Receipt
    {
        return $this->dbService->getReceiptStatusByUiKey($uiKey);
    } 

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

    /**
     * Dispatch a receipt to the configured electronic invoicing provider.
     */
    protected function sendToProvider(Receipt $receipt, array $data): void
    {
        $provider = config('invoicing.electronic_invoicing_provider');
        SendReceiptToProviderJob::dispatch($receipt, $data, $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 ValidationException|\InvalidArgumentException
     */
    public function validateByType(string $type, array $data): array
    {
        $validatorClass = self::resolveValidator($type);

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

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

        return $validator->validate();
    }

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

    /**
     * Resolve the  label for a document type.
     */
    private static function resolveLabel(string $type): string
    {
        return self::DOCUMENT_TYPE_LABELS[$type]
            ?? throw new \InvalidArgumentException("Unsupported document type: {$type}");
    }

    /**
     * Resolve the validator service class for a document type.
     */
    private static function resolveValidator(string $type): string
    {
        return self::VALIDATOR_MAP[$type]
            ?? throw new \InvalidArgumentException("No validator registered for document type: {$type}");
    }

    /**
     * Assert that a type code is supported.
     */
    private static function assertValidType(string $type): void
    {
        if (!isset(self::DOCUMENT_TYPE_LABELS[$type])) {
            throw new \InvalidArgumentException("Unsupported document type: {$type}");
        }
    }



    /**
     * Map CodigoMensaje to a reception status string.
     */
    private static function mapReceptionStatus(?string $code): string
    {
        return match ($code) {
            '1'     => 'accepted',
            '2'     => 'partially_accepted',
            '3'     => 'rejected',
            default => 'unknown',
        };
    }

    /**
     * Map Clave.TipoComprobante to a reception document type code.
     */
    private static function mapReceptionType(?string $code): string
    {
        return match ($code) {
            '05'    => self::TYPE_CA,
            '06'    => self::TYPE_CAP,
            '07'    => self::TYPE_CR,
            default => 'unknown',
        };
    }

    /**
     * Build a standardised success result array.
     */
    private static function successResult(array $data, string $message): array
    {
        return [
            'success' => true,
            'data'    => $data,
            'message' => $message,
        ];
    }

    /**
     * Build a standardised error result array.
     */
    private static function errorResult(string $message, int $statusCode, ?array $errors = null): array
    {
        $result = [
            'success'     => false,
            'message'     => $message,
            'status_code' => $statusCode,
        ];

        if ($errors !== null) {
            $result['errors'] = $errors;
        }

        return $result;
    }
}
