<?php

namespace Daniv04\HaciendaPackage\Services\Validators;

class DetailLinesValidator
{
    public function __construct(
        private array $data
    ) {}

    public function validate($validator): void
    {
        $this->validateSubTotal($validator);
        $this->validateMontoTotalLinea($validator);
        $this->validateFormaFarmaceutica($validator);
        $this->validateUnidadMedida($validator);
        $this->validatePartidaArancelaria($validator);
        $this->validateCodigoCabys($validator);
        $this->validateNumeroVINoSerie($validator);
        $this->validateMontoTotal($validator);
        $this->validateMontoDescuento($validator);
        $this->validateBaseImponible($validator);
        $this->validateMontoExportacion($validator);
        $this->validateImpuestoAsumidoEmisorFabrica($validator);
        $this->validateImpuestoNeto($validator);
    }

    protected function validatePartidaArancelaria($validator)
    {
        $lines = $this->data['DetalleServicio']['LineaDetalle'];

        $documentType = $this->data['Clave']['TipoComprobante'];

        foreach ($lines as $index => $line) {
            $cabys = $line['CodigoCABYS'];

            if (!$this->isMerchandiseCabys($cabys)) {
                continue;
            }

            if ($documentType == '09' && !isset($line['PartidaArancelaria'])) {
                $validator->errors()->add(
                    "DetalleServicio.LineaDetalle.{$index}.PartidaArancelaria",
                    "The field Partida Arancelaria is required when the e-document is a Electronic Export Invoice and is a merchandise "
                );
            }
        }
    }

    protected function validateCodigoCabys($validator)
    {
        $lines = $this->data['DetalleServicio']['LineaDetalle'] ?? [];
        $documentType = $this->data['Clave']['TipoComprobante'] ?? null;
        $codeReference = $this->data['InformacionReferencia']['Codigo'] ?? null;


        if ($documentType == '02' || $documentType == '03' && $codeReference != '09' || $codeReference != '10') {
            foreach ($lines as $index => $line) {
                $cabys = $line['CodigoCABYS'] ?? null;
                if (!$cabys) {
                    $validator->errors()->add(
                        "DetalleServicio.LineaDetalle.{$index}.CodigoCABYS",
                        "The CodigoCABYS is required"
                    );
                }
            }
        }
    }

    protected function validateNumeroVINoSerie($validator)
    {
        $lines = $this->data['DetalleServicio']['LineaDetalle'] ?? [];

        foreach ($lines as $index => $line) {
            $cabys = $line['CodigoCABYS'] ?? null;

            if ($cabys && $this->isTransportCabys($cabys)) {
                $numeroVINoSerie = $line['NumeroVINoSerie'] ?? null;

                if (!$numeroVINoSerie) {
                    $validator->errors()->add(
                        "DetalleServicio.LineaDetalle.{$index}.NumeroVINoSerie",
                        "The field NumeroVINoSerie is required for transport services (CABYS codes starting with 64 or 65)."
                    );
                }
            }
        }
    }

    protected function validateUnidadMedida($validator)
    {
        $units = array_keys(config('catalogues.measurement_units', []));
        $lines = $this->data['DetalleServicio']['LineaDetalle'] ?? [];

        foreach ($lines as $index => $line) {
            $lineUnit = $line['UnidadMedida'] ?? null;

            if ($lineUnit && !in_array($lineUnit, $units)) {
                $validator->errors()->add(
                    "DetalleServicio.LineaDetalle.{$index}.UnidadMedida",
                    "The measurement unit '{$lineUnit}' is not valid."
                );
            }
        }
    }

    protected function validateFormaFarmaceutica($validator)
    {
        $formasFarmaceuticas = array_keys(config('catalogues.pharmaceutical_forms', []));
        $lines = $this->data['DetalleServicio']['LineaDetalle'] ?? [];

        foreach ($lines as $index => $line) {
            $formaFarmaceutica = $line['FormaFarmaceutica'] ?? null;

            if ($formaFarmaceutica && !in_array($formaFarmaceutica, $formasFarmaceuticas)) {
                $validator->errors()->add(
                    "DetalleServicio.LineaDetalle.{$index}.FormaFarmaceutica",
                    "The pharmaceutical form '{$formaFarmaceutica}' is not valid."
                );
            }
        }
    }

    public function validateSubTotal($validator)
    {
        $lines = $this->data['DetalleServicio']['LineaDetalle'] ?? [];
        foreach ($lines as $index => $line) {
            $totalAmount = $line['MontoTotal'] ?? 0;
            $discounts = $line['Descuento'] ?? [];
            $subTotal = $line['SubTotal'] ?? 0;

            $totalDiscount = 0;
            foreach ($discounts as $discount) {
                $totalDiscount += $discount['MontoDescuento'] ?? 0;
            }

            $expectedSubTotal = round($totalAmount - $totalDiscount, 5);

            if (abs($subTotal - $expectedSubTotal) > 0.01) {
                $validator->errors()->add(
                    "DetalleServicio.LineaDetalle.{$index}.SubTotal",
                    "The SubTotal must be equal to (Quantity × UnitPrice) - TotalDiscounts. Expected: {$expectedSubTotal}, Received: {$subTotal}"
                );
            }
        }
    }


    protected function validateMontoTotal($validator)
    {
        $lines = $this->data['DetalleServicio']['LineaDetalle'] ?? [];
       $total = 0;
        $calculatedTotal = 0;
        foreach ($lines as $line) {
            $amount = $line['Cantidad'] ?? 0;
            $unitPrice = $line['PrecioUnitario'] ?? 0;
            $total += $line['MontoTotal'] ?? 0;
            $calculatedTotal += round($amount * $unitPrice, 5);

            if (abs($calculatedTotal - $total) > 0.01) {
                $validator->errors()->add(
                    "DetalleServicio.MontoTotal",
                    "The field MontoTotal must be equal to the multiplication of Quantity × UnitPrice for all lines. Expected: {$calculatedTotal}, Received: {$total}"
                );
            }
        }
    }

    protected function validateBaseImponible($validator)
    {
        $lines = $this->data['DetalleServicio']['LineaDetalle'] ?? [];

        foreach ($lines['Impuesto'] ?? [] as $index => $line) {
            $baseImponible = $line['BaseImponible'] ?? 0;
            $subTotal = $line['SubTotal'] ?? 0;
            $taxTotal = 0;
            $taxTotal += $tax['Monto'] ?? 0;
            foreach ($line['Impuesto'] ?? [] as $taxIndex => $tax) {
                if (isset($tax['Codigo']) && in_array($tax['Codigo'], ['02', '04', '05', '12'])) {
                    $taxTotal += $tax['Monto'] ?? 0;
                }
            }

            $expectedBaseImponible = round($subTotal + $taxTotal, 5);
            if (abs($baseImponible - $expectedBaseImponible) > 0.01) {
                $validator->errors()->add(
                    "DetalleServicio.LineaDetalle.{$index}.BaseImponible",
                    "The field BaseImponible must be equal to SubTotal plus the sum of taxes with codes 02, 04, 05, or 12. Expected: {$expectedBaseImponible}, Received: {$baseImponible}"
                );
            }
        }
    }

    protected function validateMontoExportacion($validator)
    {
        $lines = $this->data['DetalleServicio']['LineaDetalle'] ?? [];

        foreach ($lines as $index => $line) {
            if ($line['CodigoCABYS'] && !$this->isMerchandiseCabys($line['CodigoCABYS'])) {
                continue;
            }
            $montoExportacion = $line['MontoExportacion'] ?? 0;
            $totalAmount = $line['MontoTotal'] ?? 0;

            if ($montoExportacion < 0 || $montoExportacion > $totalAmount) {
                $validator->errors()->add(
                    "DetalleServicio.LineaDetalle.{$index}.MontoExportacion",
                    "The field MontoExportacion must be a positive number and cannot exceed the total amount."
                );
            }
        }
    }




    //----------------------Discount validations
    private function validateMontoDescuento($validator)
    {
        $lines = $this->data['DetalleServicio']['LineaDetalle'] ?? [];

        foreach ($lines as $index => $line) {
            $discounts = $line['Descuento'] ?? [];
            $totalAmount = $line['MontoTotal'] ?? 0;

            foreach ($discounts as $discountIndex => $discount) {
                $amount = $discount['MontoDescuento'] ?? 0;
                if ($amount < 0 || $amount > $totalAmount) {
                    $validator->errors()->add(
                        "DetalleServicio.LineaDetalle.{$index}.Descuento.{$discountIndex}.MontoDescuento",
                        "The discount amount must be a positive number and cannot exceed the total amount."
                    );
                }
            }
        }
    }

    //----------------------Assumed Tax validation
    private function validateImpuestoAsumidoEmisorFabrica($validator)
    {
        $lines = $this->data['DetalleServicio']['LineaDetalle'] ?? [];

        foreach ($lines as $lineIndex => $line) {
            $declaredAssumedTax = $line['ImpuestoAsumidoEmisorFabrica'] ?? null;
            $discounts = $line['Descuento'] ?? [];
            $factoryVATCharged = $line['IVACobradoFabrica'] ?? null;
            $taxes = $line['Impuesto'] ?? [];

            $hasSpecialDiscounts = $this->hasDiscountCodes($discounts, ['01', '03']);            // Verificar si hay impuestos específicos (03, 04, 05, 06, 08)
            $hasSpecificTaxes = $this->hasSpecificTaxCodes($taxes, ['03', '04', '05', '06', '08']);

            $hasIVAFabrica = ($factoryVATCharged !== null);

            $isRequired = $hasSpecialDiscounts || $hasSpecificTaxes || $hasIVAFabrica;

            if ($isRequired && ($declaredAssumedTax === null || $declaredAssumedTax === '')) {
                $validator->errors()->add(
                    "DetalleServicio.LineaDetalle.{$lineIndex}.ImpuestoAsumidoEmisorFabrica",
                    "The field ImpuestoAsumidoEmisorFabrica is required when using discount codes 01/03, specific taxes (03,04,05,06,08), or IVACobradoFabrica."
                );
                continue;
            }

            if (!$isRequired && $declaredAssumedTax === null) {
                continue;
            }

            $expectedValue = 0.0;

            foreach ($taxes as $tax) {
                $taxCode = $tax['Codigo'] ?? null;
                $taxAmount = floatval($tax['Monto'] ?? 0);
                $exemptionAmount = floatval($tax['Exoneracion']['MontoExoneracion'] ?? 0);

                //Include the tax in the calculation if:
                // 1. There are special discounts (01 or 03)
                // 2. Or it's a specific tax (03, 04, 05, 06, 08)
                // 3. Or there is IVACobradoFabr
                $shouldInclude = false;

                if ($hasSpecialDiscounts && in_array($taxCode, ['01', '07'])) {

                    $shouldInclude = true;
                } elseif (in_array($taxCode, ['03', '04', '05', '06', '08'])) {

                    $shouldInclude = true;
                } elseif ($hasIVAFabrica && in_array($taxCode, ['01', '07'])) {

                    $shouldInclude = true;
                }

                if ($shouldInclude) {

                    $expectedValue += ($taxAmount - $exemptionAmount);
                }
            }

            $expectedValue = round($expectedValue, 5);
            $declaredAssumedTaxFloat = floatval($declaredAssumedTax ?? 0);

        
            if (!$isRequired && $declaredAssumedTaxFloat != 0) {
                $validator->errors()->add(
                    "DetalleServicio.LineaDetalle.{$lineIndex}.ImpuestoAsumidoEmisorFabrica",
                    "The field ImpuestoAsumidoEmisorFabrica must be 0 when no special conditions apply. Received: {$declaredAssumedTaxFloat}"
                );
                continue;
            }

        
            if ($isRequired && abs($declaredAssumedTaxFloat - $expectedValue) > 0.01) {
                $validator->errors()->add(
                    "DetalleServicio.LineaDetalle.{$lineIndex}.ImpuestoAsumidoEmisorFabrica",
                    "The field ImpuestoAsumidoEmisorFabrica is incorrect. Expected: {$expectedValue}, Received: {$declaredAssumedTaxFloat}"
                );
            }

          
            if ($declaredAssumedTaxFloat < 0) {
                $validator->errors()->add(
                    "DetalleServicio.LineaDetalle.{$lineIndex}.ImpuestoAsumidoEmisorFabrica",
                    "The field ImpuestoAsumidoEmisorFabrica must be greater than or equal to zero. Received: {$declaredAssumedTaxFloat}"
                );
            }
        }
    }

    private function hasDiscountCodes(array $discounts, array $codes): bool
    {
        foreach ($discounts as $discount) {
            $discountCode = $discount['CodigoDescuento'] ?? null;
            if (in_array($discountCode, $codes)) {
                return true;
            }
        }
        return false;
    }

    private function hasSpecificTaxCodes(array $taxes, array $codes): bool
    {
        foreach ($taxes as $tax) {
            $taxCode = $tax['Codigo'] ?? null;
            if (in_array($taxCode, $codes)) {
                return true;
            }
        } 
        return false;
    }

    //----------------------Tax Net Amount validation
    private function validateImpuestoNeto($validator)
    {
        $lines = $this->data['DetalleServicio']['LineaDetalle'] ?? [];

        foreach ($lines as $lineIndex => $line) {
            $netTaxDeclared  = floatval($line['ImpuestoNeto'] ?? 0);
            $taxes = $line['Impuesto'] ?? [];
            $assumedTax = floatval($line['ImpuestoAsumidoEmisorFabrica'] ?? 0);

            $totalNetTax = 0.0;

            foreach ($taxes as $tax) {
                $taxAmount = floatval($tax['Monto'] ?? 0);
                $exonerationAmount = floatval($tax['Exoneracion']['MontoExoneracion'] ?? 0);


                $totalNetTax += ($taxAmount - $exonerationAmount);
            }

            $expectedNetTax = round($totalNetTax - $assumedTax, 5);

            if (abs($netTaxDeclared - $expectedNetTax) > 0.01) {
                $validator->errors()->add(
                    "DetalleServicio.LineaDetalle.{$lineIndex}.ImpuestoNeto",
                    "The field ImpuestoNeto is incorrect. Expected: {$expectedNetTax}, Received: {$netTaxDeclared} (calculated as sum of (Tax Amount - Exoneration Amount) - Assumed Tax: {$assumedTax})"
                );
            }
        }
    }

    //----------------------MontoTotalLinea validations
    private function validateMontoTotalLinea($validator)
    {
        $lines = $this->data['DetalleServicio']['LineaDetalle'] ?? [];

        foreach ($lines as $index => $line) {
            $subTotal = $line['SubTotal'] ?? 0;
            $tax = $line['ImpuestoNeto'] ?? 0;
            $totalAmountLine = $line['MontoTotalLinea'] ?? 0;

            $totalAmount = round($subTotal + $tax, 5);

            if (abs($totalAmountLine - $totalAmount) > 0.01) {
                $validator->errors()->add(
                    "DetalleServicio.LineaDetalle.{$index}.MontoTotalLinea",
                    "The field MontoTotalLinea must be equal to SubTotal + ImpuestoNeto. Expected: {$totalAmount}, Received: {$totalAmountLine}"
                );
            }
        }
    }



    //-------------------Helpers
    private function isMerchandiseCabys(string $cabys): bool
    {
        return preg_match('/^[0-4]/', $cabys) === 1;
    }
    private function isTransportCabys(string $cabys): bool
    {
        return preg_match('/^(64|65)/', $cabys) === 1;
    }
}
