<?php

namespace App\Support;

use App\Models\Batch;

class FeedingRecommendationEngine
{
    private static ?array $templateRows = null;

    public static function recommendForBatch(Batch $batch, ?string $date = null): array
    {
        $forDate = $date ?: now()->toDateString();
        $snapshot = is_array($batch->feeding_plan_snapshot) ? $batch->feeding_plan_snapshot : [];
        $rows = self::templateRows();

        $fishCount = BatchMetrics::liveFishCountAtDate($batch, $forDate);
        $avgWeightG = max(0.0, (float) ($batch->avg_weight_current_g ?? 0));
        $startMode = (string) ($snapshot['start_mode'] ?? 'por_peso');
        $mealsPerDay = max(1, (int) ($snapshot['meals_per_day'] ?? 2));
        $feedingTimes = self::normalizeTimes($mealsPerDay, is_array($snapshot['feeding_times'] ?? null) ? $snapshot['feeding_times'] : []);
        $startRowIndex = isset($snapshot['start_row_index']) ? (int) $snapshot['start_row_index'] : null;
        $startedAt = $forDate;
        if ($batch->started_at) {
            $startedAt = is_object($batch->started_at) && method_exists($batch->started_at, 'toDateString')
                ? $batch->started_at->toDateString()
                : (string) $batch->started_at;
        }
        $weekOffset = self::currentWeekIndex($startedAt, $forDate);

        $startRow = self::pickStartRow($rows, $avgWeightG, $startMode, $startRowIndex);
        $activeRow = self::activeRow($rows, $startRow['row_index'], $weekOffset);
        $stageMeta = self::stageMeta($rows, $activeRow);

        $dosePerFishG = (float) ($activeRow['dose_per_fish_g'] ?? 0);
        $dailyDoseG = max(0.0, $fishCount * $dosePerFishG);
        $portionPerMealG = $mealsPerDay > 0 ? $dailyDoseG / $mealsPerDay : 0;
        $gramsToOzFactor = $activeRow['grams_to_oz_factor'] ?? null;

        return [
            'date' => $forDate,
            'source' => 'server_excel_v1',
            'fish_count_effective' => $fishCount,
            'avg_weight_current_g' => $avgWeightG,
            'week_index' => (int) ($activeRow['week_index'] ?? 0),
            'week_date' => self::addDaysIso($startedAt, ((int) ($activeRow['week_index'] ?? 0)) * 7),
            'row_index' => (int) ($activeRow['row_index'] ?? 1),
            'feed_type' => (string) ($activeRow['feed_type'] ?? '0x0'),
            'stage_start_week_index' => (int) ($stageMeta['stage_start_week_index'] ?? 0),
            'stage_weeks_elapsed' => (int) ($stageMeta['stage_weeks_elapsed'] ?? 0),
            'weight_estimated_g' => (float) ($activeRow['weight_estimated_g'] ?? 0),
            'dose_per_fish_g' => $dosePerFishG,
            'daily_dose_g' => $dailyDoseG,
            'daily_dose_oz' => self::dailyDoseOz($dailyDoseG, $gramsToOzFactor),
            'grams_to_oz_factor' => $gramsToOzFactor === null ? null : (float) $gramsToOzFactor,
            'meals_per_day' => $mealsPerDay,
            'feeding_times' => $feedingTimes,
            'portion_per_meal_g' => $portionPerMealG,
        ];
    }

    private static function templateRows(): array
    {
        if (self::$templateRows !== null) {
            return self::$templateRows;
        }

        $root = dirname(base_path());
        $path = $root.'/frontend_app/src/assets/mock-api/ras/feeding-plans/template_excel_v1.json';
        if (! is_file($path)) {
            return self::$templateRows = self::fallbackRows();
        }

        $decoded = json_decode((string) file_get_contents($path), true);
        $rowsRaw = is_array($decoded['data']['rows'] ?? null) ? $decoded['data']['rows'] : [];
        $rows = array_values(array_filter(array_map(function ($row) {
            $weekIndex = (int) ($row['week_index'] ?? 0);
            $dosePerFish = (float) ($row['dose_per_fish_g'] ?? 0);
            $weightEstimated = (float) ($row['weight_estimated_g'] ?? 0);
            if ($dosePerFish <= 0 || $weightEstimated <= 0) {
                return null;
            }

            return [
                'row_index' => (int) ($row['row_index'] ?? 1),
                'week_index' => $weekIndex,
                'feed_type' => (string) ($row['feed_type'] ?? '0x0'),
                'weight_estimated_g' => $weightEstimated,
                'dose_per_fish_g' => $dosePerFish,
                'grams_to_oz_factor' => array_key_exists('grams_to_oz_factor', $row) && $row['grams_to_oz_factor'] !== null
                    ? (float) $row['grams_to_oz_factor']
                    : null,
            ];
        }, $rowsRaw)));

        usort($rows, fn ($a, $b) => ((int) $a['week_index']) <=> ((int) $b['week_index']));

        return self::$templateRows = $rows ?: self::fallbackRows();
    }

    private static function fallbackRows(): array
    {
        return [
            ['row_index' => 1, 'week_index' => 0, 'feed_type' => '0x0', 'weight_estimated_g' => 2.0, 'dose_per_fish_g' => 0.124, 'grams_to_oz_factor' => 16.0],
            ['row_index' => 2, 'week_index' => 1, 'feed_type' => '0x0', 'weight_estimated_g' => 4.0, 'dose_per_fish_g' => 0.166, 'grams_to_oz_factor' => 16.0],
            ['row_index' => 8, 'week_index' => 7, 'feed_type' => '2x2', 'weight_estimated_g' => 13.0, 'dose_per_fish_g' => 0.302, 'grams_to_oz_factor' => 15.0],
            ['row_index' => 18, 'week_index' => 17, 'feed_type' => '4x4', 'weight_estimated_g' => 106.0, 'dose_per_fish_g' => 2.044, 'grams_to_oz_factor' => 13.5],
            ['row_index' => 28, 'week_index' => 27, 'feed_type' => '6x6', 'weight_estimated_g' => 286.0, 'dose_per_fish_g' => 4.474, 'grams_to_oz_factor' => null],
        ];
    }

    private static function normalizeTimes(int $mealsPerDay, array $times): array
    {
        $suggested = ['06:00', '10:00', '14:00', '18:00', '20:00', '22:00'];
        $cleaned = array_values(array_filter($times, fn ($time) => is_string($time) && preg_match('/^\d{2}:\d{2}$/', $time)));
        $result = [];

        for ($i = 0; $i < $mealsPerDay; $i++) {
            $result[] = $cleaned[$i] ?? $suggested[$i] ?? '06:00';
        }

        return $result;
    }

    private static function currentWeekIndex(string $startDateIso, string $todayIso): int
    {
        $start = strtotime($startDateIso);
        $today = strtotime($todayIso);
        if (! $start || ! $today) {
            return 0;
        }

        $days = (int) floor(($today - $start) / 86400);

        return max(0, (int) floor($days / 7));
    }

    private static function pickStartRow(array $rows, float $avgWeightG, string $startMode, ?int $startRowIndex): array
    {
        if ($startRowIndex) {
            foreach ($rows as $row) {
                if ((int) $row['row_index'] === $startRowIndex) {
                    return $row;
                }
            }
        }

        if ($startMode === 'alevines') {
            return $rows[0];
        }

        foreach ($rows as $row) {
            if ((float) ($row['weight_estimated_g'] ?? 0) >= $avgWeightG) {
                return $row;
            }
        }

        return $rows[array_key_last($rows)];
    }

    private static function activeRow(array $rows, int $startRowIndex, int $weekOffset): array
    {
        $startPos = 0;
        foreach ($rows as $index => $row) {
            if ((int) ($row['row_index'] ?? 0) === $startRowIndex) {
                $startPos = $index;
                break;
            }
        }

        $targetIndex = min(count($rows) - 1, $startPos + max(0, $weekOffset));

        return $rows[$targetIndex] ?? $rows[0];
    }

    private static function stageMeta(array $rows, array $activeRow): array
    {
        $feedType = (string) ($activeRow['feed_type'] ?? '');
        $stageStartWeekIndex = (int) ($activeRow['week_index'] ?? 0);

        foreach ($rows as $row) {
            if ((string) ($row['feed_type'] ?? '') !== $feedType) {
                continue;
            }

            $candidate = (int) ($row['week_index'] ?? 0);
            if ($candidate <= (int) ($activeRow['week_index'] ?? 0)) {
                $stageStartWeekIndex = min($stageStartWeekIndex, $candidate);
            }
        }

        return [
            'stage_start_week_index' => $stageStartWeekIndex,
            'stage_weeks_elapsed' => max(0, (int) ($activeRow['week_index'] ?? 0) - $stageStartWeekIndex),
            'stage_start_weight_estimated_g' => (float) ($rows[array_search($stageStartWeekIndex, array_column($rows, 'week_index'))]['weight_estimated_g'] ?? $activeRow['weight_estimated_g'] ?? 0),
        ];
    }

    private static function dailyDoseOz(float $dailyDoseG, $gramsToOzFactor): ?float
    {
        if ($gramsToOzFactor === null) {
            return null;
        }

        $factor = (float) $gramsToOzFactor;
        if ($factor <= 0) {
            return null;
        }

        return round($dailyDoseG / $factor);
    }

    private static function addDaysIso(string $dateIso, int $days): string
    {
        $ts = strtotime($dateIso);
        if (! $ts) {
            return now()->toDateString();
        }

        return date('Y-m-d', strtotime(sprintf('%+d days', $days), $ts));
    }
}
