<?php

namespace Daniv04\HaciendaPackage\Services\Validators;

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

    public function validate($validator): void
    {
        $this->validateUnidadMedidaSurtido($validator);
        $this->validateMontoTotalSurtido($validator);
        $this->validateMontoDescuentoSurtido($validator);
        $this->validateSubTotalSurtido($validator);
        $this->validateBaseImponibleSurtido($validator);
        $this->validateTarifaSurtido($validator);
        $this->validateDatosImpuestoEspecificoSurtido($validator);
        $this->validateMontoImpuestoSurtido($validator);
    }


    public function validateUnidadMedidaSurtido($validator)
    {
        $units = array_keys(config('catalogues.measurement_units', []));
        $lines = $this->data['DetalleServicio']['LineaDetalle'] ?? [];
        foreach ($lines as $index => $line) {

            $assortmentLine = $line['DetalleSurtido']['LineaDetalleSurtido'] ?? null;
            foreach ($assortmentLine as $assortmentIndex => $assortment) {
                $assortmentUnit = $assortment['UnidadMedidaSurtido'] ?? null;

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

    public function validateMontoTotalSurtido($validator)
    {

        $lines = $this->data['DetalleServicio']['LineaDetalle'] ?? [];
        foreach ($lines as $index => $line) {
            $assortmentLine = $line['DetalleSurtido']['LineaDetalleSurtido'] ?? null;
            foreach ($assortmentLine as $assortmentIndex => $assortment) {
                $amount = $assortment['CantidadSurtida'] ?? 0;
                $unitPrice = $assortment['PrecioUnitarioSurtido'] ?? 0;
                $total = $assortment['MontoTotalSurtido'] ?? 0;
                $calculatedTotal = round($amount * $unitPrice, 5);

                if (abs($calculatedTotal - $total) > 0.01) {
                    $validator->errors()->add(
                        "DetalleServicio.LineaDetalle.{$index}.DetalleSurtido.LineaDetalleSurtido.{$assortmentIndex}.MontoTotalSurtido",
                        "The field MontoTotalSurtido must be equal to the multiplication of CantidadSurtida × PrecioUnitarioSurtido. Expected: {$calculatedTotal}, Received: {$total}"
                    );
                }
            }
        }
    }

    public function validateMontoDescuentoSurtido($validator)
    {

        $lines = $this->data['DetalleServicio']['LineaDetalle'] ?? [];
        foreach ($lines as $index => $line) {
            $assortmentLine = $line['DetalleSurtido']['LineaDetalleSurtido'] ?? null;
            foreach ($assortmentLine as $assortmentIndex => $assortment) {
                $totalAmount = $assortment['MontoTotalSurtido'] ?? 0;
                $discounts = $assortment['DescuentoSurtido'] ?? [];
                foreach ($discounts as $discountIndex => $discount) {
                    $amount = $discount['MontoDescuentoSurtido'] ?? 0;
                    if ($amount < 0 || $amount > $totalAmount) {
                        $validator->errors()->add(
                            "DetalleServicio.LineaDetalle.{$index}.DetalleSurtido.LineaDetalleSurtido.{$assortmentIndex}.DescuentoSurtido.{$discountIndex}.MontoDescuentoSurtido",
                            "The discount amount must be a positive number and cannot exceed the total amount."
                        );
                    }
                }
            }
        }
    }

    public function validateSubTotalSurtido($validator)
    {

        $lines = $this->data['DetalleServicio']['LineaDetalle'] ?? [];
        foreach ($lines as $index => $line) {
            $assortmentLine = $line['DetalleSurtido']['LineaDetalleSurtido'] ?? null;
            foreach ($assortmentLine as $assortmentIndex => $assortment) {
                $totalAmount = $assortment['MontoTotalSurtido'] ?? 0;
                $discounts = $assortment['DescuentoSurtido'] ?? [];
                $subTotal = $assortment['SubTotalSurtido'] ?? 0;
                $totalDiscount = 0;
                foreach ($discounts as $discount) {
                    $totalDiscount += $discount['MontoDescuentoSurtido'] ?? 0;
                }

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

                if (abs($subTotal - $expectedSubTotal) > 0.01) {
                    $validator->errors()->add(
                        "DetalleServicio.LineaDetalle.{$index}.DetalleSurtido.LineaDetalleSurtido.{$assortmentIndex}.SubTotalSurtido",
                        "The SubTotalSurtido must be equal to (MontoTotalSurtido - TotalDescuentosSurtido). Expected: {$expectedSubTotal}, Received: {$subTotal}"
                    );
                }
            }
        }
    }

    public function validateBaseImponibleSurtido($validator)
    {
        $lines = $this->data['DetalleServicio']['LineaDetalle'] ?? [];
        foreach ($lines as $index => $line) 
        {
            $assortmentLine = $line['DetalleSurtido']['LineaDetalleSurtido'] ?? null;
            foreach ($assortmentLine as $assortmentIndex => $assortment) {
                $subTotal = $assortment['SubTotalSurtido'] ?? 0;
                $taxes = $assortment['ImpuestoSurtido'] ?? [];
                $taxTotal = 0;
                foreach ($taxes as $tax) {
                    if (isset($tax['Codigo']) && in_array($tax['Codigo'], ['02', '04', '05'])) {
                        $taxTotal += $tax['Monto'] ?? 0;
                    }
                }

                $expectedBaseImponible = round($subTotal + $taxTotal, 5);
                $baseImponible = $assortment['BaseImponibleSurtido'] ?? 0;

                if (abs($baseImponible - $expectedBaseImponible) > 0.01) {
                    $validator->errors()->add(
                        "DetalleServicio.LineaDetalle.{$index}.DetalleSurtido.LineaDetalleSurtido.{$assortmentIndex}.BaseImponibleSurtido",
                        "The field BaseImponibleSurtido must be equal to SubTotalSurtido plus the sum of taxes with codes 02, 04, or 05. Expected: {$expectedBaseImponible}, Received: {$baseImponible}"
                    );
                }
            }
        }
    }

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

        foreach ($lines as $lineIndex => $line) {

            $assortmentLine = $line['DetalleSurtido']['LineaDetalleSurtido'] ?? null;
            foreach ($assortmentLine as $assortmentIndex => $assortment) {
                $codigoTarifaIVA = $assortment['CodigoTarifaIVASurtido'] ?? null;
                $codigoImpuesto = $assortment['CodigoImpuestoSurtido'] ?? null;

                if (in_array($codigoImpuesto, ['01']) && empty($codigoTarifaIVA)) {
                    $validator->errors()->add(
                        "DetalleServicio.LineaDetalle.{$lineIndex}.DetalleSurtido.LineaDetalleSurtido.{$assortmentIndex}.CodigoTarifaIVASurtido",
                        "The field CodigoTarifaIVASurtido is required when CodigoImpuestoSurtido is 01 or 07."
                    );
                }
            }
        }
    }

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

        foreach ($lines as $lineIndex => $line) {
            $assortmentLines = $line['DetalleSurtido']['LineaDetalleSurtido'] ?? [];

            if (empty($assortmentLines) || !is_array($assortmentLines)) {
                continue;
            }

            foreach ($assortmentLines as $assortmentIndex => $assortment) {
                $taxes = $assortment['ImpuestoSurtido'] ?? [];

                if (empty($taxes) || !is_array($taxes)) {
                    continue;
                }

                foreach ($taxes as $taxIndex => $tax) {
                    $codigoImpuesto = $tax['CodigoImpuestoSurtido'] ?? null;
                    $tarifa = $tax['TarifaSurtido'] ?? null;

                    if (in_array($codigoImpuesto, ['01', '02', '99'], true) && ($tarifa === null || $tarifa === '')) {
                        $validator->errors()->add(
                            "DetalleServicio.LineaDetalle.{$lineIndex}.DetalleSurtido.LineaDetalleSurtido.{$assortmentIndex}.ImpuestoSurtido.{$taxIndex}.TarifaSurtido",
                            "The field TarifaSurtido is required when CodigoImpuestoSurtido is 01, 02, or 99."
                        );
                        continue;
                    }

                    if ($tarifa === null || $tarifa === '') {
                        continue;
                    }

                    if ($codigoImpuesto === '01') {
                        $codigoTarifaIVA = $tax['CodigoTarifaIVASurtido'] ?? null;
                        $expectedTariff = $this->tarifaConvertion($codigoTarifaIVA);

                        if ($expectedTariff !== null && abs(floatval($tarifa) - $expectedTariff) > 0.01) {
                            $validator->errors()->add(
                                "DetalleServicio.LineaDetalle.{$lineIndex}.DetalleSurtido.LineaDetalleSurtido.{$assortmentIndex}.ImpuestoSurtido.{$taxIndex}.TarifaSurtido",
                                "The TarifaSurtido value must be {$expectedTariff} when CodigoImpuestoSurtido is 01 and CodigoTarifaIVASurtido is {$codigoTarifaIVA}."
                            );
                        }
                    }

                    if (in_array($codigoImpuesto, ['02', '99'], true)) {
                        $tarifaFloat = floatval($tarifa);

                        if (abs($tarifaFloat - round($tarifaFloat)) > 0.00001) {
                            $validator->errors()->add(
                                "DetalleServicio.LineaDetalle.{$lineIndex}.DetalleSurtido.LineaDetalleSurtido.{$assortmentIndex}.ImpuestoSurtido.{$taxIndex}.TarifaSurtido",
                                "The TarifaSurtido value must be an integer when CodigoImpuestoSurtido is 02 or 99."
                            );
                        }
                    }
                }
            }
        }
    }

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

        foreach ($lines as $lineIndex => $line) {
            $assortmentLines = $line['DetalleSurtido']['LineaDetalleSurtido'] ?? [];

            if (empty($assortmentLines) || !is_array($assortmentLines)) {
                continue;
            }

            foreach ($assortmentLines as $assortmentIndex => $assortment) {
                $taxes = $assortment['ImpuestoSurtido'] ?? [];

                if (empty($taxes) || !is_array($taxes)) {
                    continue;
                }

                foreach ($taxes as $taxIndex => $tax) {
                    $codigoImpuesto = $tax['CodigoImpuestoSurtido'] ?? null;
                    $datosEspecificos = $tax['DatosImpuestoEspecificoSurtido'] ?? null;

                    if (in_array($codigoImpuesto, ['04', '05', '06'], true) && empty($datosEspecificos)) {
                        $validator->errors()->add(
                            "DetalleServicio.LineaDetalle.{$lineIndex}.DetalleSurtido.LineaDetalleSurtido.{$assortmentIndex}.ImpuestoSurtido.{$taxIndex}.DatosImpuestoEspecificoSurtido",
                            "The field DatosImpuestoEspecificoSurtido is required when CodigoImpuestoSurtido is 04, 05, or 06."
                        );
                    }
                }
            }
        }
    }

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

        foreach ($lines as $lineIndex => $line) {
            $assortmentLines = $line['DetalleSurtido']['LineaDetalleSurtido'] ?? [];

            if (empty($assortmentLines) || !is_array($assortmentLines)) {
                continue;
            }

            foreach ($assortmentLines as $assortmentIndex => $assortment) {
                $taxes = $assortment['ImpuestoSurtido'] ?? [];

                if (empty($taxes) || !is_array($taxes)) {
                    continue;
                }

                foreach ($taxes as $taxIndex => $tax) {
                    $taxCode = $tax['CodigoImpuestoSurtido'] ?? null;
                    $taxAmount = $tax['MontoImpuestoSurtido'] ?? null;

                    if ($taxAmount === null || $taxAmount === '') {
                        $validator->errors()->add(
                            "DetalleServicio.LineaDetalle.{$lineIndex}.DetalleSurtido.LineaDetalleSurtido.{$assortmentIndex}.ImpuestoSurtido.{$taxIndex}.MontoImpuestoSurtido",
                            "The field MontoImpuestoSurtido is required when ImpuestoSurtido is present."
                        );
                        continue;
                    }

                    $expectedMonto = $this->calculateExpectedMontoImpuestoSurtido($assortment, $tax);

                    if ($expectedMonto === null) {
                        continue;
                    }

                    $actualMonto = floatval($taxAmount);

                    if (abs($actualMonto - $expectedMonto) > 0.01) {
                        $validator->errors()->add(
                            "DetalleServicio.LineaDetalle.{$lineIndex}.DetalleSurtido.LineaDetalleSurtido.{$assortmentIndex}.ImpuestoSurtido.{$taxIndex}.MontoImpuestoSurtido",
                            "The MontoImpuestoSurtido value is incorrect. Expected: {$expectedMonto}, Received: {$actualMonto}"
                        );
                    }
                }
            }
        }
    }

    private function calculateExpectedMontoImpuestoSurtido(array $assortment, array $tax): ?float
    {
        $taxCode = $tax['CodigoImpuestoSurtido'] ?? null;

        return match ($taxCode) {
            '01' => $this->calculateIvaSurtido($assortment, $tax),
            '02' => $this->calculateSelectiveConsumptionSurtido($assortment, $tax),
            '04' => $this->calculateAlcoholicBeverageTaxSurtido($assortment, $tax),
            '05' => $this->calculateNonAlcoholicBeverageTaxSurtido($assortment, $tax),
            '06' => $this->calculateTobaccoTaxSurtido($assortment, $tax),
            default => null,
        };
    }

    private function calculateIvaSurtido(array $assortment, array $tax): float
    {
        $tariff = floatval($tax['TarifaSurtido'] ?? 0) / 100;
        $baseImponible = floatval($assortment['BaseImponibleSurtido'] ?? 0);

        if ($this->hasSpecialDiscountSurtido($assortment)) {
            $totalAmount = floatval($assortment['MontoTotalSurtido'] ?? 0);
            return round($totalAmount * $tariff, 5);
        }

        return round($baseImponible * $tariff, 5);
    }

    private function calculateSelectiveConsumptionSurtido(array $assortment, array $tax): float
    {
        $tariff = floatval($tax['TarifaSurtido'] ?? 0) / 100;
        $subTotal = floatval($assortment['SubTotalSurtido'] ?? 0);

        return round($subTotal * $tariff, 5);
    }

    private function calculateAlcoholicBeverageTaxSurtido(array $assortment, array $tax): float
    {
        $especificTaxes = $tax['DatosImpuestoEspecificoSurtido'] ?? [];
        $quantity = floatval($assortment['CantidadSurtida'] ?? 0);
        $proportion = floatval($especificTaxes['ProporcionSurtido'] ?? 0);
        $taxPerUnit = floatval($especificTaxes['ImpuestoUnidadSurtido'] ?? 0);

        return round($quantity * $proportion * $taxPerUnit, 5);
    }

    private function calculateNonAlcoholicBeverageTaxSurtido(array $assortment, array $tax): float
    {
        $specificTaxes = $tax['DatosImpuestoEspecificoSurtido'] ?? [];
        $quantity = floatval($assortment['CantidadSurtida'] ?? 0);
        $unitQuantity = floatval($specificTaxes['CantidadUnidadMedidaSurtido'] ?? 0);
        $taxPerUnit = floatval($specificTaxes['ImpuestoUnidadSurtido'] ?? 0);
        $consumptionVolume = floatval($specificTaxes['VolumenUnidadConsumoSurtido'] ?? 1);

        if ($consumptionVolume == 0.0) {
            return 0.0;
        }

        return round($quantity * $unitQuantity * ($taxPerUnit / $consumptionVolume), 5);
    }

    private function calculateTobaccoTaxSurtido(array $assortment, array $tax): float
    {
        $specificTaxes = $tax['DatosImpuestoEspecificoSurtido'] ?? [];
        $quantity = floatval($assortment['CantidadSurtida'] ?? 0);
        $unitQuantity = floatval($specificTaxes['CantidadUnidadMedidaSurtido'] ?? 0);
        $taxPerUnit = floatval($specificTaxes['ImpuestoUnidadSurtido'] ?? 0);

        return round($quantity * $unitQuantity * $taxPerUnit, 5);
    }

    private function hasSpecialDiscountSurtido(array $assortment): bool
    {
        $discounts = $assortment['DescuentoSurtido'] ?? [];

        foreach ($discounts as $discount) {
            $code = $discount['CodigoDescuentoSurtido'] ?? null;

            if (in_array($code, ['01', '02'], true)) {
                return true;
            }
        }

        return false;
    }

    private function tarifaConvertion($code): ?float
    {
        $tariffMapping = [
            '01' => 0,
            '02' => 1,
            '03' => 2,
            '04' => 4,
            '05' => 0,
            '06' => 4,
            '07' => 8,
            '08' => 13,
            '09' => 0.5,
            '10' => 0,
            '11' => 0
        ];

        return $tariffMapping[$code] ?? null;
    }

    

}
