<?php

namespace App\Services;

use App\Models\CattleCalvingEvent;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;

class RanchCalvingReportService
{
    /**
     * @param  array<string, mixed>  $filters
     * @return array<string, mixed>
     */
    public function generate(array $filters = []): array
    {
        $from = isset($filters['from']) ? Carbon::parse($filters['from']) : null;
        $to = isset($filters['to']) ? Carbon::parse($filters['to']) : null;
        $motherId = isset($filters['mother_id']) ? (int) $filters['mother_id'] : null;
        $pastureId = isset($filters['pasture_id']) ? (int) $filters['pasture_id'] : null;

        $events = $this->baseQuery($from, $to, $motherId, $pastureId)
            ->orderByDesc('calving_date')
            ->orderByDesc('id')
            ->get();

        $rows = $events->map(fn (CattleCalvingEvent $event) => $this->rowFor($event))->all();

        $weights = collect($rows)->pluck('birth_weight_kg')->filter();
        $males = collect($rows)->where('calf_gender', 'male')->count();
        $females = collect($rows)->where('calf_gender', 'female')->count();

        return [
            'from' => $from?->toDateString(),
            'to' => $to?->toDateString(),
            'mother_id' => $motherId,
            'pasture_id' => $pastureId,
            'totals' => [
                'calvings_count' => count($rows),
                'male_calves' => $males,
                'female_calves' => $females,
                'avg_birth_weight_kg' => $weights->isNotEmpty()
                    ? round($weights->avg(), 2)
                    : null,
                'with_birth_weight' => $weights->count(),
            ],
            'by_month' => $this->groupByMonth($rows),
            'by_mother' => $this->groupByMother($rows),
            'events' => $rows,
        ];
    }

    /**
     * @param  array<string, mixed>  $report
     */
    public function toCsv(array $report): string
    {
        $lines = [
            'Reporte de partos',
            'Desde,'.($report['from'] ?? 'todos'),
            'Hasta,'.($report['to'] ?? 'todos'),
            'Total partos,'.($report['totals']['calvings_count'] ?? 0),
            'Machos,'.($report['totals']['male_calves'] ?? 0),
            'Hembras,'.($report['totals']['female_calves'] ?? 0),
            'Peso promedio nacer kg,'.($report['totals']['avg_birth_weight_kg'] ?? ''),
            '',
            'Por mes',
            'Mes,Partos,Machos,Hembras',
        ];

        foreach ($report['by_month'] ?? [] as $month) {
            $lines[] = implode(',', [
                $month['month'],
                $month['calvings_count'],
                $month['male_calves'],
                $month['female_calves'],
            ]);
        }

        $lines[] = '';
        $lines[] = 'Por vaca madre';
        $lines[] = 'Arete madre,Raza,Partos en periodo';
        foreach ($report['by_mother'] ?? [] as $mother) {
            $lines[] = implode(',', [
                $this->csvCell($mother['mother_ear_tag']),
                $this->csvCell($mother['mother_breed'] ?? ''),
                $mother['calvings_count'],
            ]);
        }

        $lines[] = '';
        $lines[] = 'Detalle';
        $lines[] = 'Fecha,Arete madre,Raza madre,Arete becerro,Género,Raza becerro,Peso nacer kg,Potrero,Notas';

        foreach ($report['events'] ?? [] as $row) {
            $lines[] = implode(',', [
                $row['calving_date'],
                $this->csvCell($row['mother_ear_tag']),
                $this->csvCell($row['mother_breed'] ?? ''),
                $this->csvCell($row['calf_ear_tag']),
                $row['calf_gender_label'],
                $this->csvCell($row['calf_breed'] ?? ''),
                $row['birth_weight_kg'] ?? '',
                $this->csvCell($row['pasture_location'] ?? ''),
                $this->csvCell($row['notes'] ?? ''),
            ]);
        }

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

    /**
     * @return Builder<CattleCalvingEvent>
     */
    private function baseQuery(
        ?Carbon $from,
        ?Carbon $to,
        ?int $motherId,
        ?int $pastureId,
    ): Builder {
        $query = CattleCalvingEvent::query()
            ->with([
                'mother:id,ear_tag,breed',
                'calf:id,ear_tag,gender,breed,status,pasture_id',
                'calf.pasture:id,name',
            ]);

        if ($from && $to) {
            $query->whereBetween('calving_date', [$from->toDateString(), $to->toDateString()]);
        } elseif ($from) {
            $query->whereDate('calving_date', '>=', $from);
        } elseif ($to) {
            $query->whereDate('calving_date', '<=', $to);
        }

        if ($motherId) {
            $query->where('mother_id', $motherId);
        }

        if ($pastureId) {
            $query->where(function (Builder $q) use ($pastureId) {
                $q->whereHas('calf', fn (Builder $c) => $c->where('pasture_id', $pastureId))
                    ->orWhereHas('mother', fn (Builder $m) => $m->where('pasture_id', $pastureId));
            });
        }

        return $query;
    }

    /**
     * @return array<string, mixed>
     */
    private function rowFor(CattleCalvingEvent $event): array
    {
        $genders = config('ranch.genders', []);

        return [
            'id' => $event->id,
            'calving_date' => $event->calving_date->toDateString(),
            'mother_id' => $event->mother_id,
            'mother_ear_tag' => $event->mother?->ear_tag,
            'mother_breed' => $event->mother?->breed,
            'calf_id' => $event->calf_id,
            'calf_ear_tag' => $event->calf?->ear_tag,
            'calf_gender' => $event->calf?->gender,
            'calf_gender_label' => $genders[$event->calf?->gender] ?? $event->calf?->gender,
            'calf_breed' => $event->calf?->breed,
            'calf_status' => $event->calf?->status,
            'birth_weight_kg' => $event->birth_weight_kg !== null ? (float) $event->birth_weight_kg : null,
            'pasture_location' => $event->pasture_location ?? $event->calf?->pasture?->name,
            'notes' => $event->notes,
        ];
    }

    /**
     * @param  list<array<string, mixed>>  $rows
     * @return list<array<string, mixed>>
     */
    private function groupByMonth(array $rows): array
    {
        $groups = [];
        foreach ($rows as $row) {
            $month = substr($row['calving_date'], 0, 7);
            if (! isset($groups[$month])) {
                $groups[$month] = [
                    'month' => $month,
                    'calvings_count' => 0,
                    'male_calves' => 0,
                    'female_calves' => 0,
                ];
            }
            $groups[$month]['calvings_count']++;
            if ($row['calf_gender'] === 'male') {
                $groups[$month]['male_calves']++;
            } elseif ($row['calf_gender'] === 'female') {
                $groups[$month]['female_calves']++;
            }
        }

        krsort($groups);

        return array_values($groups);
    }

    /**
     * @param  list<array<string, mixed>>  $rows
     * @return list<array<string, mixed>>
     */
    private function groupByMother(array $rows): array
    {
        $groups = [];
        foreach ($rows as $row) {
            $key = (string) $row['mother_id'];
            if (! isset($groups[$key])) {
                $groups[$key] = [
                    'mother_id' => $row['mother_id'],
                    'mother_ear_tag' => $row['mother_ear_tag'],
                    'mother_breed' => $row['mother_breed'],
                    'calvings_count' => 0,
                ];
            }
            $groups[$key]['calvings_count']++;
        }

        $list = array_values($groups);
        usort($list, fn ($a, $b) => $b['calvings_count'] <=> $a['calvings_count']);

        return $list;
    }

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