<?php

declare(strict_types=1);

namespace Tournament\Stage;

use PDO;
use RuntimeException;
use Throwable;

final class FinalStageService
{
    public function __construct(
        private readonly PDO $pdo
    ) {
    }

    /**
     * Finalize Battle Royale Final Stage.
     *
     * @return array{
     *     stage_id:int,
     *     participants:int,
     *     winner:array<string,mixed>,
     *     leaderboard:array<int,array<string,mixed>>
     * }
     */
    public function finalize(
        int $stageId
    ): array {
        $stage =
            $this->getStage(
                $stageId
            );

        if (!$stage) {
            throw new RuntimeException(
                'Final Stage not found.'
            );
        }

        /*
         * فقط Battle Royale
         */
        if (
            $stage['format']
            !== 'battle_royale'
        ) {
            throw new RuntimeException(
                'Final Stage must be Battle Royale.'
            );
        }

        /*
         * فقط Final
         */
        if (
            strtolower(
                trim(
                    (string) $stage['type']
                )
            ) !== 'final'
        ) {
            throw new RuntimeException(
                'This Stage is not a Final.'
            );
        }

        /*
         * Final باید active باشد.
         */
        if (
            $stage['status']
            !== 'active'
        ) {
            throw new RuntimeException(
                'Final Stage must be active.'
            );
        }

        /*
         * ==========================================
         * بررسی Roomها
         * ==========================================
         */
        $roomStats =
            $this->getRoomStats(
                $stageId
            );

        if (
            $roomStats['total_rooms'] < 1
        ) {
            throw new RuntimeException(
                'Final has no rooms.'
            );
        }

        if (
            $roomStats['completed_rooms']
            !==
            $roomStats['total_rooms']
        ) {
            throw new RuntimeException(
                "هنوز همه Roomهای Final تکمیل نشده‌اند. "
                . "("
                . $roomStats['completed_rooms']
                . "/"
                . $roomStats['total_rooms']
                . ")"
            );
        }

        /*
         * ==========================================
         * تعداد Participantها
         * ==========================================
         */
        $participantCount =
            $this->getFinalParticipantCount(
                $stageId
            );

        if ($participantCount < 2) {
            throw new RuntimeException(
                'Final must have at least 2 participants.'
            );
        }

        /*
         * ==========================================
         * Resultهای Confirmed
         * ==========================================
         */
        $confirmedResults =
            $this->getConfirmedResultCount(
                $stageId
            );

        if ($confirmedResults <= 0) {
            throw new RuntimeException(
                'No confirmed Final results found.'
            );
        }

        /*
         * ==========================================
         * بررسی کامل بودن Resultها
         * ==========================================
         */
        $missing =
            $this->findParticipantsWithMissingResults(
                $stageId
            );

        if ($missing) {
            throw new RuntimeException(
                'برای بعضی بازیکنان Final نتیجه کامل ثبت نشده است.'
            );
        }

        /*
         * ==========================================
         * Transaction
         * ==========================================
         */
        $ownsTransaction =
            !$this->pdo->inTransaction();

        if ($ownsTransaction) {
            $this->pdo->beginTransaction();
        }

        try {

            /*
             * ======================================
             * Leaderboard
             * ======================================
             */
            $leaderboard =
                $this->buildLeaderboard(
                    $stageId
                );

            if (!$leaderboard) {
                throw new RuntimeException(
                    'Final leaderboard is empty.'
                );
            }

            /*
             * Rank
             */
            foreach (
                $leaderboard as $index => &$row
            ) {
                $row['rank'] =
                    $index + 1;
            }

            unset($row);

            /*
             * ======================================
             * Winner
             * ======================================
             */
            $winner =
                $leaderboard[0];

            /*
             * ======================================
             * همه Participantهای Final
             * → eliminated
             * ======================================
             */
            $statement =
                $this->pdo->prepare("
                    UPDATE tournament_stage_participants
                    SET
                        status = 'eliminated',
                        updated_at = NOW()
                    WHERE stage_id = ?
                      AND status = 'active'
                ");

            $statement->execute([
                $stageId
            ]);

            /*
             * ======================================
             * Champion در Stage
             * ======================================
             */
            $statement =
                $this->pdo->prepare("
                    UPDATE tournament_stage_participants
                    SET
                        status = 'winner',
                        updated_at = NOW()
                    WHERE stage_id = ?
                      AND tournament_participant_id = ?
                    LIMIT 1
                ");

            $statement->execute([
                $stageId,
                (int) $winner['participant_id']
            ]);

            if (
                $statement->rowCount() !== 1
            ) {
                throw new RuntimeException(
                    'Champion participant could not be updated.'
                );
            }

            /*
             * ======================================
             * Participant اصلی
             * ======================================
             */
            $statement =
                $this->pdo->prepare("
                    UPDATE tournament_participants
                    SET
                        status = 'winner',
                        updated_at = NOW()
                    WHERE id = ?
                    LIMIT 1
                ");

            $statement->execute([
                (int) $winner['participant_id']
            ]);

            /*
             * ======================================
             * Final → completed
             * ======================================
             */
            $statement =
                $this->pdo->prepare("
                    UPDATE tournament_stages
                    SET
                        status = 'completed',
                        updated_at = NOW()
                    WHERE id = ?
                      AND status = 'active'
                ");

            $statement->execute([
                $stageId
            ]);

            if (
                $statement->rowCount() !== 1
            ) {
                throw new RuntimeException(
                    'Final Stage could not be completed.'
                );
            }

            /*
             * Commit
             */
            if ($ownsTransaction) {
                $this->pdo->commit();
            }

            return [
                'stage_id' =>
                    $stageId,

                'participants' =>
                    count($leaderboard),

                'winner' =>
                    $winner,

                'leaderboard' =>
                    $leaderboard,
            ];

        } catch (Throwable $e) {

            if (
                $ownsTransaction
                && $this->pdo->inTransaction()
            ) {
                $this->pdo->rollBack();
            }

            throw $e;
        }
    }

    /**
     * دریافت Stage
     */
    private function getStage(
        int $stageId
    ): ?array {
        $statement =
            $this->pdo->prepare("
                SELECT
                    id,
                    ev_id,
                    name,
                    type,
                    format,
                    status
                FROM tournament_stages
                WHERE id = ?
                LIMIT 1
            ");

        $statement->execute([
            $stageId
        ]);

        $stage =
            $statement->fetch(
                PDO::FETCH_ASSOC
            );

        return $stage ?: null;
    }

    /**
     * آمار Roomهای Final
     *
     * @return array{
     *     total_rooms:int,
     *     completed_rooms:int
     * }
     */
    private function getRoomStats(
        int $stageId
    ): array {
        $statement =
            $this->pdo->prepare("
                SELECT
                    COUNT(*) AS total_rooms,

                    COALESCE(
                        SUM(
                            CASE
                                WHEN status = 'completed'
                                THEN 1
                                ELSE 0
                            END
                        ),
                        0
                    ) AS completed_rooms

                FROM tournament_br_rooms

                WHERE stage_id = ?
            ");

        $statement->execute([
            $stageId
        ]);

        $row =
            $statement->fetch(
                PDO::FETCH_ASSOC
            );

        return [
            'total_rooms' =>
                (int) (
                    $row['total_rooms']
                    ?? 0
                ),

            'completed_rooms' =>
                (int) (
                    $row['completed_rooms']
                    ?? 0
                ),
        ];
    }

    /**
     * تعداد Participantهای Final
     */
    private function getFinalParticipantCount(
        int $stageId
    ): int {
        $statement =
            $this->pdo->prepare("
                SELECT COUNT(*)
                FROM tournament_stage_participants
                WHERE stage_id = ?
                  AND status IN (
                      'active',
                      'winner',
                      'eliminated'
                  )
            ");

        $statement->execute([
            $stageId
        ]);

        return (int)
            $statement->fetchColumn();
    }

    /**
     * تعداد Resultهای Confirmed
     */
    private function getConfirmedResultCount(
        int $stageId
    ): int {
        $statement =
            $this->pdo->prepare("
                SELECT COUNT(*)
                FROM tournament_br_results r

                INNER JOIN tournament_br_rooms br
                    ON br.id = r.room_id

                WHERE br.stage_id = ?
                  AND r.status = 'confirmed'
            ");

        $statement->execute([
            $stageId
        ]);

        return (int)
            $statement->fetchColumn();
    }

    /**
     * پیدا کردن Participantهایی که
     * Result ناقص دارند.
     *
     * هر Participant باید برای تمام
     * Roomهایی که در آن حضور داشته
     * Result confirmed داشته باشد.
     */
    private function findParticipantsWithMissingResults(
        int $stageId
    ): array {
        $statement =
            $this->pdo->prepare("
                SELECT
                    rp.tournament_participant_id,

                    COUNT(*) AS room_count,

                    COUNT(
                        CASE
                            WHEN r.id IS NOT NULL
                             AND r.status = 'confirmed'
                            THEN 1
                        END
                    ) AS result_count

                FROM tournament_br_room_participants rp

                INNER JOIN tournament_br_rooms br
                    ON br.id = rp.room_id

                LEFT JOIN tournament_br_results r
                    ON r.room_id = rp.room_id
                    AND r.room_participant_id = rp.id

                WHERE br.stage_id = ?

                GROUP BY
                    rp.tournament_participant_id

                HAVING
                    result_count < room_count
            ");

        $statement->execute([
            $stageId
        ]);

        return $statement->fetchAll(
            PDO::FETCH_ASSOC
        );
    }

    /**
     * ساخت Leaderboard نهایی
     *
     * Tie-break:
     *
     * 1. Total Score DESC
     * 2. Total Kills DESC
     * 3. Best Placement ASC
     * 4. Participant ID ASC
     *
     * @return array<int,array<string,mixed>>
     */
    private function buildLeaderboard(
        int $stageId
    ): array {
        $statement =
            $this->pdo->prepare("
                SELECT
                    tp.id AS participant_id,

                    tp.event_player_id,

                    ep.user_id,
                    ep.ign,

                    COALESCE(
                        SUM(r.score),
                        0
                    ) AS score,

                    COALESCE(
                        SUM(r.kills),
                        0
                    ) AS kills,

                    MIN(r.placement)
                        AS best_placement,

                    COUNT(r.id)
                        AS rounds_played

                FROM tournament_stage_participants tsp

                INNER JOIN tournament_participants tp
                    ON tp.id =
                        tsp.tournament_participant_id

                INNER JOIN event_players ep
                    ON ep.id =
                        tp.event_player_id

                INNER JOIN tournament_br_rooms br
                    ON br.stage_id =
                        tsp.stage_id

                INNER JOIN tournament_br_room_participants rp
                    ON rp.room_id =
                        br.id
                    AND rp.tournament_participant_id =
                        tp.id

                INNER JOIN tournament_br_results r
                    ON r.room_id =
                        br.id
                    AND r.room_participant_id =
                        rp.id
                    AND r.status = 'confirmed'

                WHERE tsp.stage_id = ?

                GROUP BY
                    tp.id,
                    tp.event_player_id,
                    ep.user_id,
                    ep.ign

                ORDER BY
                    score DESC,
                    kills DESC,
                    best_placement ASC,
                    tp.id ASC
            ");

        $statement->execute([
            $stageId
        ]);

        $rows =
            $statement->fetchAll(
                PDO::FETCH_ASSOC
            );

        foreach (
            $rows as &$row
        ) {
            $row['participant_id'] =
                (int) (
                    $row['participant_id']
                    ?? 0
                );

            $row['event_player_id'] =
                (int) (
                    $row['event_player_id']
                    ?? 0
                );

            $row['user_id'] =
                (int) (
                    $row['user_id']
                    ?? 0
                );

            $row['score'] =
                (float) (
                    $row['score']
                    ?? 0
                );

            $row['kills'] =
                (int) (
                    $row['kills']
                    ?? 0
                );

            $row['best_placement'] =
                (int) (
                    $row['best_placement']
                    ?? 0
                );

            $row['rounds_played'] =
                (int) (
                    $row['rounds_played']
                    ?? 0
                );
        }

        unset($row);

        return $rows;
    }
}