<?php

namespace App\Services;

use App\Data\DiscountData;
use App\Data\ElectronicDocument;
use App\Data\LineaItemData;
use App\Data\TaxData;
use Exception;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;

use App\HttpClients\Clients\HaciendaClient;
use App\Services\CertificadosService;
use App\Utils\ConsecutiveGenerator;

use App\Factories\ElectronicDocumentFactory;

use App\Enums\CodigoImpuesto;
use App\Enums\CodigoTarifaIVA;
use App\Enums\TipoDocumento;
use App\HttpClients\DTOs\Hacienda\DocStatusResponse;
use App\Models\Sucursal;
use App\Utils\SucursalUtil;
use Carbon\Carbon;

use Closure;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Cache;
use Illuminate\Validation\UnauthorizedException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Throwable;

use Illuminate\Support\Facades\Http;

class DocumentoService 
{
    protected $client;

    public function __construct(
        protected ElectronicDocumentFactory $factory,
        protected HaciendaClient $mh_client,
        protected TipoCambioService $tc_client,
        protected HaciendaSigner $signer
    ) {}

    public function agregarCliente($data) 
    {
        try {
            $response = Http::withOptions(['verify' => false])
                ->get('https://api.hacienda.go.cr/fe/ae', [
                    'identificacion' => $data['numero_identificacion']
                ])
                ->throw()
                ->json();

            DB::table('clientes')->insertGetId([
                'nombre' => $response['nombre'],
                'tipo_identificacion' => $response['tipoIdentificacion'],
                'identificacion' => $data['numero_identificacion'],
                'pin' => $data['pin']
            ]);
        } catch (Throwable $e) {
            return new Exception("No se ha podido agregar el cliente");
        }
    }

    public function crearSucursalApiKey($clienteId, $sucursalId) 
    {
        $exists = DB::table('sucursales')
            ->where('id', $sucursalId)
            ->where('cliente_id', $clienteId)
            ->exists();

        if (!$exists) {
            throw new Exception("No existe esta sucursal para crear un API-KEY");
        }

        $data = [
            "sucursal_id" => $sucursalId,
            "api_key" => SucursalUtil::generarApiKey()
        ];

        $id = DB::table(table: 'api_keys')->insertGetId($data);
        return DB::table('api_keys')->where('id', $id)->first(); 
    }

    public function getSucursalApiKeys($clienteId, $sucursalId)
    {
        $apiKeys = DB::table('api_keys')
            ->join('sucursales', 'api_keys.sucursal_id', '=', 'sucursales.id')
            ->where('sucursales.cliente_id', $clienteId)
            ->where('sucursales.id', $sucursalId)
            // ->select()
            ->get();
       

        return $apiKeys;
    }

    public function crearSucursal($data, $clienteId) 
    {
        $insertData = SucursalUtil::normalizeData($data);
        $insertData['cliente_id'] = $clienteId;

        $exists = DB::table('sucursales')
            ->where('cod_sucursal', $insertData['cod_sucursal'])
            ->exists();

        if ($exists) {
            throw new Exception("Ya existe una sucursal con el mismo codigo");
        }

        $id = DB::table('sucursales')->insertGetId($insertData); 
        return DB::table('sucursales')->where('id', $id)->first();
    }

    public function updateSucursal($data, $clienteId, $sucursalId) 
    {
        $allowed = [ 
            'correo', 
            'telefono', 
            'provincia', 
            'canton', 
            'distrito', 
            'sennas', 
            'nombre_comercial',
            'cod_actividad',
        ];

        $datos = SucursalUtil::normalizeData($data);

        $updateData = array_intersect_key($datos, array_flip($allowed));
        $updateData = array_filter($updateData, function ($value) {
            return !is_null($value) && $value !== '';
        });

        if (empty($updateData)) { 
            throw new Exception("No se recibieron datos para actualizar sucursal");
        }

        $updated = DB::table('sucursales')
            ->where('id', $sucursalId)
            ->where('cliente_id', $clienteId)
            ->update($updateData); 
        
        return DB::table('sucursales')
            ->where('id', $sucursalId)
            ->where('cliente_id', $clienteId)
            ->first(); 
    }

    public function deleteSucursal(int $sucursalId, int $clientId): bool 
    {
        $deleted = DB::table('sucursales')
            ->where('id', $sucursalId)
            ->where('cliente_id', $clientId) 
            ->delete();
        
        return $deleted > 0; 
    }

    public function getSucursalesByClient(int $clientId)
    {
        $sucursales = DB::table('sucursales')
            ->where('cliente_id', $clientId)
            ->get();

        return $sucursales;
    }
    
    public function procesarFirmados() 
    {
        $documentos = DB::table('documentos')
            ->where('estado', 'firmado')
            ->get();
        
        $resultados = []; 
        
        
        foreach ($documentos as $doc) {
            $sucursal = DB::table('sucursales') 
                ->where('id', $doc->sucursal_id) 
                ->first(); 
            
        
            
            if (!$sucursal) { continue; }
            
            $cliente = DB::table('clientes') 
                ->where('id', $sucursal->cliente_id) 
                ->first(); 
            
            $credenciales = DB::table('mh_credenciales')
                ->where('client_id', $sucursal->cliente_id) 
                ->first(); 
                
            if (!$credenciales) { continue; }
            
            $isSandbox = $credenciales->mh_environment == "sandbox";
            
            try {
                $result = [
                    "fecha" => $doc->fecha_emision,
                    "clave" => $doc->clave,
                    "comprobanteXml" => $doc->xml_firmado,
                    "emisor" => [
                        "tipo_identificacion" => $cliente->tipo_identificacion,
                        "numero_identificacion" => $cliente->identificacion
                    ]
                ];
                
                if (!empty($doc->receptor_ident) && !empty($doc->receptor_tipo_ident)) 
                {
                    $result['receptor'] = [
                        "tipo_identificacion" => $doc->receptor_ident,
                        "numero_identificacion" => $doc->receptor_tipo_ident
                    ];
                }
                
            
                
                $this->mh_client->useSandboxMode($isSandbox);
                $this->mh_client->authenticate($credenciales->mh_username, $credenciales->mh_password);
                
                $response = $this->mh_client->sendDocument($result);
                
                
    
                if ($response->accepted())
                {
                    DB::table('documentos')
                        ->where('clave', $result['clave'])
                        ->update([
                            'estado' => 'enviado'
                        ]);
                } else {
                    if ($response->hasHeader('X-Error-Cause')) {
                        $errorCause = $response->header('X-Error-Cause');
                    }
        
                    DB::table('documentos')
                        ->where('clave', $result['clave'])
                        ->update([
                            'estado' => 'error',
                            'respuesta_error_hacienda' => $errorCause ?? null
                        ]);
                }
                
            } catch (\Throwable $e) { continue; }
                
        }
        
    }
    

    public function getDocsByClient(int $clienteId) 
    {
        $documents = DB::table('documentos')
            ->join('sucursales', 'documentos.sucursal_id', '=', 'sucursales.id')
            ->join('clientes', 'sucursales.cliente_id', '=', 'clientes.id')
            ->where('clientes.id', $clienteId)
            ->select(
                'documentos.clave',
                'documentos.consecutivo',
                'documentos.situacion',
                'documentos.receptor_tipo_ident',
                'documentos.receptor_ident',
                'documentos.receptor_nombre',
                'documentos.ambiente',
                'documentos.tipo_documento',
                'documentos.moneda',
                'documentos.xml_firmado',
                'documentos.tipo_cambio',
                'documentos.total_comprobante',
                'documentos.fecha_emision'
            )
            ->get();

        return $documents;
    }

    public function getDocumentByClave(string $clave)
    {
        $record = DB::table('documentos')
            ->where('clave', $clave)
            ->first();
        
        if (!$record) {
            throw new NotFoundHttpException("No se ha encontrado un documento para descargar");
        }

        return $record;
    }
    
    public function getDocumentLineasByDocId(string $id)
    {
        $record = DB::table('documento_detalles')
            ->where('documento_id', $id)
            ->get();

        return $record;
    }

    public function getCredentialsByEnvironment(int $clientId, string $environment) 
    {
        $record = DB::table('mh_credenciales')
            ->where('client_id', '=', $clientId)
            ->where('mh_environment', '=', $environment)
            ->first();

        return $record;
    }

    public function getDocumentStatus(string $clave, string $clientId): DocStatusResponse 
    {
        $record = $this->getDocumentByClave($clave);
        $document = DocStatusResponse::fromDatabase($record);

        if ($document->isAccepted()) {
            return $document;
        }

        $credentials = $this->getCredentialsByEnvironment(
            $clientId,
            $record->ambiente
        );

        $this->testCredentials(
            $credentials->mh_username, 
            $credentials->mh_password, 
            $credentials->mh_environment === "sandbox"
        );

        $reception = $this->mh_client->getDocumentReceptionStatus($clave);

        DB::table('documentos')
            ->where('clave', $clave)
            ->whereIn('estado', ['enviado', 'pendiente'])
            ->update([
                'estado'        => $reception->response_xml ? 'finalizado' : 'pendiente',
                'xml_respuesta' => $reception->response_xml ?? null,
                'respuesta_hacienda' => $reception->status ?? null,
            ]);

        return $reception;
    }

    public function addCredentials($data, $client_id) 
    {
        $sandboxMode = $data['mh_environment'] == "sandbox";
        $this->testCredentials($data['mh_username'], $data['mh_password'], $sandboxMode);

        $p12Content = file_get_contents($data['cert_file']->getRealPath());
        //$p12Processed = CertificadosService::procesarCertificado($p12Content, $data['cert_pin']);

        $certs = [];
        $isValid = openssl_pkcs12_read($p12Content, $certs, $data['cert_pin']);
        if (!$isValid) throw new Exception("No se ha podido procesar el certificado");

        $parsed = openssl_x509_parse($certs['cert']);
        $expiresAt = $parsed['validTo_time_t']; 

        $isExpired = $expiresAt < time();
        if ($isExpired) {
            throw new Exception('El certificado ya ha expirado');
        }

        if(!$parsed['subject']['serialNumber'] === strtoupper(strstr($data['mh_username'], '@', true))) 
        {
            throw new Exception("El certificado no corresponde al usuario brindado");
        }

        $cedula = preg_replace('/\D/', '', $parsed['subject']['serialNumber']);

        if (substr($cedula, 0, 1) === '0') {
            $cedula = substr($cedula, 1); 
        }

        $pathToSave = "certificados/{$cedula}/{$cedula}_{$data['mh_environment']}.p12";
        Storage::disk('local')->put($pathToSave, $p12Content);

        DB::table('mh_credenciales')->updateOrInsert([ 
            'client_id'         => $client_id,
            'mh_environment'    => $data['mh_environment']
        ], [
            'mh_username'       => $data['mh_username'], 
            'mh_password'       => $data['mh_password'],
            'cert_path'         => $pathToSave,
            'cert_pin'          => $data['cert_pin'],
        ]);
    }

    public function testCredentials(string $username, string $password, bool $isSandbox) 
    {
        $this->mh_client->useSandboxMode($isSandbox);
        return $this->mh_client->authenticate($username, $password);
    }

    public function addDocumento($branchData, $data) 
    {
        // SE HACE UNA PRE-FACTURA SIN LA CLAVE NUMERICA, SE INTENTA OBTENER EL TOKEN PARA
        // OBTENER LA SITUACION DEL DOCUMENTO, LUEGO SE ENTRA EN LA TRANSACCION, 
        // SE BLOQUEA LA SUCURSAL PARA OBTENER EL SIGUIENTE CONSECUTIVO
        // SE CREA EL XML CON LA CLAVE Y LA SITUACION...
        // Y SE INTENTA FIRMAR (SI NO ES VALIDA O NO EXISTE, SE TERMINA EL PROCESO)
        // SE GUARDA LO NECESARIO EN BASE DE DATOS... LUEGO DE LA TRANSACCION SE RETORNA
        // CLAVE, XML FIRMADO, SITUACION, Y SI FUE EXITOSO LA AUTH EN HACIENDA SE INTENTA ENVIAR
        // SINO SOLAMENTE SE RETORNA EXITOSO Y LUEGO SE INTENTA MEDIANTE CRON
   
        $tipoDocumento = TipoDocumento::from($data['tipo_documento']);
        $draftInvoice = $this->enrich([...$data, "branch" => $branchData]);
        
        $result = $this->withSiguienteNumero($branchData['sucursal_id'], $tipoDocumento->consecutivoCol(),
        function ($sucursal, $consecutivo) use ($data, $branchData, $draftInvoice)
        {
            
            try
            {
                $tipoDoc = $data['tipo_documento'];

                $consecutivoNumerico = ConsecutiveGenerator::generarNumeroConsecutivo(
                    $sucursal->cod_sucursal, 
                    $sucursal->cod_terminal, 
                    $tipoDoc, 
                    $consecutivo
                );

                $emisor = $data['emisor'];
                $now = Carbon::now('America/Costa_Rica');
                $situacion = "1";

                try {
                    $this->mh_client->useSandboxMode($branchData['isSandbox']);
                    $this->mh_client->authenticate($branchData['username'], $branchData['password']);
                } catch (\RuntimeException $e) {
                    // SI NO ES POSIBLE CONECTAR CON HACIENDA (NO RESPONDE EL SERVIDOR) SE HACE EL COMPROBANTE EN SITUACION 3
                    $situacion = "3";
                } catch (UnauthorizedException|RequestException $e) {
                    // SI NO TIENE CREDENCIALES VALIDAS SE RECHAZA LA CREACION DEL COMPROBANTE
                    throw new Exception('Credenciales incorrectas para conectar con Hacienda...');
                }
                
                $claveNumerica = ConsecutiveGenerator::generarClave(
                    $now,
                    $consecutivoNumerico, 
                    $emisor['tipo_identificacion'],
                    $emisor['numero_identificacion'],
                    $situacion
                );
                
                $finalInvoice = $draftInvoice->finalize(
                    $claveNumerica,
                    $consecutivoNumerico,
                    $now->toAtomString()
                );

                $xmlBuilder = $this->factory::create($tipoDoc);
                $rawXml     = $xmlBuilder->build($finalInvoice);

                $signedXml  = $this->signer::sign($branchData, $rawXml);

                $receptor = $draftInvoice->receptor ? [
                    "receptor_nombre" => $draftInvoice->receptor->nombre,
                    "receptor_nombre_comercial" => $draftInvoice->receptor->nombre_comercial,
                    "receptor_tipo_ident" => $draftInvoice->receptor->tipo_identificacion,
                    "receptor_ident" => $draftInvoice->receptor->numero_identificacion,
                    "receptor_correo"  => $draftInvoice->receptor->correo,
                    "receptor_provincia" => $draftInvoice->receptor->ubicacion['provincia'] ?? null,
                    "receptor_canton" => $draftInvoice->receptor->ubicacion['canton'] ?? null,
                    "receptor_distrito" => $draftInvoice->receptor->ubicacion['distrito'] ?? null,
                    "receptor_barrio" => $draftInvoice->receptor->ubicacion['barrio'] ?? null,
                    "receptor_otras_sennas" => $draftInvoice->receptor->ubicacion['receptor_otras_sennas'] ?? null,
                ] : [];
           
                $documentId = DB::table('documentos')->insertGetId([
                    'sucursal_id'       => $branchData['sucursal_id'],
                    'clave'             => $claveNumerica,
                    'consecutivo'       => $consecutivoNumerico,
                    'tipo_documento'    => $tipoDoc,
                    'cod_actividad_emisor'      => $draftInvoice->cod_act_economica_emisor,
                    'cod_actividad_receptor'    => $draftInvoice->cod_act_economica_receptor,
                    'cod_sucursal'      => (string) $branchData['sucursal_code'],
                    'cod_terminal'      => (string) $branchData['sucursal_terminal'],
                    'situacion'         => $situacion,
                    'condicion_venta'   => $draftInvoice->condicion_venta,
                    'plazo_credito'     => $draftInvoice->plazo_credito,
                    'moneda'            => $draftInvoice->moneda,
                    'tipo_cambio'       => (strtoupper((string)$draftInvoice->moneda) === 'CRC')
                        ? 1.0
                        : (float)$draftInvoice->tipo_cambio,
                    'ambiente'          => $sucursal->mh_ambiente,
                    'xml_firmado'       => $signedXml,

                    'medio_pago'            => $draftInvoice->medio_pago[0]['medio_pago_tipo'],
                    'medio_pago_otros'      => $draftInvoice->medio_pago[0]['medio_pago_otros'] ?? null,

                    ...$receptor,
                    
                    'total_serv_gravados'   => $draftInvoice->resumen['total_serv_gravados'],
                    'total_serv_exentos'    => $draftInvoice->resumen['total_serv_exentos'],
                    'total_serv_exonerados' => $draftInvoice->resumen['total_serv_exonerados'],
                    'total_serv_no_sujetos' => $draftInvoice->resumen['total_serv_no_sujetos'],
                    'total_merc_gravadas'   => $draftInvoice->resumen['total_merc_gravadas'],
                    'total_merc_exentas'    => $draftInvoice->resumen['total_merc_exentas'],
                    'total_merc_exoneradas' => $draftInvoice->resumen['total_merc_exoneradas'],
                    'total_merc_no_sujetas' => $draftInvoice->resumen['total_merc_no_sujetas'],
                    'total_gravado'         => $draftInvoice->resumen['total_gravado'],
                    'total_exento'          => $draftInvoice->resumen['total_exento'],
                    'total_exonerado'       => $draftInvoice->resumen['total_exonerado'],
                    'total_no_sujeto'       => $draftInvoice->resumen['total_no_sujeto'],
                    'total_venta'           => $draftInvoice->resumen['total_venta'],
                    'total_descuentos'      => $draftInvoice->resumen['total_descuentos'],
                    'total_venta_neta'      => $draftInvoice->resumen['total_venta_neta'],
                    'total_impuesto'        => $draftInvoice->resumen['total_impuesto'],
                    'total_imp_asum_emi'    => $draftInvoice->resumen['total_imp_asum_emi'],
                    'total_iva_devuelto'    => $draftInvoice->resumen['total_iva_devuelto'],
                    'total_comprobante'     => $draftInvoice->resumen['total_comprobante'],
                    'fecha_emision'         => $now->toAtomString(),
                    'estado'                => 'firmado',
                    'ref_tipo_documento'    => $draftInvoice->info_referencia['tipo_doc'] ?? null,
                    'ref_clave'             => $draftInvoice->info_referencia['clave'] ?? null,
                    'ref_fecha_emision'     => $draftInvoice->info_referencia['fecha_emision'] ?? null,
                    'ref_codigo'            => $draftInvoice->info_referencia['codigo'] ?? null,
                    'ref_razon'             => $draftInvoice->info_referencia['razon'] ?? null
                ]);
                
                $lines = collect($draftInvoice->lineas)->map(function ($line, $index) use ($documentId) {
                    return [
                        'num_linea'         => $line->numero_linea,
                        'codigo_cabys'      => $line->codigo_cabys,
                        'unidad_medida'     => $line->unidad_medida,
                        'detalle'           => $line->detalle,
                        'precio_unitario'   => $line->precio_unitario,
                        'cantidad'          => $line->cantidad,
                        'monto_total'       => $line->monto_total,
                        'sub_total'         => $line->sub_total,
                        'base_imponible'    => $line->sub_total,
                        'codigo_descuento'  => $line->cod_desc ? str_pad((string) $line->cod_desc, 2, "0", STR_PAD_LEFT) : null,
                        'codigo_descuento_otros' => $line->cod_desc_otros,
                        'razon_descuento'   => $line->desc_razon,
                        'monto_descuento'   => $line->desc_total > 0 ? $line->desc_total : null,
                        'codigo_impuesto'   => $line->cod_imp,
                        'codigo_impuesto_otros' => $line->cod_imp_otros,
                        'codigo_tarifa_iva' => $line->cod_imp_iva,
                        'tarifa_impuesto'   => $line->imp_tarifa,
                        'monto_impuesto'    => $line->imp_monto,
                        'impuesto_asumido_emisor' => $line->imp_asum_emisor,
                        'impuesto_neto'     => $line->impuesto_neto,
                        'monto_total_linea' => $line->monto_total_linea,
                        'documento_id'      => $documentId
                    ];
                })->toArray();

                DB::table('documento_detalles')->insert($lines);
                
                return [
                    "doc_id" => $documentId,
                    "fecha" => $now->toAtomString(),
                    "clave" => $claveNumerica,
                    "comprobanteXml" => $signedXml,
                    "emisor" => $data['emisor'],
                    "receptor" => $data['receptor'] ?? null
                ];

            } catch (Throwable $e) {
                throw new Exception("No es posible agregar una factura en este momento. Intenta de nuevo. {$e->getMessage()} {$e->getLine()}");
            }
        });

        $response = $this->mh_client->sendDocument($result);

        if ($response->accepted())
        {
            DB::table('documentos')
                ->where('clave', $result['clave'])
                ->update([
                    'estado' => 'enviado'
                ]);
        } else {
            if ($response->hasHeader('X-Error-Cause')) {
                $errorCause = $response->header('X-Error-Cause');
            }

            DB::table('documentos')
                ->where('clave', $result['clave'])
                ->update([
                    'estado' => 'error',
                    'respuesta_error_hacienda' => $errorCause ?? null
                ]);
        }

        return [
            "clave"     => $result['clave'],
            "fecha"     => $result['fecha'],
            "enviado"   => $response->accepted() ?? false,
        ];
    }

    protected function formatDecimal(float $amount, int $decimals = 5): string
    {
        return number_format($amount,$decimals,".","");
    }

    public function getTipoCaByS($codigo) 
    {
        $primerDigito = (int) substr($codigo, 0, 1);

        if ($primerDigito >= 0 && $primerDigito <= 4) {
            return 'Unid';
        } elseif ($primerDigito >= 5 && $primerDigito <= 9) {
            return 'Sp';
        }
    }

    public function enrich(array $data): ElectronicDocument
    {
        $enrichedLines = [];

        $totalServGravados      = 0;
        $totalServExentos       = 0;
        $totalServExonerados    = 0;
        $totalServNoSujetos     = 0;
        $totalMercGravadas      = 0;
        $totalMercExentas       = 0;
        $totalMercExoneradas    = 0;
        $totalMercNoSujetas     = 0;

        $totalImpuestos         = 0;
        $totalDescuentos        = 0;
       
        $impuestoAsumEmisor     = 0;

        foreach ($data['lineas'] as $index => $linea) 
        {
            // RESUMEN
            $montoTotal     = $this->formatDecimal($linea["cantidad"] * $linea["precio_unitario"], 5);
            $codigoCaByS    = $linea["codigo_cabys"];
            $tipoCaByS      = $this->getTipoCaByS($codigoCaByS);

            // DESCUENTOS
            $descuento              = $linea["descuentos"][0] ?? []; // SOLO UN DESCUENTO POR LINEA

            $codigoDescuento        = $descuento["codigo_descuento"] ?? null;
            $codigoDescuentoOtros   = $descuento["codigo_descuento_otros"] ?? null;
            $descuentoNaturaleza    = $descuento["naturaleza_descuento"] ?? null;
            $descuentoMonto         = $descuento['monto_descuento'] ?? 0;

            $descuentosArray = [];

            if (\is_array($descuento) && !empty($descuento)) 
            {
                $descuentosArray[] = DiscountData::from($descuento);
            }
            
            $descuentoTotal         = $this->formatDecimal($descuentoMonto);

            $totalDescuentos += $descuentoMonto;
            
            // SUBTOTAL
            $subTotal = $this->formatDecimal($montoTotal - $descuentoTotal);

            // IMPUESTOS
            $impuesto = $linea["impuestos"][0] ?? []; // SOLO UN IMPUESTO POR LINEA
            
            
            $codigoImpuesto         = $impuesto['codigo'] ?? null;
            $codigoImpuestoOtros    = $impuesto['codigo_impuesto_otros'] ?? null;
            $codigoTarifaIva        = $impuesto['codigo_tarifa_iva'] ?? null;

            $esNoSujeto = (
                $codigoImpuesto === CodigoImpuesto::IMPUESTO_AL_VALOR_AGREGADO->value 
                && $codigoTarifaIva === CodigoTarifaIVA::TARIFA_CERO->value
                || $codigoTarifaIva === CodigoTarifaIVA::TARIFA_CERO_SIN_DERECHO_CREDITO->value
            );

            $esExento = !$esNoSujeto && $codigoTarifaIva === CodigoTarifaIVA::TARIFA_EXENTA->value;
            
            if ($tipoCaByS == "Sp") {
                if ($esNoSujeto) {
                    $totalServNoSujetos += $montoTotal;
                } elseif ($esExento) {
                    $totalServExentos += $montoTotal;
                } else {
                    $totalServGravados += $montoTotal;
                }
            } else {
                if ($esNoSujeto) {
                    $totalMercNoSujetas += $montoTotal;
                } elseif ($esExento) {
                    $totalMercExentas += $montoTotal;
                } else {
                    $totalMercGravadas += $montoTotal;
                }
            }

            $impuestoAsumEmisorUno = 0;
            $indicadorBoniRegalia = ($linea["ind_boniregalia"] ?? false);
            $impuestoTarifa = (!$esNoSujeto && !$esExento) ? $this->formatDecimal($impuesto['tarifa'], 2) : 0;
            $base = $indicadorBoniRegalia ? $montoTotal : $subTotal;
            $impuestoMonto = $this->formatDecimal($base * ($impuestoTarifa / 100));

            $desgloseImpuestoKey = "{$codigoImpuesto}_{$codigoTarifaIva}";
			if (isset($totalesImpuestoPorCodigo[$desgloseImpuestoKey])) {
				$totalesImpuestoPorCodigo[$desgloseImpuestoKey]["total_monto_impuesto"] += !$indicadorBoniRegalia ? $impuestoMonto : 0;
			} else {
				$totalesImpuestoPorCodigo[$desgloseImpuestoKey] = [
					"codigo" => $codigoImpuesto,
					"codigo_tarifa_iva" => $impuesto['codigo_tarifa_iva'],
					"total_monto_impuesto" => !$indicadorBoniRegalia ? $impuestoMonto : 0
				];
		    }

            if ($indicadorBoniRegalia)
            {
                $impuestoAsumEmisorUno += $impuestoMonto;
                $impuestoAsumEmisor += $impuestoMonto;
            } else {
                $totalImpuestos += $impuestoMonto;
            }

            $impuestoNeto = $this->formatDecimal($impuestoMonto - $impuestoAsumEmisorUno);
            
            if (!\is_array($impuesto) || empty($impuesto)) 
            {
                if ($tipoCaByS == "Sp") 
                {
                    $totalServNoSujetos += $montoTotal;
                } else {
                    $totalMercNoSujetas += $montoTotal;
                }
            }

            $enrichedLines[] = LineaItemData::from([
                "numero_linea" => $index + 1,
                "codigo_cabys"      => $codigoCaByS,
                "cantidad"          => $this->formatDecimal($linea["cantidad"], 3),
                "unidad_medida"     => $tipoCaByS,
                "detalle"           => $linea["detalle"],
                "precio_unitario"   => $this->formatDecimal($linea["precio_unitario"]),
                "monto_total"       => $montoTotal,
                "cod_desc"          => $codigoDescuento,
                "cod_desc_otros"    => $codigoDescuentoOtros,
                "desc_razon"        => $descuentoNaturaleza,
                "desc_total"        => $descuentoTotal,
                "sub_total"         => $subTotal,
                "base_imponible"    => $subTotal, // SOLO VARIA CUANDO HAY IMPUESTOS ESPECIFICOS
                "cod_imp"           => $codigoImpuesto,
                "cod_imp_otros"     => $codigoImpuestoOtros,
                "cod_imp_iva"       => $codigoTarifaIva,
                "imp_tarifa"        => $impuestoTarifa,
                "imp_monto"         => $impuestoMonto,
                "imp_asum_emisor"   => $this->formatDecimal($impuestoAsumEmisorUno),
                "impuesto_neto"     => $impuestoNeto,
                "monto_total_linea" => $this->formatDecimal($subTotal + $impuestoNeto),
                "impuestos"     =>  [
                    TaxData::from([
                        "codigo_tarifa_iva" => $codigoTarifaIva,
                        "codigo" => $codigoImpuesto,
                        "tarifa" => $impuestoTarifa,
                        "monto" => $impuestoMonto
                    ])
                ],
                "descuentos" => $descuentosArray,
            ]);
            
        }

        $totalGravado       = $totalServGravados   + $totalMercGravadas;
        $totalExento        = $totalServExentos    + $totalMercExentas;
        $totalExonerado     = $totalServExonerados + $totalMercExoneradas;
        $totalNoSujeto      = $totalServNoSujetos  + $totalMercNoSujetas;
        $totalVenta         = $totalGravado + $totalExento + $totalExonerado + $totalNoSujeto;
        $totalVentaNeta     = $totalVenta - $totalDescuentos;
        $totalIVADevuelto   = 0;
        $totalImpAsumEmi    = $impuestoAsumEmisor;
        $totalComprobante   = $totalVentaNeta + $totalImpuestos;

        $resumen = [
            'total_serv_gravados'   => $totalServGravados,
            'total_serv_exentos'    => $totalServExentos,
            'total_serv_exonerados' => $totalServExonerados,
            'total_serv_no_sujetos' => $totalServNoSujetos,
            'total_merc_gravadas'   => $totalMercGravadas,
            'total_merc_exentas'    => $totalMercExentas,
            'total_merc_exoneradas' => $totalMercExoneradas,
            'total_merc_no_sujetas' => $totalMercNoSujetas,
            'total_gravado'         => $totalGravado,
            'total_exento'          => $totalExento,
            'total_exonerado'       => $totalExonerado,
            'total_no_sujeto'       => $totalNoSujeto,
            'total_venta'           => $totalVenta,
            'total_descuentos'      => $totalDescuentos,
            'total_venta_neta'      => $totalVentaNeta,
            'total_impuesto'        => $totalImpuestos,
            'total_imp_asum_emi'    => $totalImpAsumEmi,
            'total_iva_devuelto'    => $totalIVADevuelto,
            'total_comprobante'     => $totalComprobante,
        ];
        
        if (collect($data['receptor']['ubicacion'] ?? [])->filter()->isEmpty() || $data['tipo_documento'] == "04") {
            $data['receptor']['ubicacion'] = null;
        }

        if (collect($data['receptor'] ?? [])->filter()->isEmpty() || $data['tipo_documento'] == "04") {
            
            $data['receptor'] = null;
        }

        return ElectronicDocument::from([
            "cod_act_economica_emisor" => $data["cod_act_economica_emisor"],
            "cod_act_economica_receptor" => $data["cod_act_economica_receptor"] ?? null,
            "proveedor_sistema"     => config('services.mh.proveedor_cedula') ?? $data["emisor"]["numero_identificacion"],
            "nombre_comercial"      => $data["branch"]["nombre_comercial"] ?? null,
            "condicion_venta"       => $data['condicion_venta'],
            "condicion_venta_otros" => $data['condicion_venta_otros'] ?? null,
            "medio_pago"            => $data['medio_pago'],
            "plazo_credito"         => $data['plazo_credito'] ?? 0,
            "moneda"                => $data["moneda"],
            "tipo_cambio" => (function () use ($data) {
                $moneda = strtoupper(trim((string)($data['moneda'] ?? 'CRC')));
                $tipoDoc = (string)($data['tipo_documento'] ?? '');
            
                // ND=02, NC=03: SIEMPRE copiar moneda y TC del documento referenciado
                if (\in_array($tipoDoc, ['02','03'], true)) {
                    $refClave = (string)($data['info_referencia']['clave'] ?? '');
                    if ($refClave === '') {
                        throw new \Exception("NC/ND requiere info_referencia.clave.");
                    }
            
                    $ref = DB::table('documentos')->where('clave', $refClave)->first();
                    if (!$ref) {
                        throw new \Exception("Documento de referencia no encontrado: {$refClave}");
                    }
            
                    $monedaRef = strtoupper((string)($ref->moneda ?? 'CRC'));
                    $tcRef = (float)($ref->tipo_cambio ?? 1);
            
                    // Forzar consistencia: la moneda de NC/ND debe ser la misma del documento referenciado
                    if ($moneda !== $monedaRef) {
                        throw new \Exception("Moneda de NC/ND debe coincidir con la del documento referenciado ({$monedaRef}).");
                    }
            
                    return ($monedaRef === 'CRC') ? 1.0 : $tcRef;
                }
            
                // CRC: siempre 1
                if ($moneda === 'CRC') return 1.0;
            
                // Si viene en el payload, úsa (y valida > 0)
                $tc = $data['tipo_cambio'] ?? null;
                if ($tc !== null && (float)$tc > 0) return (float)$tc;
            
                // Si no vino, lo obtenemos del servicio (por moneda)
                $resp  = $this->tc_client->obtenerCambioActual(['bccr'], $moneda);
                $venta = $resp['venta'] ?? null;
            
                if ($venta === null || (float)$venta <= 0) {
                    throw new \Exception("Tipo de cambio requerido para moneda {$moneda} y no se pudo obtener automáticamente.");
                }
            
                return (float)$venta;
            })(),
            "emisor"                => $data['emisor'],
            "receptor"              => $data['receptor'] ?? null,
            "lineas"                => $enrichedLines,
            "resumen"               => $resumen,
            "desglose_impuestos"    => $totalesImpuestoPorCodigo,
            "info_referencia"       => $data['info_referencia'] ?? null
        ]);
    }

    private function withSiguienteNumero($sucursalId, $tipoDocumento, Closure $callback) 
    {
        // Hvd8KTAepfEN1qtK
        return DB::transaction(function () use ($sucursalId, $tipoDocumento, $callback) 
        {
            $sucursal = DB::table('sucursales')
                ->where('id', $sucursalId)
                ->lockForUpdate()
                ->first();

            if (!$sucursal) {
                throw new Exception("No se ha encontrado una sucursal para obtener el consecutivo");
            }

            // SE PUEDE HACER UN INDICADOR DE SUCURSAL ACTIVA O NO
            try {
                $nextNumber = $sucursal->$tipoDocumento + 1;
            } catch (Throwable $e) {
                throw new Exception('No se ha encontrado una numeracion consecutiva para este tipo de documento');
            }

            $result = $callback($sucursal, $nextNumber);

            DB::table('sucursales')
                ->where('id', $sucursalId)
                ->update([$tipoDocumento => $nextNumber]);

            return $result;
        }, 2);
    }
}