<?php

declare(strict_types=1);

namespace Tournament\Stage;

use PDO;
use RuntimeException;
use Throwable;

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

    /**
     * دریافت اطلاعات Room
     */
    public function getRoom(
        int $roomId
    ): array {
        if ($roomId <= 0) {
            throw new RuntimeException(
                'شناسه Room نامعتبر است.'
            );
        }

        $statement = $this->pdo->prepare("
            SELECT
                id,
                stage_id,
                round_number,
                room_number,
                capacity,
                participant_count,
                status,
                created_at,
                updated_at
            FROM tournament_br_rooms
            WHERE id = ?
            LIMIT 1
        ");

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

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

        if (!$room) {
            throw new RuntimeException(
                'Room پیدا نشد.'
            );
        }

        return $room;
    }

    /**
     * دریافت بازیکنان Room
     *
     * Result اگر وجود داشته باشد
     * همراه بازیکن برگردانده می‌شود.
     */
    public function getRoomParticipants(
        int $roomId
    ): array {
        $this->getRoom($roomId);

        $statement = $this->pdo->prepare("
            SELECT
                rp.id AS room_participant_id,
                rp.room_id,
                rp.tournament_participant_id,
                rp.slot_number,
                rp.status AS participant_status,

                tp.event_player_id,

                ep.user_id,
                ep.ign,

                r.id AS result_id,
                r.placement,
                r.kills,
                r.score,
                r.status AS result_status,
                r.updated_at AS result_updated_at

            FROM tournament_br_room_participants rp

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

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

            LEFT JOIN tournament_br_results r
                ON r.room_id =
                    rp.room_id

                AND r.room_participant_id =
                    rp.id

                AND r.tournament_participant_id =
                    rp.tournament_participant_id

            WHERE rp.room_id = ?

            ORDER BY
                COALESCE(
                    r.placement,
                    999999
                ) ASC,
                rp.slot_number ASC
        ");

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

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

    /**
     * دریافت Result یک بازیکن
     */
    public function getResult(
        int $resultId
    ): array {
        if ($resultId <= 0) {
            throw new RuntimeException(
                'شناسه Result نامعتبر است.'
            );
        }

        $statement = $this->pdo->prepare("
            SELECT
                r.id,
                r.room_id,
                r.room_participant_id,
                r.tournament_participant_id,

                r.placement,
                r.kills,
                r.score,
                r.status,

                br.stage_id,
                br.round_number,
                br.room_number,
                br.status AS room_status,
                br.participant_count,

                rp.slot_number,

                ep.user_id,
                ep.ign

            FROM tournament_br_results r

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

            INNER JOIN tournament_br_room_participants rp
                ON rp.id =
                    r.room_participant_id

                AND rp.room_id =
                    r.room_id

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

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

            WHERE r.id = ?

            LIMIT 1
        ");

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

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

        if (!$result) {
            throw new RuntimeException(
                'Result پیدا نشد.'
            );
        }

        return $result;
    }

    /**
     * ایجاد Result اولیه برای کل Room
     *
     * این متد زمانی استفاده می‌شود که
     * Room تمام شده ولی هنوز Result ساخته نشده.
     *
     * Resultها با وضعیت draft ساخته می‌شوند.
     */
    public function initializeRoomResults(
        int $roomId
    ): int {
        $room = $this->getRoom(
            $roomId
        );

        if (
            $room['status'] === 'cancelled'
        ) {
            throw new RuntimeException(
                'Room لغو شده است.'
            );
        }

        $participants =
            $this->getRoomParticipants(
                $roomId
            );

        if (!$participants) {
            throw new RuntimeException(
                'این Room بازیکنی ندارد.'
            );
        }

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

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

        try {

            $insert = $this->pdo->prepare("
                INSERT INTO tournament_br_results (
                    room_id,
                    room_participant_id,
                    tournament_participant_id,
                    placement,
                    kills,
                    score,
                    status
                )
                VALUES (
                    ?,
                    ?,
                    ?,
                    ?,
                    0,
                    0.00,
                    'draft'
                )
                ON DUPLICATE KEY UPDATE
                    updated_at = updated_at
            ");

            $created = 0;

            /*
             * Placement موقت:
             * Slot number
             *
             * بعداً ادمین آن را اصلاح می‌کند.
             */
            foreach ($participants as $participant) {

                if (
                    $participant['result_id']
                    !== null
                ) {
                    continue;
                }

                $insert->execute([
                    $roomId,

                    (int)
                        $participant[
                            'room_participant_id'
                        ],

                    (int)
                        $participant[
                            'tournament_participant_id'
                        ],

                    (int)
                        $participant[
                            'slot_number'
                        ]
                ]);

                $created++;
            }

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

            return $created;

        } catch (Throwable $e) {

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

            throw $e;
        }
    }

    /**
     * تغییر Placement
     *
     * Score فعلاً تغییر نمی‌کند.
     *
     * محاسبه Score باید توسط
     * ScoringStrategy انجام شود.
     */
    public function updatePlacement(
        int $resultId,
        int $placement
    ): array {
        if ($placement < 1) {
            throw new RuntimeException(
                'Placement باید حداقل 1 باشد.'
            );
        }

        $result =
            $this->getResult(
                $resultId
            );

        $roomParticipantCount =
            (int)
                $result['participant_count'];

        if (
            $placement >
            $roomParticipantCount
        ) {
            throw new RuntimeException(
                'Placement از تعداد بازیکنان Room بیشتر است.'
            );
        }

        if (
            $result['room_status'] ===
            'cancelled'
        ) {
            throw new RuntimeException(
                'Room لغو شده است.'
            );
        }

        /*
         * جلوگیری از Placement تکراری
         */
        $statement = $this->pdo->prepare("
            SELECT
                id
            FROM tournament_br_results
            WHERE room_id = ?
              AND placement = ?
              AND id <> ?
            LIMIT 1
            FOR UPDATE
        ");

        $statement->execute([
            (int) $result['room_id'],
            $placement,
            $resultId
        ]);

        if (
            $statement->fetch(
                PDO::FETCH_ASSOC
            )
        ) {
            throw new RuntimeException(
                "Placement {$placement} قبلاً در این Room استفاده شده است."
            );
        }

        $statement = $this->pdo->prepare("
            UPDATE tournament_br_results
            SET
                placement = ?,
                status = 'draft'
            WHERE id = ?
        ");

        $statement->execute([
            $placement,
            $resultId
        ]);

        return $this->getResult(
            $resultId
        );
    }

    /**
     * تغییر Kills
     */
    public function updateKills(
        int $resultId,
        int $kills
    ): array {
        if ($kills < 0) {
            throw new RuntimeException(
                'Kills نمی‌تواند منفی باشد.'
            );
        }

        $result =
            $this->getResult(
                $resultId
            );

        if (
            $result['room_status'] ===
            'cancelled'
        ) {
            throw new RuntimeException(
                'Room لغو شده است.'
            );
        }

        $statement = $this->pdo->prepare("
            UPDATE tournament_br_results
            SET
                kills = ?,
                status = 'draft'
            WHERE id = ?
        ");

        $statement->execute([
            $kills,
            $resultId
        ]);

        return $this->getResult(
            $resultId
        );
    }

    /**
     * تغییر همزمان Placement و Kills
     */
    public function updateResult(
        int $resultId,
        int $placement,
        int $kills
    ): array {
        if ($placement < 1) {
            throw new RuntimeException(
                'Placement باید حداقل 1 باشد.'
            );
        }

        if ($kills < 0) {
            throw new RuntimeException(
                'Kills نمی‌تواند منفی باشد.'
            );
        }

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

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

        try {

            $result =
                $this->getResult(
                    $resultId
                );

            if (
                $result['room_status'] ===
                'cancelled'
            ) {
                throw new RuntimeException(
                    'Room لغو شده است.'
                );
            }

            $participantCount =
                (int)
                    $result['participant_count'];

            if (
                $placement >
                $participantCount
            ) {
                throw new RuntimeException(
                    'Placement از تعداد بازیکنان Room بیشتر است.'
                );
            }

            /*
             * Duplicate Placement
             */
            $statement = $this->pdo->prepare("
                SELECT
                    id
                FROM tournament_br_results
                WHERE room_id = ?
                  AND placement = ?
                  AND id <> ?
                LIMIT 1
                FOR UPDATE
            ");

            $statement->execute([
                (int) $result['room_id'],
                $placement,
                $resultId
            ]);

            if (
                $statement->fetch(
                    PDO::FETCH_ASSOC
                )
            ) {
                throw new RuntimeException(
                    "Placement {$placement} قبلاً در این Room ثبت شده است."
                );
            }

            /*
             * Update
             */
            $statement = $this->pdo->prepare("
                UPDATE tournament_br_results
                SET
                    placement = ?,
                    kills = ?,
                    status = 'draft'
                WHERE id = ?
            ");

            $statement->execute([
                $placement,
                $kills,
                $resultId
            ]);

            $updated =
                $this->getResult(
                    $resultId
                );

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

            return $updated;

        } catch (Throwable $e) {

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

            throw $e;
        }
    }

    /**
     * Confirm کردن Result
     *
     * فقط زمانی مجاز است که:
     *
     * - Placement معتبر باشد
     * - Kills معتبر باشد
     * - Score قبلاً محاسبه شده باشد
     */
    public function confirmResult(
        int $resultId
    ): array {
        $result =
            $this->getResult(
                $resultId
            );

        if (
            $result['room_status'] ===
            'cancelled'
        ) {
            throw new RuntimeException(
                'Room لغو شده است.'
            );
        }

        if (
            (int) $result['placement'] < 1
        ) {
            throw new RuntimeException(
                'Placement نامعتبر است.'
            );
        }

        if (
            (int) $result['kills'] < 0
        ) {
            throw new RuntimeException(
                'Kills نامعتبر است.'
            );
        }

        $statement = $this->pdo->prepare("
            UPDATE tournament_br_results
            SET
                status = 'confirmed'
            WHERE id = ?
        ");

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

        return $this->getResult(
            $resultId
        );
    }

    /**
     * Confirm کردن تمام Resultهای Room
     *
     * قبل از Confirm:
     *
     * - همه بازیکنان باید Result داشته باشند.
     * - Placementها نباید تکراری باشند.
     */
    public function confirmRoom(
        int $roomId
    ): array {
        $room =
            $this->getRoom(
                $roomId
            );

        if (
            $room['status'] ===
            'cancelled'
        ) {
            throw new RuntimeException(
                'Room لغو شده است.'
            );
        }

        $participants =
            $this->getRoomParticipants(
                $roomId
            );

        if (!$participants) {
            throw new RuntimeException(
                'Room بازیکنی ندارد.'
            );
        }

        $seenPlacements = [];

        foreach (
            $participants as $participant
        ) {

            if (
                $participant['result_id']
                === null
            ) {
                throw new RuntimeException(
                    'هنوز Result تمام بازیکنان ثبت نشده است.'
                );
            }

            $placement =
                (int)
                    $participant['placement'];

            if ($placement < 1) {
                throw new RuntimeException(
                    'Placement نامعتبر وجود دارد.'
                );
            }

            if (
                isset(
                    $seenPlacements[
                        $placement
                    ]
                )
            ) {
                throw new RuntimeException(
                    "Placement {$placement} تکراری است."
                );
            }

            $seenPlacements[
                $placement
            ] = true;
        }

        $statement = $this->pdo->prepare("
            UPDATE tournament_br_results
            SET
                status = 'confirmed'
            WHERE room_id = ?
        ");

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

        return [
            'room_id' =>
                $roomId,

            'participants' =>
                count($participants),

            'confirmed' =>
                $statement->rowCount(),
        ];
    }
}