<?php

namespace App\Support;

use App\Models\LaundrySaleProduct;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;

/**
 * Validación de líneas de venta con catálogo TIBU (modo de cobro, tipo de prenda, peso).
 * No aplica a ítems que solo envían producto_id y cantidad (compatibilidad con POS actual).
 */
class OrdenLavanderiaDetalleLineValidator
{
    public const BILLING_BY_GARMENT = 'by_garment_type';

    public const BILLING_BY_WEIGHT = 'by_weight';

    /**
     * @param  array<int, array<string, mixed>>  $items
     */
    public static function validateItems(array $items): void
    {
        foreach ($items as $index => $item) {
            if (self::isInformationalItem($item)) {
                self::validateInformationalLine($item, $index);

                continue;
            }

            if (self::itemUsesSaleCatalog($item)) {
                self::validateSaleCatalogLine($item, $index);

                continue;
            }

            if (! self::itemUsesTibuCatalog($item)) {
                continue;
            }

            if (empty($item['producto_id'])) {
                if (empty($item['etapa_id'])) {
                    throw ValidationException::withMessages([
                        "items.{$index}.etapa_id" => ['El tipo de servicio es obligatorio para líneas sin producto de catálogo.'],
                    ]);
                }
                Validator::make(
                    ['etapa_id' => $item['etapa_id']],
                    ['etapa_id' => ['required', 'integer', 'exists:etapas,id']]
                )->validate();
            }

            $mode = self::resolveBillingMode($item);

            if ($mode !== self::BILLING_BY_GARMENT && $mode !== self::BILLING_BY_WEIGHT) {
                throw ValidationException::withMessages([
                    "items.{$index}.billing_mode" => ['Modo de cobro no válido.'],
                ]);
            }

            if ($mode === self::BILLING_BY_GARMENT) {
                if (empty($item['garment_type_id'])) {
                    throw ValidationException::withMessages([
                        "items.{$index}.garment_type_id" => ['El tipo de prenda es obligatorio cuando el cobro es por tipo de prenda.'],
                    ]);
                }
                Validator::make(
                    ['garment_type_id' => $item['garment_type_id']],
                    ['garment_type_id' => ['required', 'integer', 'exists:garment_types,id']]
                )->validate();
            }

            if ($mode === self::BILLING_BY_WEIGHT) {
                if (! isset($item['weight_kg']) || (float) $item['weight_kg'] <= 0) {
                    throw ValidationException::withMessages([
                        "items.{$index}.weight_kg" => ['El peso (kg) es obligatorio y debe ser mayor a cero cuando el cobro es por peso.'],
                    ]);
                }
            }

            if (isset($item['load_type_id']) && $item['load_type_id'] !== null && $item['load_type_id'] !== '') {
                Validator::make(
                    ['load_type_id' => $item['load_type_id']],
                    ['load_type_id' => ['integer', 'exists:load_types,id']]
                )->validate();
            }

            if (isset($item['observations']) && $item['observations'] !== null) {
                Validator::make(
                    ['observations' => $item['observations']],
                    ['observations' => ['string', 'max:2000']]
                )->validate();
            }
        }
    }

    /**
     * Línea con catálogo POS por producto (ADR-001), sin matriz prenda×servicio.
     *
     * @param  array<string, mixed>  $item
     */
    /**
     * Línea informativa: desglose de contenido del producto POS (sin cobro).
     *
     * @param  array<string, mixed>  $item
     */
    public static function isInformationalItem(array $item): bool
    {
        return ! empty($item['is_informational']);
    }

    public static function itemUsesSaleCatalog(array $item): bool
    {
        return empty($item['producto_id'])
            && ! empty($item['laundry_sale_product_id'])
            && ! self::isInformationalItem($item);
    }

    /**
     * @param  array<string, mixed>  $item
     */
    private static function validateInformationalLine(array $item, int $index): void
    {
        Validator::make(
            ['laundry_sale_product_id' => $item['laundry_sale_product_id'] ?? null],
            ['laundry_sale_product_id' => ['required', 'integer', 'exists:laundry_sale_products,id']]
        )->validate();

        $product = LaundrySaleProduct::query()
            ->whereKey((int) $item['laundry_sale_product_id'])
            ->where('is_active', true)
            ->first();

        if ($product === null) {
            throw ValidationException::withMessages([
                "items.{$index}.laundry_sale_product_id" => ['El producto del catálogo POS no existe o está inactivo.'],
            ]);
        }

        Validator::make(
            ['garment_type_id' => $item['garment_type_id'] ?? null],
            ['garment_type_id' => ['required', 'integer', 'exists:garment_types,id']]
        )->validate();

        $cant = (float) ($item['cantidad'] ?? 0);
        if ($cant <= 0) {
            throw ValidationException::withMessages([
                "items.{$index}.cantidad" => ['La cantidad debe ser mayor a cero.'],
            ]);
        }

        if (isset($item['observations']) && $item['observations'] !== null) {
            Validator::make(
                ['observations' => $item['observations']],
                ['observations' => ['string', 'max:2000']]
            )->validate();
        }
    }

    /**
     * @param  array<string, mixed>  $item
     */
    private static function itemUsesTibuCatalog(array $item): bool
    {
        if (! empty($item['producto_id'])) {
            return isset($item['billing_mode'])
                || isset($item['garment_type_id'])
                || isset($item['weight_kg']);
        }

        if (! empty($item['laundry_sale_product_id'])) {
            return false;
        }

        return true;
    }

    /**
     * @param  array<string, mixed>  $item
     */
    private static function validateSaleCatalogLine(array $item, int $index): void
    {
        if (! empty($item['garment_type_id'])) {
            throw ValidationException::withMessages([
                "items.{$index}.garment_type_id" => ['No combine tipo de prenda con producto del catálogo POS (laundry_sale_product_id).'],
            ]);
        }

        Validator::make(
            ['laundry_sale_product_id' => $item['laundry_sale_product_id'] ?? null],
            ['laundry_sale_product_id' => ['required', 'integer', 'exists:laundry_sale_products,id']]
        )->validate();

        $product = LaundrySaleProduct::query()
            ->whereKey((int) $item['laundry_sale_product_id'])
            ->where('is_active', true)
            ->first();

        if ($product === null) {
            throw ValidationException::withMessages([
                "items.{$index}.laundry_sale_product_id" => ['El producto del catálogo POS no existe o está inactivo.'],
            ]);
        }

        if ($product->tracks_stock && ! $product->manejaInventarioPos()) {
            throw ValidationException::withMessages([
                "items.{$index}.laundry_sale_product_id" => ['Producto del catálogo mal configurado: el inventario solo aplica a artículos tipo producto con cobro por pieza.'],
            ]);
        }

        $serviceId = $item['etapa_id'] ?? null;
        if (($serviceId === null || $serviceId === '') && $product->default_etapa_id === null) {
            throw ValidationException::withMessages([
                "items.{$index}.etapa_id" => ['Indique tipo de servicio o configure un servicio por defecto en el producto POS.'],
            ]);
        }

        if ($serviceId !== null && $serviceId !== '') {
            Validator::make(
                ['etapa_id' => $serviceId],
                ['etapa_id' => ['required', 'integer', 'exists:etapas,id']]
            )->validate();
        }

        $billing = isset($item['billing_mode']) && $item['billing_mode'] !== ''
            ? (string) $item['billing_mode']
            : ($product->billing_unit === LaundrySaleProduct::BILLING_UNIT_KG
                ? self::BILLING_BY_WEIGHT
                : self::BILLING_BY_GARMENT);

        if ($billing !== self::BILLING_BY_GARMENT && $billing !== self::BILLING_BY_WEIGHT) {
            throw ValidationException::withMessages([
                "items.{$index}.billing_mode" => ['Modo de cobro no válido.'],
            ]);
        }

        if ($billing === self::BILLING_BY_WEIGHT) {
            if (! isset($item['weight_kg']) || (float) $item['weight_kg'] <= 0) {
                throw ValidationException::withMessages([
                    "items.{$index}.weight_kg" => ['El peso (kg) es obligatorio y debe ser mayor a cero cuando el cobro es por peso.'],
                ]);
            }
        }

        if (isset($item['load_type_id']) && $item['load_type_id'] !== null && $item['load_type_id'] !== '') {
            Validator::make(
                ['load_type_id' => $item['load_type_id']],
                ['load_type_id' => ['integer', 'exists:load_types,id']]
            )->validate();
        }

        if ($product->manejaInventarioPos()) {
            if ($billing === self::BILLING_BY_WEIGHT) {
                throw ValidationException::withMessages([
                    "items.{$index}.laundry_sale_product_id" => ['Los productos con inventario del catálogo POS solo se venden por pieza (unidades).'],
                ]);
            }
            $cant = (float) ($item['cantidad'] ?? 0);
            $need = (int) round($cant, 0);
            if ($need <= 0) {
                throw ValidationException::withMessages([
                    "items.{$index}.cantidad" => ['La cantidad debe ser mayor a cero.'],
                ]);
            }
            if (! $product->tieneStockSuficiente((float) $need)) {
                throw ValidationException::withMessages([
                    "items.{$index}.cantidad" => ['Stock insuficiente para este producto.'],
                ]);
            }
        }

        if (isset($item['observations']) && $item['observations'] !== null) {
            Validator::make(
                ['observations' => $item['observations']],
                ['observations' => ['string', 'max:2000']]
            )->validate();
        }
    }

    /**
     * @param  array<string, mixed>  $item
     */
    private static function resolveBillingMode(array $item): string
    {
        if (! empty($item['billing_mode'])) {
            return $item['billing_mode'];
        }
        if (isset($item['weight_kg'])) {
            return self::BILLING_BY_WEIGHT;
        }

        return self::BILLING_BY_GARMENT;
    }

    /**
     * Misma lógica que resolveBillingMode (uso en OrdenLavanderiaService / C2).
     *
     * @param  array<string, mixed>  $item
     */
    public static function billingModeForItem(array $item): string
    {
        return self::resolveBillingMode($item);
    }
}
