<?php
declare(strict_types=1);

namespace App\Modules\Tournaments\Services;

use App\Core\App;
use App\Core\EventBus;
use App\Modules\Tournaments\Models\Tournament;
use App\Modules\Tournaments\Models\TournamentParticipant;
use App\Modules\Tournaments\Models\TournamentMatch;

class TournamentService
{
    /**
     * Create a new tournament.
     */
    public static function createTournament(array $data): array
    {
        $tournament = Tournament::create($data);

        EventBus::dispatch('tournament.created', [
            'tournament_id' => (int) $tournament->id,
            'name'          => $data['name_ar'] ?? '',
            'discipline_id' => (int) ($data['discipline_id'] ?? 0),
        ]);

        return $tournament->toArray();
    }

    /**
     * Register a participant in a tournament.
     *
     * @param int $tournamentId
     * @param int $playerId
     * @param array|null $teamData ['team_name' => string, 'team_members_json' => array]
     * @return array The participant record
     * @throws \RuntimeException on validation failure
     */
    public static function registerParticipant(int $tournamentId, int $playerId, ?array $teamData = null): array
    {
        $db = App::getInstance()->db();

        // Load tournament
        $tournament = Tournament::findOrFail($tournamentId);

        // Check tournament status allows registration
        if (!in_array($tournament->status, ['registration_open'], true)) {
            throw new \RuntimeException('التسجيل في البطولة غير مفتوح حالياً');
        }

        // Check max_participants
        if ($tournament->max_participants !== null) {
            $currentCount = $db->selectOne(
                "SELECT COUNT(*) AS cnt FROM tournament_participants WHERE tournament_id = ? AND status NOT IN ('withdrawn', 'disqualified')",
                [$tournamentId]
            );
            if ((int) ($currentCount['cnt'] ?? 0) >= (int) $tournament->max_participants) {
                throw new \RuntimeException('البطولة ممتلئة - تم الوصول للحد الأقصى من المشاركين');
            }
        }

        // Check if player is already registered
        $existing = $db->selectOne(
            "SELECT id FROM tournament_participants WHERE tournament_id = ? AND player_id = ? AND status NOT IN ('withdrawn', 'disqualified')",
            [$tournamentId, $playerId]
        );
        if ($existing) {
            throw new \RuntimeException('اللاعب مسجل بالفعل في هذه البطولة');
        }

        // Validate player eligibility (age/gender)
        $player = $db->selectOne("SELECT date_of_birth, gender FROM players WHERE id = ?", [$playerId]);
        if (!$player) {
            throw new \RuntimeException('اللاعب غير موجود');
        }

        // Gender check
        if (!empty($tournament->gender) && $tournament->gender !== 'mixed') {
            if ($player['gender'] !== $tournament->gender) {
                throw new \RuntimeException('البطولة مقصورة على جنس ' . ($tournament->gender === 'male' ? 'ذكور' : 'إناث'));
            }
        }

        // Age group check
        if (!empty($tournament->age_group) && !empty($player['date_of_birth'])) {
            $dob = new \DateTime($player['date_of_birth']);
            $now = new \DateTime();
            $age = (int) $now->diff($dob)->y;

            // Parse age_group format like "8-12" or "under_16" or "18+"
            $ageGroup = $tournament->age_group;
            if (preg_match('/^(\d+)-(\d+)$/', $ageGroup, $matches)) {
                $minAge = (int) $matches[1];
                $maxAge = (int) $matches[2];
                if ($age < $minAge || $age > $maxAge) {
                    throw new \RuntimeException("البطولة مخصصة للأعمار من {$minAge} إلى {$maxAge} سنة");
                }
            } elseif (preg_match('/^under[_\s]?(\d+)$/i', $ageGroup, $matches)) {
                $maxAge = (int) $matches[1];
                if ($age >= $maxAge) {
                    throw new \RuntimeException("البطولة مخصصة لمن هم أقل من {$maxAge} سنة");
                }
            }
        }

        // Create participant record
        $participantData = [
            'tournament_id'     => $tournamentId,
            'player_id'         => $playerId,
            'team_name'         => $teamData['team_name'] ?? null,
            'team_members_json' => !empty($teamData['team_members_json']) ? json_encode($teamData['team_members_json'], JSON_UNESCAPED_UNICODE) : null,
            'registration_date' => date('Y-m-d H:i:s'),
            'status'            => 'registered',
            'points'            => 0,
            'wins'              => 0,
            'losses'            => 0,
            'draws'             => 0,
        ];

        $participant = TournamentParticipant::create($participantData);

        EventBus::dispatch('tournament.participant_registered', [
            'tournament_id'  => $tournamentId,
            'participant_id' => (int) $participant->id,
            'player_id'      => $playerId,
        ]);

        return $participant->toArray();
    }

    /**
     * Generate bracket/matches based on tournament format.
     */
    public static function generateBracket(int $tournamentId): array
    {
        $db = App::getInstance()->db();
        $tournament = Tournament::findOrFail($tournamentId);

        // Get confirmed/registered participants
        $participants = $db->select(
            "SELECT * FROM tournament_participants WHERE tournament_id = ? AND status IN ('registered', 'confirmed') ORDER BY seed_number ASC, RAND()",
            [$tournamentId]
        );

        $count = count($participants);
        if ($count < (int) ($tournament->min_participants ?? 2)) {
            throw new \RuntimeException('عدد المشاركين أقل من الحد الأدنى المطلوب');
        }

        // Delete existing matches (regenerate)
        $db->query("DELETE FROM tournament_matches WHERE tournament_id = ?", [$tournamentId]);

        $format = $tournament->format ?? 'knockout';
        $matches = [];

        if ($format === 'knockout') {
            $matches = self::generateKnockoutBracket($tournamentId, $participants);
        } elseif ($format === 'round_robin') {
            $matches = self::generateRoundRobin($tournamentId, $participants);
        } else {
            // Default to knockout for other formats
            $matches = self::generateKnockoutBracket($tournamentId, $participants);
        }

        // Update tournament status
        $tournament->update(['status' => 'in_progress']);

        // Assign seed numbers to participants
        foreach ($participants as $i => $p) {
            $db->update('tournament_participants', [
                'seed_number' => $i + 1,
                'status'      => 'confirmed',
            ], 'id = ?', [(int) $p['id']]);
        }

        EventBus::dispatch('tournament.bracket_generated', [
            'tournament_id'    => $tournamentId,
            'format'           => $format,
            'participant_count' => $count,
            'match_count'      => count($matches),
        ]);

        return $matches;
    }

    /**
     * Generate knockout bracket (random seeding pairs).
     */
    private static function generateKnockoutBracket(int $tournamentId, array $participants): array
    {
        $db = App::getInstance()->db();
        $count = count($participants);

        // Shuffle for random seeding
        shuffle($participants);

        // Find next power of 2 for bracket size
        $bracketSize = 1;
        while ($bracketSize < $count) {
            $bracketSize *= 2;
        }

        $totalRounds = (int) log($bracketSize, 2);
        $matches = [];
        $matchNumber = 1;

        // Create first round matches
        for ($i = 0; $i < $bracketSize; $i += 2) {
            $participantA = $participants[$i] ?? null;
            $participantB = $participants[$i + 1] ?? null;

            $matchData = [
                'tournament_id'    => $tournamentId,
                'round_number'     => 1,
                'match_number'     => $matchNumber,
                'participant_a_id' => $participantA ? (int) $participantA['id'] : null,
                'participant_b_id' => $participantB ? (int) $participantB['id'] : null,
                'status'           => 'scheduled',
            ];

            // If one participant is null (bye), auto-advance
            if ($participantA && !$participantB) {
                $matchData['winner_participant_id'] = (int) $participantA['id'];
                $matchData['status'] = 'walkover';
                $matchData['completed_at'] = date('Y-m-d H:i:s');
            } elseif (!$participantA && $participantB) {
                $matchData['winner_participant_id'] = (int) $participantB['id'];
                $matchData['status'] = 'walkover';
                $matchData['completed_at'] = date('Y-m-d H:i:s');
            }

            $matchId = $db->insert('tournament_matches', array_merge($matchData, [
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s'),
            ]));

            $matchData['id'] = $matchId;
            $matches[] = $matchData;
            $matchNumber++;
        }

        // Create placeholder matches for subsequent rounds
        for ($round = 2; $round <= $totalRounds; $round++) {
            $matchesInRound = $bracketSize / (int) pow(2, $round);
            for ($m = 0; $m < $matchesInRound; $m++) {
                $matchData = [
                    'tournament_id'    => $tournamentId,
                    'round_number'     => $round,
                    'match_number'     => $matchNumber,
                    'participant_a_id' => null,
                    'participant_b_id' => null,
                    'status'           => 'scheduled',
                    'created_at'       => date('Y-m-d H:i:s'),
                    'updated_at'       => date('Y-m-d H:i:s'),
                ];

                $matchId = $db->insert('tournament_matches', $matchData);
                $matchData['id'] = $matchId;
                $matches[] = $matchData;
                $matchNumber++;
            }
        }

        // Auto-advance byes to next round
        self::advanceByeWinners($tournamentId);

        return $matches;
    }

    /**
     * Generate round-robin (all vs all) matches.
     */
    private static function generateRoundRobin(int $tournamentId, array $participants): array
    {
        $db = App::getInstance()->db();
        $count = count($participants);
        $matches = [];
        $matchNumber = 1;

        // If odd number of participants, add a dummy bye participant
        $hasBye = ($count % 2 !== 0);
        if ($hasBye) {
            $participants[] = null; // BYE
            $count++;
        }

        $totalRounds = $count - 1;
        $half = $count / 2;

        // Circle method for round-robin scheduling
        $fixed = array_shift($participants);
        $rotating = $participants;

        for ($round = 1; $round <= $totalRounds; $round++) {
            // First match: fixed vs first rotating
            $pairs = [];
            $pairs[] = [$fixed, $rotating[0]];

            // Remaining pairs
            for ($i = 1; $i < $half; $i++) {
                $pairs[] = [$rotating[$i], $rotating[$count - 2 - $i]];
            }

            foreach ($pairs as $pair) {
                $a = $pair[0];
                $b = $pair[1];

                // Skip BYE matches
                if ($a === null || $b === null) {
                    continue;
                }

                $matchData = [
                    'tournament_id'    => $tournamentId,
                    'round_number'     => $round,
                    'match_number'     => $matchNumber,
                    'participant_a_id' => (int) $a['id'],
                    'participant_b_id' => (int) $b['id'],
                    'status'           => 'scheduled',
                    'created_at'       => date('Y-m-d H:i:s'),
                    'updated_at'       => date('Y-m-d H:i:s'),
                ];

                $matchId = $db->insert('tournament_matches', $matchData);
                $matchData['id'] = $matchId;
                $matches[] = $matchData;
                $matchNumber++;
            }

            // Rotate: move last to front (after fixed)
            $last = array_pop($rotating);
            array_unshift($rotating, $last);
        }

        return $matches;
    }

    /**
     * Advance bye winners to next round in knockout.
     */
    private static function advanceByeWinners(int $tournamentId): void
    {
        $db = App::getInstance()->db();

        // Get round 1 walkovers
        $walkovers = $db->select(
            "SELECT id, match_number, winner_participant_id FROM tournament_matches
             WHERE tournament_id = ? AND round_number = 1 AND status = 'walkover' AND winner_participant_id IS NOT NULL",
            [$tournamentId]
        );

        foreach ($walkovers as $walkover) {
            self::advanceWinnerToNextRound($tournamentId, (int) $walkover['match_number'], (int) $walkover['winner_participant_id']);
        }
    }

    /**
     * Record the result of a match.
     */
    public static function recordMatchResult(int $matchId, array $result): bool
    {
        $db = App::getInstance()->db();

        $match = $db->selectOne("SELECT * FROM tournament_matches WHERE id = ?", [$matchId]);
        if (!$match) {
            throw new \RuntimeException('المباراة غير موجودة');
        }

        $tournamentId = (int) $match['tournament_id'];
        $tournament = Tournament::findOrFail($tournamentId);

        $winnerId = !empty($result['winner_participant_id']) ? (int) $result['winner_participant_id'] : null;
        $isDraw = (int) ($result['is_draw'] ?? 0);

        $updateData = [
            'score_a'                => $result['score_a'] ?? null,
            'score_b'                => $result['score_b'] ?? null,
            'winner_participant_id'  => $winnerId,
            'is_draw'               => $isDraw,
            'status'                => 'completed',
            'referee_name'          => $result['referee_name'] ?? null,
            'match_notes'           => $result['match_notes'] ?? null,
            'completed_at'          => date('Y-m-d H:i:s'),
            'updated_at'            => date('Y-m-d H:i:s'),
        ];

        $db->update('tournament_matches', $updateData, 'id = ?', [$matchId]);

        // Update participant stats
        $participantA = (int) $match['participant_a_id'];
        $participantB = (int) $match['participant_b_id'];

        if ($isDraw) {
            // Both get a draw
            if ($participantA > 0) {
                $db->query("UPDATE tournament_participants SET draws = draws + 1, points = points + 1 WHERE id = ?", [$participantA]);
            }
            if ($participantB > 0) {
                $db->query("UPDATE tournament_participants SET draws = draws + 1, points = points + 1 WHERE id = ?", [$participantB]);
            }
        } else {
            // Winner gets a win, loser gets a loss
            $loserId = ($winnerId === $participantA) ? $participantB : $participantA;
            if ($winnerId > 0) {
                $db->query("UPDATE tournament_participants SET wins = wins + 1, points = points + 3 WHERE id = ?", [$winnerId]);
            }
            if ($loserId > 0) {
                $db->query("UPDATE tournament_participants SET losses = losses + 1 WHERE id = ?", [$loserId]);
            }

            // For knockout format, advance winner to next round
            if ($tournament->format === 'knockout' && $winnerId > 0) {
                self::advanceWinnerToNextRound($tournamentId, (int) $match['match_number'], $winnerId);

                // Mark loser as eliminated
                if ($loserId > 0) {
                    $db->update('tournament_participants', ['status' => 'eliminated'], 'id = ?', [$loserId]);
                }
            }
        }

        EventBus::dispatch('tournament.match_completed', [
            'tournament_id' => $tournamentId,
            'match_id'      => $matchId,
            'winner_id'     => $winnerId,
            'is_draw'       => $isDraw,
        ]);

        return true;
    }

    /**
     * Advance a winner to the next round in a knockout bracket.
     */
    private static function advanceWinnerToNextRound(int $tournamentId, int $matchNumber, int $winnerId): void
    {
        $db = App::getInstance()->db();

        // In a standard knockout bracket, match N feeds into match ceil(N/2) of next round
        // Determine which "slot" the winner goes to
        $nextMatchIndex = (int) ceil($matchNumber / 2);
        $isSlotA = ($matchNumber % 2 !== 0); // Odd match -> slot A, Even -> slot B

        // Get the current round of this match
        $currentMatch = $db->selectOne(
            "SELECT round_number FROM tournament_matches WHERE tournament_id = ? AND match_number = ?",
            [$tournamentId, $matchNumber]
        );
        if (!$currentMatch) return;

        $nextRound = (int) $currentMatch['round_number'] + 1;

        // Find next round matches
        $nextRoundMatches = $db->select(
            "SELECT id, match_number FROM tournament_matches WHERE tournament_id = ? AND round_number = ? ORDER BY match_number ASC",
            [$tournamentId, $nextRound]
        );

        if (empty($nextRoundMatches)) return;

        // Map match to next round: based on position within round
        // First round match pairs (1,2) -> next round match 1; (3,4) -> match 2; etc.
        $currentRoundMatches = $db->select(
            "SELECT match_number FROM tournament_matches WHERE tournament_id = ? AND round_number = ? ORDER BY match_number ASC",
            [$tournamentId, (int) $currentMatch['round_number']]
        );

        // Find position of this match in its round
        $posInRound = 0;
        foreach ($currentRoundMatches as $idx => $m) {
            if ((int) $m['match_number'] === $matchNumber) {
                $posInRound = $idx;
                break;
            }
        }

        $nextMatchIdx = (int) floor($posInRound / 2);
        $slot = ($posInRound % 2 === 0) ? 'participant_a_id' : 'participant_b_id';

        if (isset($nextRoundMatches[$nextMatchIdx])) {
            $db->update('tournament_matches', [
                $slot        => $winnerId,
                'updated_at' => date('Y-m-d H:i:s'),
            ], 'id = ?', [(int) $nextRoundMatches[$nextMatchIdx]['id']]);
        }
    }

    /**
     * Update standings for a tournament (recalculate from match results).
     */
    public static function updateStandings(int $tournamentId): void
    {
        $db = App::getInstance()->db();

        // Reset all participant stats
        $db->query(
            "UPDATE tournament_participants SET points = 0, wins = 0, losses = 0, draws = 0 WHERE tournament_id = ?",
            [$tournamentId]
        );

        // Recalculate from completed matches
        $matches = $db->select(
            "SELECT * FROM tournament_matches WHERE tournament_id = ? AND status = 'completed'",
            [$tournamentId]
        );

        foreach ($matches as $match) {
            $aId = (int) ($match['participant_a_id'] ?? 0);
            $bId = (int) ($match['participant_b_id'] ?? 0);
            $winnerId = (int) ($match['winner_participant_id'] ?? 0);
            $isDraw = (int) ($match['is_draw'] ?? 0);

            if ($isDraw) {
                if ($aId > 0) {
                    $db->query("UPDATE tournament_participants SET draws = draws + 1, points = points + 1 WHERE id = ?", [$aId]);
                }
                if ($bId > 0) {
                    $db->query("UPDATE tournament_participants SET draws = draws + 1, points = points + 1 WHERE id = ?", [$bId]);
                }
            } else {
                $loserId = ($winnerId === $aId) ? $bId : $aId;
                if ($winnerId > 0) {
                    $db->query("UPDATE tournament_participants SET wins = wins + 1, points = points + 3 WHERE id = ?", [$winnerId]);
                }
                if ($loserId > 0) {
                    $db->query("UPDATE tournament_participants SET losses = losses + 1 WHERE id = ?", [$loserId]);
                }
            }
        }
    }

    /**
     * Complete a tournament - determine winner.
     */
    public static function completeTournament(int $tournamentId): bool
    {
        $db = App::getInstance()->db();
        $tournament = Tournament::findOrFail($tournamentId);

        // Determine winner based on format
        $winnerId = null;
        $winnerName = null;

        if ($tournament->format === 'knockout') {
            // Winner is the last match winner
            $finalMatch = $db->selectOne(
                "SELECT tm.winner_participant_id, COALESCE(tp.team_name, p.full_name_ar) AS winner_name, tp.player_id
                 FROM tournament_matches tm
                 LEFT JOIN tournament_participants tp ON tp.id = tm.winner_participant_id
                 LEFT JOIN players p ON p.id = tp.player_id
                 WHERE tm.tournament_id = ? AND tm.status = 'completed'
                 ORDER BY tm.round_number DESC, tm.match_number DESC
                 LIMIT 1",
                [$tournamentId]
            );

            if ($finalMatch && $finalMatch['winner_participant_id']) {
                $winnerId = (int) ($finalMatch['player_id'] ?? 0);
                $winnerName = $finalMatch['winner_name'] ?? null;

                // Mark winner participant
                $db->update('tournament_participants', ['status' => 'winner', 'final_position' => 1],
                    'id = ?', [(int) $finalMatch['winner_participant_id']]);
            }
        } else {
            // For round_robin, winner is top of standings
            $standings = TournamentParticipant::getStandings($tournamentId);
            if (!empty($standings)) {
                $top = $standings[0];
                $winnerId = (int) ($top['player_id'] ?? 0);
                $winnerName = $top['team_name'] ?? $top['player_name'] ?? null;

                $db->update('tournament_participants', ['status' => 'winner', 'final_position' => 1],
                    'id = ?', [(int) $top['id']]);

                // Mark runner-up
                if (isset($standings[1])) {
                    $db->update('tournament_participants', ['status' => 'runner_up', 'final_position' => 2],
                        'id = ?', [(int) $standings[1]['id']]);
                }
            }
        }

        // Update tournament
        $updateData = [
            'status'           => 'completed',
            'winner_player_id' => $winnerId > 0 ? $winnerId : null,
            'winner_team_name' => $winnerName,
        ];
        $tournament->update($updateData);

        EventBus::dispatch('tournament.completed', [
            'tournament_id'   => $tournamentId,
            'winner_player_id'=> $winnerId,
            'winner_name'     => $winnerName,
        ]);

        return true;
    }
}
