<?php

declare(strict_types=1);

require_once dirname(__DIR__, 2) . '/core/auth.php';
require_once dirname(__DIR__, 2) . '/core/csrf.php';
require_once dirname(__DIR__, 2) . '/core/validation.php';
require_once dirname(__DIR__, 2) . '/core/audit.php';
require_once dirname(__DIR__, 2) . '/modules/media/helpers.php';
require_once dirname(__DIR__, 2) . '/modules/notifications/helpers.php';

function competition_module_boot(): void
{
    require_login();
    require_permission('manage_competitions');
}

function competition_type_options(): array
{
    return ['league', 'knockout', 'group_stage', 'mixed'];
}

function competition_status_options(): array
{
    return ['draft', 'open', 'active', 'paused', 'completed', 'cancelled'];
}

function competition_tie_breaker_options(): array
{
    return ['goal_difference', 'head_to_head'];
}

function competition_season_options(): array
{
    return db_fetch_all('SELECT id, name, status, start_date FROM seasons ORDER BY FIELD(status, "active", "paused", "upcoming", "completed", "cancelled"), start_date DESC, id DESC');
}

function competition_list(): array
{
    return db_fetch_all(
        'SELECT c.*, s.name AS season_name
         FROM competitions c
         INNER JOIN seasons s ON s.id = c.season_id
         ORDER BY FIELD(c.status, "active", "paused", "open", "draft", "completed", "cancelled"), s.start_date DESC, c.sequence_order ASC, c.id ASC'
    );
}

function competition_next_sequence_order(int $seasonId, ?int $ignoreCompetitionId = null): int
{
    $sql = 'SELECT COALESCE(MAX(sequence_order), 0) FROM competitions WHERE season_id = :season_id';
    $params = ['season_id' => $seasonId];

    if ($ignoreCompetitionId !== null) {
        $sql .= ' AND id <> :id';
        $params['id'] = $ignoreCompetitionId;
    }

    return ((int) db_fetch_value($sql, $params, 0)) + 1;
}

function competition_sequence_order_exists(int $seasonId, int $sequenceOrder, ?int $ignoreCompetitionId = null): bool
{
    $sql = 'SELECT COUNT(*) FROM competitions WHERE season_id = :season_id AND sequence_order = :sequence_order';
    $params = [
        'season_id' => $seasonId,
        'sequence_order' => $sequenceOrder,
    ];

    if ($ignoreCompetitionId !== null) {
        $sql .= ' AND id <> :id';
        $params['id'] = $ignoreCompetitionId;
    }

    return (int) db_fetch_value($sql, $params, 0) > 0;
}

function competition_find_next_rotation_candidate(array $competition): ?array
{
    return db_fetch_one(
        'SELECT c.*, s.name AS season_name, s.status AS season_status
         FROM competitions c
         INNER JOIN seasons s ON s.id = c.season_id
         WHERE c.season_id = :season_id
           AND c.id <> :id
           AND c.status IN ("paused", "open", "draft")
         ORDER BY
           CASE WHEN c.sequence_order > :sequence_order THEN 0 ELSE 1 END ASC,
           c.sequence_order ASC,
           c.id ASC
         LIMIT 1',
        [
            'season_id' => (int) $competition['season_id'],
            'id' => (int) $competition['id'],
            'sequence_order' => (int) $competition['sequence_order'],
        ]
    );
}

function competition_pause_active_competitions_for_switch(array $competition): array
{
    $activeCompetitions = db_fetch_all(
        'SELECT c.*, :season_name AS season_name
         FROM competitions c
         WHERE c.season_id = :season_id AND c.status = "active" AND c.id <> :id',
        [
            'season_name' => $competition['season_name'],
            'season_id' => (int) $competition['season_id'],
            'id' => (int) $competition['id'],
        ]
    );

    if ($activeCompetitions === []) {
        return [];
    }

    db_query(
        'UPDATE competitions
         SET status = "paused"
         WHERE season_id = :season_id AND status = "active" AND id <> :id',
        [
            'season_id' => (int) $competition['season_id'],
            'id' => (int) $competition['id'],
        ]
    );

    $switchedCompetitionNames = [];
    foreach ($activeCompetitions as $activeCompetition) {
        $switchedCompetitionNames[] = (string) $activeCompetition['name'];
        competition_publish_news(
            'Competition paused: ' . $activeCompetition['name'],
            competition_event_news_body($activeCompetition, 'The competition has been paused while another competition takes focus in the same season')
        );
    }

    return $switchedCompetitionNames;
}

function competition_activate_for_rotation(array $competition, string $triggerMessage): void
{
    $switchedCompetitionNames = competition_pause_active_competitions_for_switch($competition);

    db_query('UPDATE competitions SET status = "active" WHERE id = :id', ['id' => (int) $competition['id']]);

    competition_publish_news(
        (($competition['status'] ?? '') === 'paused' ? 'Competition resumed: ' : 'Competition active: ') . $competition['name'],
        competition_event_news_body($competition, $triggerMessage)
    );

    if ($switchedCompetitionNames !== []) {
        competition_publish_news(
            'Competition switch: ' . $competition['name'],
            competition_event_news_body(
                $competition,
                'League operations have switched focus from ' . implode(', ', $switchedCompetitionNames) . ' to ' . $competition['name']
            )
        );
    }
}

function competition_event_news_body(array $competition, string $message): string
{
    return sprintf(
        '%s. Competition: %s. Season: %s. Type: %s.',
        $message,
        $competition['name'],
        $competition['season_name'],
        ucwords(str_replace('_', ' ', (string) $competition['type']))
    );
}

function competition_publish_news(string $headline, string $body): int
{
    db_query(
        'INSERT INTO news_posts (
            title, slug, category, body, status, published_at, created_at, created_by
         ) VALUES (
            :title, :slug, :category, :body, "published", NOW(), NOW(), :created_by
         )',
        [
            'title' => $headline,
            'slug' => unique_news_slug($headline),
            'category' => 'competition_update',
            'body' => $body,
            'created_by' => (int) ($_SESSION['user_id'] ?? 0) ?: null,
        ]
    );

    $postId = (int) db()->lastInsertId();
    web_push_send_news_post($postId);

    return $postId;
}

function competition_set_status(int $competitionId, string $status): void
{
    $competition = find_competition_or_abort($competitionId);
    if (!in_array($status, competition_status_options(), true)) {
        throw new RuntimeException('Competition status is invalid.');
    }

    if (($competition['season_status'] ?? '') === 'paused' && $status === 'active') {
        throw new RuntimeException('Resume the parent season before activating a competition.');
    }

    $currentStatus = (string) $competition['status'];
    if ($currentStatus === $status) {
        return;
    }

    db()->beginTransaction();

    try {
        if ($status === 'active') {
            competition_activate_for_rotation(
                $competition,
                $currentStatus === 'paused'
                    ? 'The competition has resumed and is now the active competition in the season'
                    : 'The competition is now active for the season'
            );
        } else {
            db_query('UPDATE competitions SET status = :status WHERE id = :id', ['status' => $status, 'id' => $competitionId]);
        }

        if ($status === 'paused' || $status === 'completed' || $status === 'cancelled') {
            $message = match ($status) {
                'paused' => 'The competition has been paused and its fixtures remain on hold until it resumes',
                'completed' => 'The competition has been completed and rotation can move to the next competition in sequence',
                'cancelled' => 'The competition has been cancelled and rotation can move to the next competition in sequence',
                default => 'Competition status updated.',
            };

            competition_publish_news(
                'Competition ' . $status . ': ' . $competition['name'],
                competition_event_news_body($competition, $message)
            );

            if ($currentStatus === 'active') {
                $nextCompetition = competition_find_next_rotation_candidate($competition);

                if ($nextCompetition !== null) {
                    competition_activate_for_rotation(
                        $nextCompetition,
                        'The competition was activated automatically as the next item in the seasonal rotation after ' . $competition['name'] . ' moved to ' . $status
                    );
                }
            }
        }

        if ($status === 'active' && $currentStatus !== 'active') {
            $refreshedCompetition = find_competition_or_abort($competitionId);
            if ($refreshedCompetition['status'] === 'active' && $currentStatus === 'paused') {
                competition_publish_news(
                    'Competition sequence: ' . $refreshedCompetition['name'],
                    competition_event_news_body(
                        $refreshedCompetition,
                        'The competition has rejoined the active rotation in sequence order ' . (string) $refreshedCompetition['sequence_order']
                    )
                );
            }
        }

        audit_log('set_competition_status', 'competitions', $competitionId, ['status' => $currentStatus], ['status' => $status]);
        db()->commit();
    } catch (Throwable $exception) {
        if (db()->inTransaction()) {
            db()->rollBack();
        }

        throw $exception;
    }
}

function get_competition_by_id(int $competitionId): ?array
{
    return db_fetch_one(
        'SELECT c.*, s.name AS season_name, s.status AS season_status
         FROM competitions c
         INNER JOIN seasons s ON s.id = c.season_id
         WHERE c.id = :id
         LIMIT 1',
        ['id' => $competitionId]
    );
}

function find_competition_or_abort(int $competitionId): array
{
    $competition = get_competition_by_id($competitionId);

    if ($competition === null) {
        abort_request(404, 'Competition not found', 'The requested competition record does not exist.');
    }

    return $competition;
}

function competition_has_fixtures(int $competitionId): bool
{
    return (int) db_fetch_value('SELECT COUNT(*) FROM fixtures WHERE competition_id = :competition_id', ['competition_id' => $competitionId], 0) > 0;
}

function competition_season_exists(int $seasonId): bool
{
    return db_fetch_one('SELECT id FROM seasons WHERE id = :id LIMIT 1', ['id' => $seasonId]) !== null;
}

function validate_competition_payload(array $data, array &$errors, ?int $competitionId = null): array
{
    validate_required($data, [
        'season_id' => 'Season',
        'name' => 'Competition name',
        'type' => 'Competition type',
        'tie_breaker_type' => 'Tie breaker',
        'status' => 'Status',
    ], $errors);
    validate_in_array_field($data, 'type', competition_type_options(), 'Competition type', $errors);
    validate_in_array_field($data, 'tie_breaker_type', competition_tie_breaker_options(), 'Tie breaker', $errors);
    validate_in_array_field($data, 'status', competition_status_options(), 'Status', $errors);
    validate_numeric_min($data, 'number_of_teams', 'Number of teams', 0, $errors);
    validate_numeric_min($data, 'points_win', 'Points for win', 0, $errors);
    validate_numeric_min($data, 'points_draw', 'Points for draw', 0, $errors);
    validate_numeric_min($data, 'points_loss', 'Points for loss', 0, $errors);

    $normalized = [
        'season_id' => (int) ($data['season_id'] ?? 0),
        'name' => trim((string) ($data['name'] ?? '')),
        'sequence_order' => (int) ($data['sequence_order'] === '' ? 0 : ($data['sequence_order'] ?? 0)),
        'type' => (string) ($data['type'] ?? 'league'),
        'number_of_teams' => (int) ($data['number_of_teams'] === '' ? 0 : ($data['number_of_teams'] ?? 0)),
        'group_count' => (int) (($data['group_count'] ?? '') === '' ? 0 : $data['group_count']),
        'qualifiers_per_group' => (int) (($data['qualifiers_per_group'] ?? '') === '' ? 0 : $data['qualifiers_per_group']),
        'points_win' => (int) ($data['points_win'] === '' ? (int) site_setting('points_win', 3) : $data['points_win']),
        'points_draw' => (int) ($data['points_draw'] === '' ? (int) site_setting('points_draw', 1) : $data['points_draw']),
        'points_loss' => (int) ($data['points_loss'] === '' ? (int) site_setting('points_loss', 0) : $data['points_loss']),
        'tie_breaker_type' => (string) ($data['tie_breaker_type'] ?? 'goal_difference'),
        'status' => (string) ($data['status'] ?? 'draft'),
    ];

    if ($normalized['season_id'] <= 0 || !competition_season_exists($normalized['season_id'])) {
        $errors['season_id'] = 'Selected season is invalid.';
    }

    if ($normalized['season_id'] > 0 && $normalized['sequence_order'] <= 0) {
        $normalized['sequence_order'] = competition_next_sequence_order($normalized['season_id'], $competitionId);
    }

    if ($normalized['sequence_order'] <= 0) {
        $errors['sequence_order'] = 'Sequence order must be at least 1.';
    }

    if ($normalized['season_id'] > 0 && $normalized['name'] !== '') {
        $query = 'SELECT COUNT(*) FROM competitions WHERE season_id = :season_id AND name = :name';
        $params = ['season_id' => $normalized['season_id'], 'name' => $normalized['name']];

        if ($competitionId !== null) {
            $query .= ' AND id <> :id';
            $params['id'] = $competitionId;
        }

        if ((int) db_fetch_value($query, $params, 0) > 0) {
            $errors['name'] = 'Competition name already exists for the selected season.';
        }
    }

    if ($normalized['season_id'] > 0 && $normalized['sequence_order'] > 0 && competition_sequence_order_exists($normalized['season_id'], $normalized['sequence_order'], $competitionId)) {
        $errors['sequence_order'] = 'Sequence order already exists for the selected season.';
    }

    if ($normalized['type'] === 'league') {
        if ($normalized['number_of_teams'] < 2) {
            $errors['number_of_teams'] = 'League competitions must define at least 2 teams.';
        }

        if ($normalized['points_win'] < $normalized['points_draw'] || $normalized['points_draw'] < $normalized['points_loss']) {
            $errors['points_win'] = 'Points configuration must follow win >= draw >= loss.';
        }
    }

    if (in_array($normalized['type'], ['group_stage', 'mixed'], true)) {
        if ($normalized['number_of_teams'] < 4) {
            $errors['number_of_teams'] = 'Group-stage competitions must define at least 4 teams.';
        }

        if ($normalized['group_count'] < 2 || $normalized['group_count'] > 26) {
            $errors['group_count'] = 'Group count must be between 2 and 26.';
        }

        if ($normalized['qualifiers_per_group'] < 1 || $normalized['qualifiers_per_group'] > 2) {
            $errors['qualifiers_per_group'] = 'Qualifiers per group must be 1 or 2 for the current progression engine.';
        }

        if ($normalized['group_count'] > 0 && $normalized['number_of_teams'] > 0 && $normalized['number_of_teams'] % $normalized['group_count'] !== 0) {
            $errors['group_count'] = 'Team count must divide evenly across the selected number of groups.';
        }

        if ($normalized['group_count'] > 0 && $normalized['number_of_teams'] > 0) {
            $teamsPerGroup = (int) ($normalized['number_of_teams'] / max(1, $normalized['group_count']));
            if ($normalized['qualifiers_per_group'] >= $teamsPerGroup) {
                $errors['qualifiers_per_group'] = 'Qualifiers per group must be lower than the number of teams in each group.';
            }

            $totalQualifiers = $normalized['group_count'] * $normalized['qualifiers_per_group'];
            if ($totalQualifiers < 2 || ($totalQualifiers & ($totalQualifiers - 1)) !== 0) {
                $errors['qualifiers_per_group'] = 'Total qualifiers must produce a power-of-two knockout bracket.';
            }
        }

        if ($normalized['points_win'] < $normalized['points_draw'] || $normalized['points_draw'] < $normalized['points_loss']) {
            $errors['points_win'] = 'Points configuration must follow win >= draw >= loss.';
        }
    }

    return $normalized;
}

function competition_status_badge_class(string $status): string
{
    return match ($status) {
        'active' => 'badge-soft-success',
        'paused' => 'bg-warning-subtle text-warning-emphasis',
        'open' => 'bg-info-subtle text-info-emphasis',
        'draft' => 'text-bg-light',
        'completed' => 'bg-secondary-subtle text-secondary-emphasis',
        'cancelled' => 'badge-soft-danger',
        default => 'text-bg-light',
    };
}
