<?php

namespace App\Services;

use App\Models\FatteningLot;
use App\Models\FatteningLotFeedIntake;
use App\Models\FatteningLotFeedIntakeItem;
use App\Models\MaterialExpense;
use App\Models\MaterialType;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;

class FatteningLotFeedIntakeService
{
    public function __construct(
        private readonly FeedStockService $feedStockService,
    ) {}
    /**
     * @param  array{intake_date: string, notes?: string|null, items: list<array{material_type_id: int, quantity: float|int|string, unit_price?: float|int|string|null}>}  $data
     */
    public function create(FatteningLot $lot, array $data, ?int $userId = null): FatteningLotFeedIntake
    {
        $this->assertLotOpen($lot);

        return DB::transaction(function () use ($lot, $data, $userId) {
            $intakeDate = $data['intake_date'];
            $exists = FatteningLotFeedIntake::query()
                ->where('fattening_lot_id', $lot->id)
                ->whereDate('intake_date', $intakeDate)
                ->exists();

            if ($exists) {
                throw new InvalidArgumentException('Ya existe una ración registrada para esa fecha en este lote.');
            }

            $heads = $this->activeHeadsOnDate($lot, $intakeDate);
            $normalizedItems = $this->normalizeItems($data['items'] ?? []);

            $intake = FatteningLotFeedIntake::create([
                'fattening_lot_id' => $lot->id,
                'intake_date' => $intakeDate,
                'animals_count_snapshot' => $heads,
                'total_cost' => 0,
                'notes' => $data['notes'] ?? null,
                'user_id' => $userId,
            ]);

            $total = $this->syncItemsAndExpenses($intake, $lot, $normalizedItems, $userId);
            $intake->update(['total_cost' => $total]);

            return $intake->fresh(['items.materialType']);
        });
    }

    /**
     * @param  array{intake_date?: string, notes?: string|null, items: list<array{material_type_id: int, quantity: float|int|string, unit_price?: float|int|string|null}>}  $data
     */
    public function update(FatteningLotFeedIntake $intake, array $data, ?int $userId = null): FatteningLotFeedIntake
    {
        $lot = $intake->fatteningLot;
        $this->assertLotOpen($lot);

        return DB::transaction(function () use ($intake, $lot, $data, $userId) {
            $intakeDate = $data['intake_date'] ?? $intake->intake_date->toDateString();

            if ($intakeDate !== $intake->intake_date->toDateString()) {
                $exists = FatteningLotFeedIntake::query()
                    ->where('fattening_lot_id', $lot->id)
                    ->whereDate('intake_date', $intakeDate)
                    ->where('id', '!=', $intake->id)
                    ->exists();

                if ($exists) {
                    throw new InvalidArgumentException('Ya existe una ración registrada para esa fecha en este lote.');
                }
            }

            $heads = $this->activeHeadsOnDate($lot, $intakeDate);
            $normalizedItems = $this->normalizeItems($data['items'] ?? []);

            $this->deleteLinkedExpenses($intake);

            $intake->items()->delete();

            $intake->update([
                'intake_date' => $intakeDate,
                'animals_count_snapshot' => $heads,
                'notes' => array_key_exists('notes', $data) ? $data['notes'] : $intake->notes,
                'user_id' => $userId ?? $intake->user_id,
            ]);

            $total = $this->syncItemsAndExpenses($intake->fresh(), $lot, $normalizedItems, $userId ?? $intake->user_id);
            $intake->update(['total_cost' => $total]);

            return $intake->fresh(['items.materialType']);
        });
    }

    public function delete(FatteningLotFeedIntake $intake): void
    {
        $lot = $intake->fatteningLot;
        $this->assertLotOpen($lot);

        DB::transaction(function () use ($intake) {
            $this->deleteLinkedExpenses($intake);
            $intake->items()->delete();
            $intake->delete();
        });
    }

    /**
     * @return array{total_cost: float, total_quantity_kg: float, cost_per_head_estimate: float|null, intakes_count: int, animals_count: int}
     */
    public function investmentSummary(FatteningLot $lot): array
    {
        $intakes = FatteningLotFeedIntake::query()
            ->where('fattening_lot_id', $lot->id)
            ->with('items')
            ->get();

        $totalCost = (float) $intakes->sum('total_cost');
        $totalQty = (float) $intakes->flatMap->items->sum(fn ($item) => (float) $item->quantity);
        $heads = $lot->activeAnimals()->count();
        $costPerHead = $heads > 0 ? round($totalCost / $heads, 2) : null;

        return [
            'total_cost' => round($totalCost, 2),
            'total_quantity_kg' => round($totalQty, 3),
            'cost_per_head_estimate' => $costPerHead,
            'intakes_count' => $intakes->count(),
            'animals_count' => $heads,
        ];
    }

    private function assertLotOpen(FatteningLot $lot): void
    {
        if ($lot->status !== FatteningLot::STATUS_OPEN) {
            throw new InvalidArgumentException('Solo se pueden registrar raciones en lotes abiertos.');
        }
    }

    private function activeHeadsOnDate(FatteningLot $lot, string $date): int
    {
        return $lot->animals()
            ->whereDate('entry_date', '<=', $date)
            ->where(function ($q) use ($date) {
                $q->whereNull('removed_at')
                    ->orWhereDate('removed_at', '>', $date);
            })
            ->count();
    }

    /**
     * @param  list<array{material_type_id: int, quantity: float|int|string, unit_price?: float|int|string|null}>  $items
     * @return list<array{material_type_id: int, quantity: float, unit_price: float, line_cost: float}>
     */
    private function normalizeItems(array $items): array
    {
        if ($items === []) {
            throw new InvalidArgumentException('Debes indicar al menos un alimento en la ración.');
        }

        $normalized = [];

        foreach ($items as $row) {
            $typeId = (int) ($row['material_type_id'] ?? 0);
            $qty = (float) ($row['quantity'] ?? 0);

            if ($typeId <= 0 || $qty <= 0) {
                throw new InvalidArgumentException('Cada línea debe tener alimento y cantidad mayor a cero.');
            }

            $type = MaterialType::query()->find($typeId);
            if (! $type || $type->category !== 'feed_supplements' || ! $type->active) {
                throw new InvalidArgumentException('El alimento seleccionado no es válido en el catálogo de comida.');
            }

            $unitPrice = array_key_exists('unit_price', $row) && $row['unit_price'] !== null && $row['unit_price'] !== ''
                ? (float) $row['unit_price']
                : (float) ($type->default_unit_price ?? 0);

            if ($unitPrice < 0) {
                throw new InvalidArgumentException('El precio unitario no puede ser negativo.');
            }

            if ($unitPrice <= 0 && $type->default_unit_price === null) {
                throw new InvalidArgumentException("Define precio para «{$type->name}» en el catálogo o indícalo en la ración.");
            }

            $normalized[] = [
                'material_type_id' => $typeId,
                'quantity' => round($qty, 3),
                'unit_price' => round($unitPrice, 4),
                'line_cost' => round($qty * $unitPrice, 2),
            ];
        }

        return $normalized;
    }

    /**
     * @param  list<array{material_type_id: int, quantity: float, unit_price: float, line_cost: float}>  $items
     */
    private function syncItemsAndExpenses(
        FatteningLotFeedIntake $intake,
        FatteningLot $lot,
        array $items,
        ?int $userId
    ): float {
        $total = 0.0;

        foreach ($items as $row) {
            $type = MaterialType::query()->findOrFail($row['material_type_id']);
            $this->feedStockService->deductStock($type, $row['quantity']);

            $item = FatteningLotFeedIntakeItem::create([
                'fattening_lot_feed_intake_id' => $intake->id,
                'material_type_id' => $row['material_type_id'],
                'quantity' => $row['quantity'],
                'unit_price' => $row['unit_price'],
                'line_cost' => $row['line_cost'],
            ]);

            MaterialExpense::create([
                'material_type_id' => $row['material_type_id'],
                'expense_date' => $intake->intake_date,
                'quantity' => $row['quantity'],
                'unit_cost' => round($row['unit_price'], 2),
                'total_cost' => $row['line_cost'],
                'fattening_lot_id' => $lot->id,
                'notes' => 'Ración diaria de engorda',
                'user_id' => $userId,
                'fattening_lot_feed_intake_item_id' => $item->id,
            ]);

            $total += $row['line_cost'];
        }

        return round($total, 2);
    }

    private function deleteLinkedExpenses(FatteningLotFeedIntake $intake): void
    {
        $items = $intake->items()->with('materialType')->get();
        if ($items->isEmpty()) {
            return;
        }

        foreach ($items as $item) {
            if ($item->materialType) {
                $this->feedStockService->restoreStock($item->materialType, (float) $item->quantity);
            }
        }

        MaterialExpense::query()
            ->whereIn('fattening_lot_feed_intake_item_id', $items->pluck('id'))
            ->delete();
    }
}
