<?php

declare(strict_types=1);

namespace Tournament\Bracket;

use PDO;
use InvalidArgumentException;

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

    /**
     * Validate bracket and stage relationship.
     */
    public function validateBracket(
        int $bracketId,
        int $stageId
    ): void {
        if ($bracketId <= 0) {
            throw new InvalidArgumentException(
                'Invalid bracket ID.'
            );
        }

        if ($stageId <= 0) {
            throw new InvalidArgumentException(
                'Invalid stage ID.'
            );
        }

        $statement = $this->pdo->prepare(
            <<<SQL
            SELECT
                id,
                stage_id,
                type,
                status
            FROM tournament_brackets
            WHERE id = ?
            LIMIT 1
            SQL
        );

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

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

        if ($bracket === false) {
            throw new InvalidArgumentException(
                'Bracket not found.'
            );
        }

        if ((int) $bracket['stage_id'] !== $stageId) {
            throw new InvalidArgumentException(
                'Bracket does not belong to the specified stage.'
            );
        }

        if ($bracket['type'] !== 'single_elimination') {
            throw new InvalidArgumentException(
                'Only single elimination brackets are currently supported.'
            );
        }

        if ($bracket['status'] !== 'pending') {
            throw new InvalidArgumentException(
                'Bracket must be pending before generation.'
            );
        }
    }

    /**
     * Validate participant count.
     */
    public function validateParticipantCount(
        int $count
    ): void {
        if ($count < 2) {
            throw new InvalidArgumentException(
                'A minimum of 2 participants is required.'
            );
        }
    }

    /**
     * Validate bracket size.
     */
    public function validateBracketSize(
        int $size
    ): void {
        if (
            $size < 2
            || !$this->isPowerOfTwo($size)
        ) {
            throw new InvalidArgumentException(
                'Bracket size must be a power of two.'
            );
        }
    }

    /**
     * Validate participants.
     *
     * @param array<int, array{id:int}> $participants
     */
    public function validateParticipants(
        array $participants
    ): void {
        $this->validateParticipantCount(
            count($participants)
        );

        $ids = [];

        foreach ($participants as $participant) {
            if (!isset($participant['id'])) {
                throw new InvalidArgumentException(
                    'Every participant must have an ID.'
                );
            }

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

            if ($id <= 0) {
                throw new InvalidArgumentException(
                    'Participant ID must be greater than zero.'
                );
            }

            if (isset($ids[$id])) {
                throw new InvalidArgumentException(
                    "Duplicate participant detected: {$id}."
                );
            }

            $ids[$id] = true;
        }
    }

    /**
     * Validate seed values.
     *
     * @param array<int, array{
     *     id:int,
     *     seed?:int|null
     * }> $participants
     */
    public function validateSeeds(
        array $participants,
        int $bracketSize
    ): void {
        $this->validateBracketSize(
            $bracketSize
        );

        $seeds = [];

        foreach ($participants as $participant) {
            if (
                !isset($participant['seed'])
                || $participant['seed'] === null
            ) {
                continue;
            }

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

            if (
                $seed < 1
                || $seed > $bracketSize
            ) {
                throw new InvalidArgumentException(
                    "Seed {$seed} is outside the bracket range."
                );
            }

            if (isset($seeds[$seed])) {
                throw new InvalidArgumentException(
                    "Duplicate seed detected: {$seed}."
                );
            }

            $seeds[$seed] = true;
        }
    }

    /**
     * Check whether a number is a power of two.
     */
    public function isPowerOfTwo(
        int $number
    ): bool {
        return $number > 0
            && ($number & ($number - 1)) === 0;
    }

    /**
     * Calculate the required bracket size.
     */
    public function calculateBracketSize(
        int $participantCount
    ): int {
        $this->validateParticipantCount(
            $participantCount
        );

        $size = 1;

        while ($size < $participantCount) {
            $size *= 2;
        }

        return $size;
    }

    /**
     * Calculate number of rounds.
     */
    public function calculateRounds(
        int $bracketSize
    ): int {
        $this->validateBracketSize(
            $bracketSize
        );

        return (int) log(
            $bracketSize,
            2
        );
    }

    /**
     * Calculate number of BYEs.
     */
    public function calculateByeCount(
        int $participantCount,
        int $bracketSize
    ): int {
        $this->validateParticipantCount(
            $participantCount
        );

        $this->validateBracketSize(
            $bracketSize
        );

        if ($participantCount > $bracketSize) {
            throw new InvalidArgumentException(
                'Participant count cannot exceed bracket size.'
            );
        }

        return $bracketSize - $participantCount;
    }
}