<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Concerns\DestroysCatalogItem;
use App\Http\Requests\StoreLaundrySaleProductRequest;
use App\Http\Requests\UpdateLaundrySaleProductRequest;
use App\Models\LaundrySaleCategory;
use App\Models\LaundrySaleProduct;
use App\Models\LoadType;
use App\Models\TipoServicio;
use App\Models\Etapa;
use App\Support\LaundrySaleProductTipoRules;
use App\Support\CatalogDeletionGuard;
use App\Support\CatalogCodeGenerator;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;

class LaundrySaleProductAdminController extends Controller
{
    use DestroysCatalogItem;

    public function index(Request $request): JsonResponse
    {
        $this->authorize('viewAny', LaundrySaleProduct::class);

        if ($request->filled('tipo')) {
            $request->validate([
                'tipo' => ['required', 'string', Rule::in([LaundrySaleProduct::TIPO_SERVICIO, LaundrySaleProduct::TIPO_PRODUCTO])],
            ]);
        }

        $rows = LaundrySaleProduct::query()
            ->when($request->filled('tipo'), fn ($q) => $q->where('tipo', $request->string('tipo')->toString()))
            ->with([
                'category:id,code,name,sort_order',
                'defaultEtapa:id,code,name',
                'defaultLoadType:id,code,name',
                'defaultTipoServicio:id,code,name,sort_order',
                'servicePrices:id,laundry_sale_product_id,etapa_id,amount',
            ])
            ->orderBy('laundry_sale_category_id')
            ->orderBy('sort_order')
            ->orderBy('id')
            ->get([
                'id', 'laundry_sale_category_id', 'code', 'name', 'tipo', 'description',
                'default_etapa_id', 'default_load_type_id', 'default_tipo_servicio_id',
                'billing_unit', 'unit_price', 'extra_cost', 'is_active', 'sort_order',
                'inventory_product_id', 'tracks_stock', 'stock_quantity',
            ]);

        $loadTypes = LoadType::query()->orderBy('sort_order')->get(['id', 'code', 'name', 'sort_order']);
        $etapas = Etapa::query()->orderBy('sort_order')->get(['id', 'code', 'name', 'sort_order']);
        $categories = LaundrySaleCategory::query()->orderBy('sort_order')->get(['id', 'code', 'name', 'sort_order', 'is_active']);

        return response()->json([
            'data' => $rows,
            'meta' => [
                'load_types' => $loadTypes,
                'etapas' => $etapas,
                'tipos_servicio' => TipoServicio::query()->orderBy('sort_order')->get(['id', 'code', 'name', 'sort_order']),
                'laundry_sale_categories' => $categories,
            ],
            'errors' => null,
        ], JsonResponse::HTTP_OK);
    }

    public function store(StoreLaundrySaleProductRequest $request): JsonResponse
    {
        $data = LaundrySaleProductTipoRules::normalizeForPersist($request->validated());

        $row = DB::transaction(function () use ($data, $request) {
            $tracks = LaundrySaleProductTipoRules::tracksStockAllowed(
                $data['tipo'] ?? LaundrySaleProduct::TIPO_SERVICIO,
                $data['billing_unit'],
                (bool) ($data['tracks_stock'] ?? false),
            );
            $tipo = $data['tipo'] ?? LaundrySaleProduct::TIPO_SERVICIO;
            $codePrefix = $tipo === LaundrySaleProduct::TIPO_PRODUCTO ? 'prod' : 'srv';
            $product = LaundrySaleProduct::query()->create([
                'laundry_sale_category_id' => $data['laundry_sale_category_id'],
                'code' => CatalogCodeGenerator::resolve(
                    $data['code'] ?? null,
                    $data['name'],
                    LaundrySaleProduct::class,
                    'code',
                    $codePrefix,
                    prefix: $codePrefix,
                ),
                'name' => $data['name'],
                'tipo' => $tipo,
                'description' => $data['description'] ?? null,
                'default_etapa_id' => $data['default_etapa_id'] ?? null,
                'default_load_type_id' => $data['default_load_type_id'] ?? null,
                'default_tipo_servicio_id' => $data['default_tipo_servicio_id'] ?? null,
                'billing_unit' => $data['billing_unit'],
                'unit_price' => (float) $data['unit_price'],
                'extra_cost' => (float) ($data['extra_cost'] ?? 0),
                'sort_order' => $data['sort_order'] ?? 0,
                'is_active' => $data['is_active'] ?? true,
                'inventory_product_id' => $data['inventory_product_id'] ?? null,
                'tracks_stock' => $tracks,
                'stock_quantity' => $tracks ? (int) ($data['stock_quantity'] ?? 0) : null,
            ]);
            if ($request->has('service_prices')) {
                $sp = $request->input('service_prices');
                $this->syncServicePrices($product, is_array($sp) ? $sp : []);
            }
            $product->load(['category', 'defaultEtapa', 'defaultLoadType', 'defaultTipoServicio', 'servicePrices']);

            return $product;
        });

        return response()->json([
            'data' => $row,
            'meta' => null,
            'errors' => null,
        ], JsonResponse::HTTP_CREATED);
    }

    public function update(UpdateLaundrySaleProductRequest $request, LaundrySaleProduct $laundrySaleProduct): JsonResponse
    {
        $row = DB::transaction(function () use ($request, $laundrySaleProduct) {
            $data = $request->validated();
            unset($data['service_prices']);
            $data = LaundrySaleProductTipoRules::normalizeForPersist($data, $laundrySaleProduct);
            $laundrySaleProduct->fill($data);
            $laundrySaleProduct->tracks_stock = LaundrySaleProductTipoRules::tracksStockAllowed(
                $laundrySaleProduct->tipo,
                $laundrySaleProduct->billing_unit,
                (bool) $laundrySaleProduct->tracks_stock,
            );
            if (! $laundrySaleProduct->tracks_stock) {
                $laundrySaleProduct->stock_quantity = null;
            }
            $laundrySaleProduct->save();
            if ($request->exists('service_prices')) {
                $sp = $request->input('service_prices');
                $this->syncServicePrices($laundrySaleProduct, is_array($sp) ? $sp : []);
            }
            $laundrySaleProduct->load(['category', 'defaultEtapa', 'defaultLoadType', 'defaultTipoServicio', 'servicePrices']);

            return $laundrySaleProduct;
        });

        return response()->json([
            'data' => $row,
            'meta' => null,
            'errors' => null,
        ], JsonResponse::HTTP_OK);
    }

    public function disable(LaundrySaleProduct $laundrySaleProduct): JsonResponse
    {
        $this->authorize('update', $laundrySaleProduct);
        $laundrySaleProduct->is_active = false;
        $laundrySaleProduct->save();
        $laundrySaleProduct->load(['category', 'defaultEtapa', 'defaultLoadType', 'defaultTipoServicio', 'servicePrices']);

        return response()->json([
            'data' => $laundrySaleProduct,
            'meta' => null,
            'errors' => null,
        ], JsonResponse::HTTP_OK);
    }

    public function destroy(LaundrySaleProduct $laundrySaleProduct): JsonResponse
    {
        $this->authorize('delete', $laundrySaleProduct);

        return $this->destroyCatalogItem(
            CatalogDeletionGuard::laundrySaleProduct($laundrySaleProduct),
            fn () => $laundrySaleProduct->delete(),
        );
    }

    /**
     * @param  array<int, array{etapa_id: int, amount: float|int|string}>  $rows
     */
    private function syncServicePrices(LaundrySaleProduct $product, array $rows): void
    {
        $product->servicePrices()->delete();
        foreach ($rows as $row) {
            if (! isset($row['etapa_id'])) {
                continue;
            }
            $product->servicePrices()->create([
                'etapa_id' => (int) $row['etapa_id'],
                'amount' => (float) ($row['amount'] ?? 0),
            ]);
        }
    }
}
