<?php

declare(strict_types=1);

namespace Tournament\Bracket;

use PDO;
use RuntimeException;

final class ByeHandler
{
    public function __construct(
        private readonly PDO $pdo,
        private readonly BracketProgression $progression
    ) {
    }

    /**
     * Process all ready BYE matches.
     *
     * Transaction is managed by the caller.
     *
     * @return array{
     *     processed: int,
     *     participants_advanced: int
     * }
     */
    public function processBracket(
        int $bracketId
    ): array {
        $matches =
            $this->getByeMatches($bracketId);

        $processed = 0;
        $advanced = 0;

        foreach ($matches as $match) {

            $matchId =
                (int) $match['id'];

            $participantId =
                $this->getByeParticipant(
                    $matchId
                );

            if ($participantId === null) {
                throw new RuntimeException(
                    "BYE match {$matchId} has no valid participant."
                );
            }

            /*
             * Finalize BYE match first.
             */
            $this->finishByeMatch(
                $matchId,
                $participantId
            );

            /*
             * Mark participant as finished.
             */
            $this->finishMatchParticipant(
                $matchId,
                $participantId
            );

            /*
             * Advance winner through the normal
             * progression pipeline.
             */
            $this->progression->advanceWinner(
                matchId: $matchId,
                winnerParticipantId: $participantId
            );

            $processed++;
            $advanced++;
        }

        return [
            'processed' =>
                $processed,

            'participants_advanced' =>
                $advanced,
        ];
    }

    /**
     * Get ready BYE matches.
     *
     * @return array<int, array{
     *     id: int|string,
     *     bracket_id: int|string,
     *     round_number: int|string
     * }>
     */
    private function getByeMatches(
        int $bracketId
    ): array {
        $statement = $this->pdo->prepare(
            <<<SQL
            SELECT
                id,
                bracket_id,
                round_number
            FROM tournament_matches
            WHERE bracket_id = ?
              AND is_bye = 1
              AND status = 'ready'
            ORDER BY
                round_number ASC,
                match_number ASC
            FOR UPDATE
            SQL
        );

        $statement->execute([
            $bracketId,
        ]);

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

    /**
     * Get the only participant of a BYE match.
     */
    private function getByeParticipant(
        int $matchId
    ): ?int {
        $statement = $this->pdo->prepare(
            <<<SQL
            SELECT
                tournament_participant_id
            FROM tournament_match_participants
            WHERE match_id = ?
            ORDER BY slot ASC
            SQL
        );

        $statement->execute([
            $matchId,
        ]);

        $participants =
            $statement->fetchAll(
                PDO::FETCH_COLUMN
            );

        if (count($participants) !== 1) {
            return null;
        }

        return (int) $participants[0];
    }

    /**
     * Finish BYE match.
     */
    private function finishByeMatch(
        int $matchId,
        int $participantId
    ): void {
        $statement = $this->pdo->prepare(
            <<<SQL
            UPDATE tournament_matches
            SET
                status = 'finished',
                winner_participant_id = ?,
                finished_at = COALESCE(
                    finished_at,
                    NOW()
                )
            WHERE id = ?
              AND is_bye = 1
              AND status = 'ready'
            SQL
        );

        $statement->execute([
            $participantId,
            $matchId,
        ]);

        if ($statement->rowCount() !== 1) {
            throw new RuntimeException(
                "Unable to finish BYE match {$matchId}."
            );
        }
    }

    /**
     * Mark BYE participant as finished.
     */
    private function finishMatchParticipant(
        int $matchId,
        int $participantId
    ): void {
        $statement = $this->pdo->prepare(
            <<<SQL
            UPDATE tournament_match_participants
            SET status = 'finished'
            WHERE match_id = ?
              AND tournament_participant_id = ?
            SQL
        );

        $statement->execute([
            $matchId,
            $participantId,
        ]);
    }
}