<?php

namespace App\Services;

use App\Models\FatteningLot;
use App\Models\FatteningLotAnimal;
use App\Models\WeightRecord;
use Carbon\Carbon;

class RanchOpenLotsProjectionService
{
    public function __construct(
        private readonly FatteningLotService $fatteningLotService,
    ) {}

    /**
     * @return array<string, mixed>
     */
    public function generate(): array
    {
        $lots = FatteningLot::query()
            ->open()
            ->with(['activeAnimals.cattle'])
            ->orderBy('entry_date')
            ->get();

        $rows = [];
        foreach ($lots as $lot) {
            $rows[] = $this->rowForLot($lot);
        }

        $belowTarget = array_values(array_filter($rows, fn ($r) => $r['meets_target'] === false));
        usort($rows, function ($a, $b) {
            if ($a['meets_target'] === false && $b['meets_target'] !== false) {
                return -1;
            }
            if ($b['meets_target'] === false && $a['meets_target'] !== false) {
                return 1;
            }

            return ($b['projected_net_profit'] ?? 0) <=> ($a['projected_net_profit'] ?? 0);
        });

        foreach ($rows as $i => &$row) {
            $row['rank'] = $i + 1;
        }
        unset($row);

        return [
            'generated_at' => Carbon::now()->toIso8601String(),
            'default_daily_gain_kg' => $this->defaultDailyGain(),
            'lots_count' => count($rows),
            'below_target_count' => count($belowTarget),
            'lots' => $rows,
        ];
    }

    public function countBelowTarget(): int
    {
        return $this->generate()['below_target_count'];
    }

    /**
     * Proyección detallada de un lote (peso al cierre del periodo planificado).
     *
     * @return array<string, mixed>
     */
    public function projectionForLot(FatteningLot $lot): array
    {
        $relation = $lot->status === FatteningLot::STATUS_OPEN ? 'activeAnimals.cattle' : 'animals.cattle';
        $lot->load([$relation]);
        $projection = $this->buildProjection($lot);
        $preview = $this->fatteningLotService->previewClose($lot, $projection['exit_weights']);

        return array_merge($preview, [
            'planned_exit_date' => $lot->plannedExitDate()->toDateString(),
            'days_remaining' => $lot->daysRemaining(),
            'animals' => $projection['animals'],
            'basis_summary' => $projection['basis_summary'],
        ]);
    }

    /**
     * @return array<string, mixed>
     */
    private function rowForLot(FatteningLot $lot): array
    {
        $evaluation = $this->fatteningLotService->evaluateLot($lot);
        $projection = $this->buildProjection($lot);
        $preview = $this->fatteningLotService->previewClose($lot, $projection['exit_weights']);

        $projectedGain = (float) ($preview['projected_weight_gain_kg'] ?? 0);
        $feedCost = (float) ($preview['projected_feed_cost'] ?? 0);
        $conversion = $projectedGain > 0 ? round($feedCost / $projectedGain, 4) : null;

        return [
            'lot_id' => $lot->id,
            'lot_name' => $lot->name,
            'entry_date' => $lot->entry_date->toDateString(),
            'days_in_confinement' => $evaluation['days_in_confinement'],
            'animals_count' => $lot->activeAnimals->count(),
            'target_net_profit' => $lot->target_net_profit !== null ? (float) $lot->target_net_profit : null,
            'target_price_per_kg' => $lot->target_price_per_kg !== null
                ? (float) $lot->target_price_per_kg
                : (float) config('ranch.fattening.default_price_per_kg_projection', 45),
            'current_feed_cost' => $evaluation['total_feed_cost'],
            'current_weight_gain_kg' => $evaluation['total_weight_gain_kg'],
            'projected_income' => $preview['projected_income'],
            'projected_feed_cost' => $preview['projected_feed_cost'],
            'projected_health_cost' => $preview['projected_health_cost'],
            'projected_total_cost' => $preview['projected_total_cost'],
            'projected_net_profit' => $preview['projected_net_profit'],
            'projected_weight_gain_kg' => $preview['projected_weight_gain_kg'],
            'projected_feed_conversion' => $conversion,
            'profit_per_kg_gained' => $preview['profit_per_kg_gained'],
            'price_per_kg_used' => $preview['price_per_kg_used'],
            'meets_target' => $preview['meets_target'],
            'projection_basis' => $projection['basis_summary'],
            'animals' => $projection['animals'],
        ];
    }

    /**
     * @return array{
     *   exit_weights: array<int, float>,
     *   animals: list<array<string, mixed>>,
     *   basis_summary: array{weighed: int, gdp_estimate: int, default_estimate: int}
     * }
     */
    private function buildProjection(FatteningLot $lot): array
    {
        $exitWeights = [];
        $animals = [];
        $basis = ['weighed' => 0, 'gdp_estimate' => 0, 'default_estimate' => 0];

        $assignments = $lot->status === FatteningLot::STATUS_OPEN
            ? $lot->activeAnimals
            : $lot->animals;

        foreach ($assignments as $assignment) {
            $detail = $this->projectedExitFor($assignment, $lot);
            $exitWeights[$assignment->cattle_id] = $detail['exit_weight_kg'];
            $basis[$detail['basis']]++;
            $animals[] = array_merge($detail, [
                'cattle_id' => $assignment->cattle_id,
                'ear_tag' => $assignment->cattle?->ear_tag,
                'entry_weight_kg' => (float) $assignment->entry_weight_kg,
                'days_in_lot' => $assignment->daysInLot(),
            ]);
        }

        return [
            'exit_weights' => $exitWeights,
            'animals' => $animals,
            'basis_summary' => $basis,
        ];
    }

    /**
     * @return array{exit_weight_kg: float, basis: string, basis_label: string, last_weight_kg: float|null, daily_gain_used: float, projected_to_date: string}
     */
    private function projectedExitFor(FatteningLotAnimal $assignment, FatteningLot $lot): array
    {
        $entry = (float) $assignment->entry_weight_kg;
        $defaultGain = $this->defaultDailyGain();
        $targetDate = $lot->plannedExitDate();
        $today = Carbon::today();
        if ($targetDate->lessThan($today)) {
            $targetDate = $today;
        }

        $latest = WeightRecord::query()
            ->where('cattle_id', $assignment->cattle_id)
            ->orderByDesc('weighed_at')
            ->first();

        if ($latest && $latest->weighed_at->greaterThanOrEqualTo($assignment->entry_date)) {
            $lastWeight = (float) $latest->weight_kg;
            $gdp = $latest->daily_weight_gain !== null && (float) $latest->daily_weight_gain > 0
                ? (float) $latest->daily_weight_gain
                : $defaultGain;

            $daysForward = max(0, $latest->weighed_at->diffInDays($targetDate));
            $projected = $lastWeight + ($daysForward * $gdp);

            $basis = $daysForward === 0 ? 'weighed' : 'gdp_estimate';

            return [
                'exit_weight_kg' => round($projected, 2),
                'basis' => $basis,
                'basis_label' => $basis === 'weighed'
                    ? 'Último pesaje'
                    : 'Proyección GDP al fin del periodo',
                'last_weight_kg' => $lastWeight,
                'daily_gain_used' => $gdp,
                'projected_to_date' => $targetDate->toDateString(),
            ];
        }

        $days = max(1, $assignment->entry_date->diffInDays($targetDate));
        $gdp = ($latest?->daily_weight_gain !== null && (float) $latest->daily_weight_gain > 0)
            ? (float) $latest->daily_weight_gain
            : $defaultGain;

        $basis = ($latest?->daily_weight_gain !== null && (float) $latest->daily_weight_gain > 0)
            ? 'gdp_estimate'
            : 'default_estimate';

        return [
            'exit_weight_kg' => round($entry + ($days * $gdp), 2),
            'basis' => $basis,
            'basis_label' => $basis === 'gdp_estimate' ? 'Estimado por GDP' : 'Estimado estándar',
            'last_weight_kg' => $latest ? (float) $latest->weight_kg : null,
            'daily_gain_used' => $gdp,
            'projected_to_date' => $targetDate->toDateString(),
        ];
    }

    private function defaultDailyGain(): float
    {
        return (float) config('ranch.fattening.default_daily_gain_kg_projection', 0.8);
    }

    public function toCsv(array $report): string
    {
        $lines = [
            'Proyección lotes de engorda abiertos',
            'Generado,'.$report['generated_at'],
            'GDP diario por defecto (kg/día),'.$report['default_daily_gain_kg'],
            'Lotes abiertos,'.$report['lots_count'],
            'Por debajo de meta,'.$report['below_target_count'],
            '',
            'Resumen por lote',
            'Rank,Lote,Días,Animales,Alimento actual,Utilidad proyectada,Meta,¿Cumple?,Conversión proyectada,$/kg usado,Basis pesados,Basis GDP,Basis default',
        ];

        foreach ($report['lots'] as $row) {
            $basis = $row['projection_basis'] ?? [];
            $lines[] = implode(',', [
                $row['rank'] ?? '',
                $this->csvCell($row['lot_name']),
                $row['days_in_confinement'],
                $row['animals_count'],
                $row['current_feed_cost'],
                $row['projected_net_profit'],
                $row['target_net_profit'] ?? '',
                $row['meets_target'] === null ? '' : ($row['meets_target'] ? 'si' : 'no'),
                $row['projected_feed_conversion'] ?? '',
                $row['price_per_kg_used'],
                $basis['weighed'] ?? 0,
                $basis['gdp_estimate'] ?? 0,
                $basis['default_estimate'] ?? 0,
            ]);
        }

        $lines[] = '';
        $lines[] = 'Detalle por animal';
        $lines[] = 'Lote,Arete,Peso entrada,Días lote,Peso salida proyectado,Basis,GDP usado';

        foreach ($report['lots'] as $lot) {
            foreach ($lot['animals'] ?? [] as $animal) {
                $lines[] = implode(',', [
                    $this->csvCell($lot['lot_name']),
                    $this->csvCell($animal['ear_tag'] ?? ''),
                    $animal['entry_weight_kg'],
                    $animal['days_in_lot'],
                    $animal['exit_weight_kg'],
                    $animal['basis_label'] ?? '',
                    $animal['daily_gain_used'] ?? '',
                ]);
            }
        }

        return implode("\n", $lines);
    }

    private function csvCell(string $value): string
    {
        return '"'.str_replace('"', '""', $value).'"';
    }
}
