<?php

namespace App\Http\Controllers;

use App\Http\Requests\StoreFactoryProductionFormulaRequest;
use App\Http\Requests\UpdateFactoryProductionFormulaRequest;
use App\Models\FactoryProduct;
use App\Models\FactoryProductRecipe;
use App\Models\PurchaseProduct;
use App\Models\User;
use App\Support\NumberFormat;
use App\Support\ProductionFormulaAccess;
use App\Support\ProductionRecipe;
use Illuminate\Http\RedirectResponse;
use Inertia\Inertia;
use Inertia\Response;

class FactoryProductionFormulaController extends Controller
{
    public function index(): Response
    {
        $user = request()->user();

        $formulas = ProductionFormulaAccess::applyVisibleFormulaScope(
            FactoryProduct::query()
                ->whereHas('recipes', fn ($query) => $query->where('is_dough', false)),
            $user,
        )
            ->with([
                'unit:id,name',
                'recipes' => fn ($query) => $query
                    ->where('is_dough', false)
                    ->with([
                        'purchaseProduct.unit:id,name',
                        'purchaseProduct.baseUnit:id,name',
                    ]),
            ])
            ->orderByDesc('id')
            ->get()
            ->map(fn (FactoryProduct $product): array => $this->formulaPayload($product, $user));

        return Inertia::render('factory/formulas/index', [
            'formulas' => $formulas,
        ]);
    }

    public function create(): Response
    {
        return Inertia::render('factory/formulas/create', [
            'products' => $this->productsWithoutFormula(),
            'rawProducts' => PurchaseProduct::optionsForForm(),
        ]);
    }

    public function store(StoreFactoryProductionFormulaRequest $request): RedirectResponse
    {
        $validated = $request->validated();
        $product = FactoryProduct::query()->findOrFail($validated['factory_product_id']);

        $this->saveFormula($product, $validated);

        return redirect()
            ->route('factory.formulas.index')
            ->with('status', __('Production formula created.'));
    }

    public function edit(FactoryProduct $factoryProduct): Response
    {
        abort_unless(ProductionFormulaAccess::mayUpdate(request()->user(), $factoryProduct), 403);

        $factoryProduct->load([
            'unit:id,name',
            'recipes' => fn ($query) => $query
                ->where('is_dough', false)
                ->with([
                    'purchaseProduct.unit:id,name',
                    'purchaseProduct.baseUnit:id,name',
                ]),
        ]);

        return Inertia::render('factory/formulas/edit', [
            'formula' => $this->formulaPayload($factoryProduct, request()->user()),
            'products' => FactoryProduct::optionsForForm([$factoryProduct->id]),
            'rawProducts' => PurchaseProduct::optionsForForm(
                $factoryProduct->recipes->pluck('purchase_product_id')->all(),
            ),
            'recipeLines' => $factoryProduct->recipes
                ->map(fn (FactoryProductRecipe $recipe): array => $this->recipeLinePayload($recipe))
                ->values()
                ->all(),
        ]);
    }

    public function update(
        UpdateFactoryProductionFormulaRequest $request,
        FactoryProduct $factoryProduct,
    ): RedirectResponse {
        $validated = $request->validated();

        $this->saveFormula($factoryProduct, $validated);

        return redirect()
            ->route('factory.formulas.index')
            ->with('status', __('Production formula updated.'));
    }

    /**
     * @param  array{dough_quantity: float|int|string, total_production: float|int|string, recipe_lines: list<array{purchase_product_id: int, quantity: float|int|string, quantity_unit?: string}>}  $validated
     */
    private function saveFormula(FactoryProduct $product, array $validated): void
    {
        $userId = auth()->id();

        $product->fill([
            'dough_quantity' => 1,
            'quantity_per_dough' => $validated['total_production'],
        ]);

        if ($product->formula_created_by === null && $userId !== null) {
            $product->formula_created_by = $userId;
        }

        $product->save();

        ProductionRecipe::syncRecipe(
            $product->id,
            $validated['recipe_lines'],
        );
    }

    /**
     * @return array{id: int, dough_quantity: string, product_name: string, unit_name: string|null, total_production: string, can_update: bool, materials: list<array{product_name: string|null, quantity: string, unit_name: string|null}>}
     */
    private function formulaPayload(FactoryProduct $product, ?User $user = null): array
    {
        $user ??= request()->user();

        return [
            'id' => $product->id,
            'dough_quantity' => $this->formatQuantityDisplay((float) $product->dough_quantity),
            'product_name' => $product->name,
            'unit_name' => $product->unit?->name,
            'total_production' => $this->formatQuantityDisplay((float) $product->quantity_per_dough),
            'can_update' => ProductionFormulaAccess::mayUpdate($user, $product),
            'materials' => $product->recipes
                ->map(fn (FactoryProductRecipe $recipe): array => $this->materialPayload($recipe))
                ->values()
                ->all(),
        ];
    }

    /**
     * @return array{purchase_product_id: int, quantity: string, quantity_unit: 'base'|'units'}
     */
    private function recipeLinePayload(FactoryProductRecipe $recipe): array
    {
        $purchaseProduct = $recipe->purchaseProduct;
        $quantity = (float) $recipe->quantity;
        $usesBase = $purchaseProduct?->usesBaseUnit() ?? false;

        return [
            'purchase_product_id' => $recipe->purchase_product_id,
            'quantity' => $this->formatQuantityDisplay(
                $usesBase
                    ? $purchaseProduct->displayBaseQuantityForForm($quantity)
                    : $quantity,
            ),
            'quantity_unit' => $usesBase ? 'base' : 'units',
        ];
    }

    /**
     * @return array{product_name: string|null, quantity: string, unit_name: string|null}
     */
    private function materialPayload(FactoryProductRecipe $recipe): array
    {
        $purchaseProduct = $recipe->purchaseProduct;
        $quantity = (float) $recipe->quantity;
        $usesBase = $purchaseProduct?->usesBaseUnit() ?? false;

        return [
            'product_name' => $purchaseProduct?->name,
            'quantity' => $this->formatQuantityDisplay(
                $usesBase
                    ? $purchaseProduct->displayBaseQuantityForForm($quantity)
                    : $quantity,
            ),
            'unit_name' => $usesBase
                ? $purchaseProduct?->baseUnit?->name
                : $purchaseProduct?->unit?->name,
        ];
    }

    private function formatQuantityDisplay(float $value): string
    {
        return NumberFormat::quantity($value);
    }

    /**
     * @return list<array{id: int, name: string, unit_name: string|null, is_active: bool}>
     */
    private function productsWithoutFormula(): array
    {
        return FactoryProduct::query()
            ->with('unit:id,name')
            ->where('is_active', true)
            ->whereDoesntHave('recipes', fn ($query) => $query->where('is_dough', false))
            ->orderByDesc('id')
            ->get(['id', 'name', 'factory_unit_id', 'is_active'])
            ->map(fn (FactoryProduct $product): array => [
                'id' => $product->id,
                'name' => $product->name,
                'unit_name' => $product->unit?->name,
                'is_active' => $product->is_active,
            ])
            ->values()
            ->all();
    }
}
