<?php

namespace App\Services;

use App\Models\LiftProduct;
use App\Models\LiftProductVariant;
use Illuminate\Validation\ValidationException;

/**
 * Server-side authority for which upgradations a catalog product/variant may carry and
 * what they cost (rules in config/product_upgradations.php). The create form renders
 * from forForm(); price() re-derives everything from the trusted rules on submit, so
 * client-side amounts are display-only.
 *
 * Every upgrade amount is final and GST-inclusive — no upgrade GST is ever added.
 */
class ProposalUpgradationService
{
    /** Hydraulic models: no Beige, no "Any RAL Colour". */
    public const HYDRAULIC_COLOUR_RESTRICTED_CODES = ['HOME-CIRCULAR-ICON', 'HOME-SQUARE-MATRIX'];

    /** Opposite/Adjacent Access is unavailable for these product configurations. */
    public const ACCESS_RESTRICTED_CONFIGURATIONS = ['HOME-CIRCULAR-ORBIT' => ['1000 mm', '1100 mm']];

    public const COLOUR_FINISH_TYPES = ['circular', 'square'];

    /** Free colour finishes, saved as zero-amount snapshots under the `colour_finish` key. */
    public const FREE_COLOURS = ['beige' => 'Beige (RAL 1019)', 'black' => 'Black (RAL 9005)'];

    /** Paid colour finishes, priced from their config item. */
    public const PAID_COLOUR_KEYS = ['ral_colour', 'ral_gold'];

    public const ACCESS_KEY = 'additional_access';

    /**
     * Eligible upgrade items for a product (and, when known, its selected variant).
     */
    public function itemsFor(LiftProduct $product, ?LiftProductVariant $variant = null): array
    {
        $catalog = config('product_upgradations', []);

        $items = match ($product->type) {
            'grooved_belt' => $catalog['grooved_belt'][$this->groovedPackageLevel($product)] ?? [],
            'stair_lift' => $catalog['stair_lift'][$product->product_code] ?? [],
            default => $catalog[$product->type] ?? [],
        };

        if (in_array($product->product_code, self::HYDRAULIC_COLOUR_RESTRICTED_CODES, true)) {
            $items = array_filter($items, fn (array $item) => $item['key'] !== 'ral_colour');
        }

        if ($variant && $this->accessRestrictedFor($product, $variant)) {
            $items = array_filter($items, fn (array $item) => $item['key'] !== self::ACCESS_KEY);
        }

        return array_values($items);
    }

    public function requiresColourFinish(LiftProduct $product): bool
    {
        return in_array($product->type, self::COLOUR_FINISH_TYPES, true);
    }

    /**
     * Allowed colour-finish choices as value => [label, amount].
     */
    public function colourChoicesFor(LiftProduct $product): array
    {
        if (! $this->requiresColourFinish($product)) {
            return [];
        }

        $choices = [];
        foreach (self::FREE_COLOURS as $value => $label) {
            $choices[$value] = ['label' => $label, 'amount' => 0.0];
        }

        if (in_array($product->product_code, self::HYDRAULIC_COLOUR_RESTRICTED_CODES, true)) {
            unset($choices['beige']);
        }

        foreach ($this->itemsFor($product) as $item) {
            if (in_array($item['key'], self::PAID_COLOUR_KEYS, true)) {
                $choices[$item['key']] = ['label' => $item['name'], 'amount' => (float) $item['amount']];
            }
        }

        return $choices;
    }

    /**
     * Everything the create form needs to render a product's upgrade panel, so the browser
     * never re-implements eligibility rules (only the variant-dependent access restriction).
     */
    public function forForm(LiftProduct $product): array
    {
        $items = array_values(array_filter(
            $this->itemsFor($product),
            fn (array $item) => ! in_array($item['key'], self::PAID_COLOUR_KEYS, true)
        ));

        return [
            'items' => $items,
            'colours' => collect($this->colourChoicesFor($product))
                ->map(fn ($choice, $value) => ['value' => $value] + $choice)
                ->values()->all(),
            'access_restricted_configurations' => self::ACCESS_RESTRICTED_CONFIGURATIONS[$product->product_code] ?? [],
        ];
    }

    /**
     * Prices the submitted selections against the trusted rules for this product/variant.
     * Ineligible or manipulated selections are rejected, never silently priced.
     *
     * @return array{selected: array<int, array>, total: float}
     *
     * @throws ValidationException
     */
    public function price(LiftProduct $product, LiftProductVariant $variant, array $input): array
    {
        $stops = (int) $variant->number_of_stops;
        $eligible = collect($this->itemsFor($product, $variant))->keyBy('key');
        $selected = [];
        $errors = [];

        $colour = $input['upgrade_colour_finish'] ?? null;
        $colourChoices = $this->colourChoicesFor($product);
        if ($this->requiresColourFinish($product) && blank($colour)) {
            $errors['upgrade_colour_finish'] = 'Please select a colour finish.';
        } elseif (filled($colour)) {
            if (! isset($colourChoices[$colour])) {
                $errors['upgrade_colour_finish'] = 'This colour finish is not available for the selected model.';
            } elseif (isset(self::FREE_COLOURS[$colour])) {
                $selected[] = $this->snapshot('colour_finish', self::FREE_COLOURS[$colour], 0, 1);
            } else {
                $selected[] = $this->snapshot($colour, $colourChoices[$colour]['label'], $colourChoices[$colour]['amount'], 1);
            }
        }

        $accessQuantity = $input['upgrade_access_quantity'] ?? 0;
        if (filled($accessQuantity) && (int) $accessQuantity > 0) {
            if (! $eligible->has(self::ACCESS_KEY)) {
                $errors['upgrade_access_quantity'] = 'Opposite or Adjacent Access is not available for the selected model.';
            } elseif ((int) $accessQuantity > $stops) {
                $errors['upgrade_access_quantity'] = 'Access quantity cannot exceed the selected number of stops.';
            } else {
                $item = $eligible->get(self::ACCESS_KEY);
                $selected[] = $this->snapshot($item['key'], $item['name'], (float) $item['amount'], (int) $accessQuantity);
            }
        }

        foreach ((array) ($input['upgrade_selections'] ?? []) as $key => $selection) {
            if (! is_array($selection) || ! filter_var($selection['selected'] ?? false, FILTER_VALIDATE_BOOLEAN)) {
                continue;
            }

            $field = "upgrade_selections.{$key}";
            $item = $eligible->get($key);

            // Colour and access have their own controls; anything else must be an eligible,
            // chargeable item for this exact product/variant.
            if (! $item || in_array($key, [...self::PAID_COLOUR_KEYS, self::ACCESS_KEY], true) || ($item['status'] ?? null) === 'included') {
                $errors[$field] = "The upgrade \"{$key}\" is not available for the selected model.";
                continue;
            }

            $unitPrice = (float) ($item['amount'] ?? 0);
            $quantity = 1;

            switch ($item['pricing'] ?? 'fixed') {
                case 'quantity':
                    $quantity = filter_var($selection['quantity'] ?? null, FILTER_VALIDATE_INT);
                    if ($quantity === false || $quantity < 1 || $quantity > $stops) {
                        $errors["{$field}.quantity"] = "Quantity for {$item['name']} must be between 1 and {$stops}.";
                        continue 2;
                    }
                    break;
                case 'per_landing':
                    $quantity = $stops;
                    break;
                case 'stops_price':
                    $unitPrice = (float) ($item['prices_by_stops'][$stops] ?? 0);
                    if ($unitPrice <= 0) {
                        $errors[$field] = "{$item['name']} is not available for {$stops} stops.";
                        continue 2;
                    }
                    break;
            }

            $selected[] = $this->snapshot($key, $item['name'], $unitPrice, $quantity);
        }

        if ($errors) {
            throw ValidationException::withMessages($errors);
        }

        return ['selected' => $selected, 'total' => (float) array_sum(array_column($selected, 'amount'))];
    }

    public function accessRestrictedFor(LiftProduct $product, LiftProductVariant $variant): bool
    {
        return in_array(
            trim((string) $variant->configuration),
            self::ACCESS_RESTRICTED_CONFIGURATIONS[$product->product_code] ?? [],
            true
        );
    }

    /**
     * Grooved-belt packages are Prime / Grande / Signature, identified by model name.
     */
    protected function groovedPackageLevel(LiftProduct $product): ?string
    {
        $name = strtolower((string) $product->name);

        foreach (['signature', 'grande', 'prime'] as $level) {
            if (str_contains($name, $level)) {
                return $level;
            }
        }

        return null;
    }

    protected function snapshot(string $key, string $name, float $unitPrice, int $quantity): array
    {
        $amount = round($unitPrice * $quantity, 2);

        return [
            'key' => $key,
            'name' => $name,
            'unit_price' => $unitPrice,
            'quantity' => $quantity,
            'base_amount' => $amount,
            'gst_percentage' => 0,
            'gst_amount' => 0,
            'amount' => $amount,
        ];
    }
}
