<?php
declare(strict_types=1);

namespace App\Modules\PoolManagement\Services;

use App\Core\App;
use App\Modules\PoolManagement\Models\PoolConfiguration;

final class PoolOccupancyOptimizer
{
    public static function suggestLanes(
        int $poolConfigId,
        string $date,
        string $startTime,
        string $endTime,
        string $bookingType,
        int $swimmerCount
    ): array {
        $config = PoolConfiguration::find($poolConfigId);
        if (!$config) {
            return [];
        }

        $lanes = $config->getLanes();
        $safetyRatios = $config->getSafetyRatios();
        $maxPerLane = $safetyRatios[$bookingType] ?? (int) $config->max_per_lane;

        $lanesNeeded = (int) ceil($swimmerCount / max(1, $maxPerLane));

        $availability = PoolGridService::checkLaneAvailability(
            $poolConfigId, $date, $startTime, $endTime,
            array_column($lanes, 'lane_number')
        );

        $bookedLaneNumbers = [];
        foreach ($availability['conflicts'] as $conflict) {
            $bookedLaneNumbers = array_merge($bookedLaneNumbers, $conflict['lanes']);
        }
        $bookedLaneNumbers = array_unique($bookedLaneNumbers);

        $availableLanes = array_filter($lanes, function ($lane) use ($bookedLaneNumbers) {
            return !in_array((int) $lane['lane_number'], $bookedLaneNumbers);
        });

        if (count($availableLanes) < $lanesNeeded) {
            return [
                'success'     => false,
                'message'     => 'لا تتوفر حارات كافية للعدد المطلوب',
                'available'   => count($availableLanes),
                'needed'      => $lanesNeeded,
                'suggestions' => [],
            ];
        }

        $suggestions = self::rankLaneCombinations(
            array_values($availableLanes),
            $lanesNeeded,
            $bookingType,
            $poolConfigId,
            $date,
            $startTime,
            $endTime
        );

        return [
            'success'       => true,
            'lanes_needed'  => $lanesNeeded,
            'max_per_lane'  => $maxPerLane,
            'suggestions'   => $suggestions,
        ];
    }

    public static function getUtilizationMetrics(int $poolConfigId, string $dateFrom, string $dateTo): array
    {
        $db = App::getInstance()->db();

        $config = PoolConfiguration::find($poolConfigId);
        if (!$config) {
            return [];
        }

        $totalLanes = count($config->getLanes());
        $hours = $config->getOperatingHours();
        $dailySlots = self::countDailySlots($hours['start'] ?? '06:00', $hours['end'] ?? '22:00', (int) ($hours['slot_minutes'] ?? 60));
        $totalDays = max(1, (int) ((strtotime($dateTo) - strtotime($dateFrom)) / 86400) + 1);
        $totalCapacity = $totalLanes * $dailySlots * $totalDays;

        $bookings = $db->selectOne(
            "SELECT COUNT(*) AS total_bookings,
                    SUM(JSON_LENGTH(lanes_json)) AS total_lane_slots,
                    SUM(expected_swimmers) AS total_swimmers,
                    SUM(total_amount) AS total_revenue
             FROM pool_bookings
             WHERE pool_config_id = ? AND booking_date BETWEEN ? AND ?
             AND status NOT IN ('cancelled', 'no_show')",
            [$poolConfigId, $dateFrom, $dateTo]
        );

        $occupancyPct = $totalCapacity > 0
            ? round(((int) ($bookings['total_lane_slots'] ?? 0)) / $totalCapacity * 100, 1)
            : 0;

        return [
            'total_bookings'   => (int) ($bookings['total_bookings'] ?? 0),
            'total_lane_slots' => (int) ($bookings['total_lane_slots'] ?? 0),
            'total_swimmers'   => (int) ($bookings['total_swimmers'] ?? 0),
            'total_revenue'    => (float) ($bookings['total_revenue'] ?? 0),
            'total_capacity'   => $totalCapacity,
            'occupancy_pct'    => $occupancyPct,
            'avg_swimmers_per_booking' => ($bookings['total_bookings'] ?? 0) > 0
                ? round((int) ($bookings['total_swimmers'] ?? 0) / (int) $bookings['total_bookings'], 1)
                : 0,
        ];
    }

    private static function rankLaneCombinations(array $availableLanes, int $needed, string $bookingType, int $poolConfigId, string $date, string $startTime, string $endTime): array
    {
        if ($needed === 1) {
            $suggestions = [];
            foreach ($availableLanes as $lane) {
                $suggestions[] = [
                    'lanes' => [(int) $lane['lane_number']],
                    'score' => self::scoreLane($lane, $bookingType),
                    'label' => $lane['label_ar'] ?? ('حارة ' . $lane['lane_number']),
                ];
            }
            usort($suggestions, fn($a, $b) => $b['score'] <=> $a['score']);
            return array_slice($suggestions, 0, 3);
        }

        $combinations = self::getAdjacentCombinations($availableLanes, $needed);

        $scored = [];
        foreach ($combinations as $combo) {
            $laneNums = array_map(fn($l) => (int) $l['lane_number'], $combo);
            $score = 0;

            $isAdjacent = true;
            sort($laneNums);
            for ($i = 1; $i < count($laneNums); $i++) {
                if ($laneNums[$i] - $laneNums[$i-1] !== 1) {
                    $isAdjacent = false;
                    break;
                }
            }
            if ($isAdjacent) $score += 20;

            foreach ($combo as $lane) {
                $score += self::scoreLane($lane, $bookingType);
            }

            $labels = array_map(fn($l) => $l['label_ar'] ?? ('حارة ' . $l['lane_number']), $combo);
            $scored[] = [
                'lanes' => $laneNums,
                'score' => $score,
                'label' => implode(' + ', $labels),
                'adjacent' => $isAdjacent,
            ];
        }

        usort($scored, fn($a, $b) => $b['score'] <=> $a['score']);
        return array_slice($scored, 0, 3);
    }

    private static function scoreLane(array $lane, string $bookingType): int
    {
        $score = 10;
        $width = (float) ($lane['width_meters'] ?? 2.5);
        if ($bookingType === 'lessons' && $width >= 2.5) $score += 5;
        if ($bookingType === 'competition' && $width >= 2.5) $score += 5;
        if ($bookingType === 'lap_swimming') $score += 3;
        return $score;
    }

    private static function getAdjacentCombinations(array $lanes, int $size): array
    {
        $n = count($lanes);
        if ($size > $n) return [];

        $combos = [];
        for ($i = 0; $i <= $n - $size; $i++) {
            $combos[] = array_slice($lanes, $i, $size);
        }

        return $combos;
    }

    private static function countDailySlots(string $start, string $end, int $slotMinutes): int
    {
        $startTs = strtotime($start);
        $endTs = strtotime($end);
        $diff = $endTs - $startTs;
        return max(1, (int) ($diff / ($slotMinutes * 60)));
    }
}
