<?php

header('Content-Type: application/json');
require_once __DIR__ . '/../includes/cors.php';

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }

require_once __DIR__ . '/../includes/supabase.php';
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/chess.php';

$token = requireAuth();
$userId = getUserId($token);
$input = getInput();
$action = $input['action'] ?? '';
$db = supabase($token);

switch ($action) {
    case 'queue':
        handleQueue($db, $userId, $input);
        break;
    case 'dequeue':
        handleDequeue($db, $userId, $input);
        break;
    case 'status':
        handleStatus($db, $userId, $input);
        break;
    default:
        jsonError('Invalid action');
}

function handleQueue($db, string $userId, array $input): void {
    $gameKey = $input['game_key'] ?? 'chess';
    $timeControl = $input['time_control'] ?? 'rapid_10_0';

    // Validate against the real `public.time_control` enum. This list used to
    // include rapid_15_15, classical_30_0, classical_30_20 and classical_60_30,
    // none of which the database accepts — a player picking one got past this
    // check and then the match insert failed.
    if (!in_array($timeControl, TIME_CONTROL_ENUM, true)) {
        jsonError('Invalid time control');
    }

    $validGames = ['chess', 'ludo', 'domino', 'backgammon'];
    if (!in_array($gameKey, $validGames, true)) {
        jsonError('Invalid game key');
    }

    $sdb = supabaseService();

    // Anti-abuse: check if player can queue
    $canQueue = supabaseRpc('can_player_queue', ['p_player_id' => $userId]);
    if (is_array($canQueue) && isset($canQueue['allowed']) && !$canQueue['allowed']) {
        jsonResponse(['error' => 'cooldown', 'seconds_remaining' => $canQueue['seconds_remaining'] ?? 60]);
        return;
    }

    // STEP 0: If an opponent has already claimed us, hand that match back
    // instead of re-queueing.
    //
    // The next step used to delete *all* of this player's queue rows
    // unconditionally, including one already marked `matched` and carrying a
    // match_id. The waiting player then never learned about their own match and
    // their opponent sat alone in a game — "I didn't get connected to the person
    // in front of me". The 60s retry in queue.js hits this path every time.
    if (liveClaimedMatch($sdb, $userId)) {
        handleStatus($db, $userId, $input);
        return;
    }

    // STEP 1: Drop our own waiting rows. A *live* claim is protected by the
    // check above; anything else, including a claim whose match is long dead,
    // is cleared here.
    $sdb->delete('matchmaking_queue', [
        'player_id' => 'eq.' . $userId,
        'status' => 'neq.matched',
    ]);

    // STEP 2: Clean stale entries from other players (older than 90 seconds)
    $staleTime = gmdate('c', time() - 90);
    $sdb->delete('matchmaking_queue', [
        'status' => 'eq.waiting',
        'queued_at' => 'lt.' . $staleTime
    ]);

    // STEP 3: Get player rating
    $profiles = $sdb->get('profiles', ['id' => 'eq.' . $userId, 'select' => 'elo_rapid,elo_blitz,elo_bullet', 'limit' => 1]);
    $profile = is_array($profiles) && !empty($profiles) ? $profiles[0] : [];
    $rating = $profile['elo_rapid'] ?? $profile['elo_blitz'] ?? 1200;

    // STEP 4: Get block list
    $blockedIds = [];
    $blocks = $sdb->get('player_blocks', [
        'or' => "(player_id.eq.{$userId},blocked_id.eq.{$userId})",
        'type' => 'eq.block',
        'select' => 'player_id,blocked_id'
    ]);
    if (is_array($blocks) && !isset($blocks['error'])) {
        foreach ($blocks as $b) {
            $blockedIds[] = ($b['player_id'] === $userId) ? $b['blocked_id'] : $b['player_id'];
        }
    }

    // STEP 5: Search for available opponent.
    // This was a hand-rolled curl that re-implemented auth and URL building —
    // the only query in the file that bypassed the client. Going through the
    // client keeps one code path for headers, timeouts and error shape.
    $excludeIds = array_merge([$userId], $blockedIds);
    $opponents = $sdb->get('matchmaking_queue', [
        'game_key' => 'eq.' . $gameKey,
        'time_control' => 'eq.' . $timeControl,
        'status' => 'eq.waiting',
        'player_id' => 'not.in.(' . implode(',', $excludeIds) . ')',
        'select' => 'id,player_id,rating',
        'order' => 'queued_at.asc',
        'limit' => 1,
    ]);

    if (!empty($opponents) && !isset($opponents['error']) && isset($opponents[0])) {
        $opponent = $opponents[0];

        // STEP 6: Atomically claim opponent — mark them as matched FIRST
        // Use a conditional update: only succeeds if still 'waiting'
        $claimResult = $sdb->update('matchmaking_queue', [
            'status' => 'matched',
            'matched_with' => $userId
        ], [
            'id' => 'eq.' . $opponent['id'],
            'status' => 'eq.waiting'
        ]);

        // If claim failed (empty result = no rows matched), opponent was already taken
        if (empty($claimResult) || isset($claimResult['error'])) {
            // Opponent was grabbed by someone else — add self to queue and wait
            $sdb->insert('matchmaking_queue', [
                'player_id' => $userId,
                'game_key' => $gameKey,
                'time_control' => $timeControl,
                'rating' => $rating,
                'status' => 'waiting'
            ]);
            jsonResponse(['queued' => true]);
            return;
        }

        // STEP 7: Create match
        $isWhite = rand(0, 1) === 0;
        $whiteId = $isWhite ? $userId : $opponent['player_id'];
        $blackId = $isWhite ? $opponent['player_id'] : $userId;

        // Derive the clock from the time control itself rather than bucketing
        // by prefix, which gave every blitz variant a flat 5 minutes and ignored
        // increments entirely.
        $clock = chessTimeControlMs($timeControl);
        $initialTime = $clock['initial'];

        $match = $sdb->insert('matches', [
            'game_key' => $gameKey,
            'match_type' => 'rated',
            'white_player_id' => $whiteId,
            'black_player_id' => $blackId,
            'status' => 'in_progress',
            'time_control' => $timeControl,
            'initial_time_ms' => $initialTime,
            'increment_ms' => $clock['increment'],
            'white_time_remaining_ms' => $initialTime,
            'black_time_remaining_ms' => $initialTime,
            'starting_fen' => CHESS_START_FEN,
            'current_fen' => CHESS_START_FEN,
            'moves' => [],
            'move_count' => 0,
            'game_state' => (object)[],
            'is_rated' => true,
            'started_at' => date('c')
        ]);
        $matchId = $match[0]['id'] ?? $match['id'] ?? null;

        if ($matchId) {
            // Update opponent's queue entry with match_id
            $sdb->update('matchmaking_queue', [
                'match_id' => $matchId
            ], ['id' => 'eq.' . $opponent['id']]);

            mpLog($matchId, $gameKey, $userId, 'match_created', [
                'white' => $whiteId, 'black' => $blackId, 'time_control' => $timeControl
            ]);

            $myColor = $isWhite ? 'w' : 'b';
            jsonResponse(['match_id' => $matchId, 'color' => $myColor, 'opponent_id' => $opponent['player_id']]);
            return;
        }
    }

    // No opponent found — add self to queue
    $sdb->insert('matchmaking_queue', [
        'player_id' => $userId,
        'game_key' => $gameKey,
        'time_control' => $timeControl,
        'rating' => $rating,
        'status' => 'waiting'
    ]);
    jsonResponse(['queued' => true]);
}


function handleStatus($db, string $userId, array $input): void {
    $sdb = supabaseService();

    $claim = liveClaimedMatch($sdb, $userId);
    if (!$claim) {
        jsonResponse(['waiting' => true]);
    }

    $match = $claim['match'];
    $color = ($match['white_player_id'] === $userId) ? 'w' : 'b';

    // The hand-off has been delivered, so the queue row has done its job.
    $sdb->delete('matchmaking_queue', ['id' => 'eq.' . $claim['queue_id']]);

    jsonResponse([
        'match_id' => $match['id'],
        'color' => $color,
        'opponent_id' => $color === 'w' ? $match['black_player_id'] : $match['white_player_id'],
    ]);
}

/**
 * A queue claim worth acting on: one that points at a match which is genuinely
 * still playable.
 *
 * Production carries queue rows marked `matched` that are months old and point
 * at long-finished games. Honouring one of those would drop a player straight
 * into a dead match, so the referenced match is checked and a stale row is
 * cleared on the spot rather than left to mislead the next request.
 *
 * @return array{queue_id: string, match: array}|null
 */
function liveClaimedMatch($sdb, string $userId): ?array {
    $rows = $sdb->get('matchmaking_queue', [
        'player_id' => 'eq.' . $userId,
        'status' => 'eq.matched',
        'select' => 'id,match_id,queued_at',
        'order' => 'queued_at.desc',
        'limit' => 5,
    ]);
    if (!is_array($rows) || isset($rows['error']) || empty($rows)) return null;

    $live = null;
    foreach ($rows as $row) {
        $stale = true;

        if (!empty($row['match_id'])) {
            $m = $sdb->get('matches', [
                'id' => 'eq.' . $row['match_id'],
                'select' => 'id,white_player_id,black_player_id,status',
                'limit' => 1,
            ]);
            $m = (is_array($m) && !empty($m) && !isset($m['error'])) ? $m[0] : null;

            $playable = $m
                && in_array($m['status'], ['waiting', 'ready', 'in_progress'], true)
                && ($m['white_player_id'] === $userId || $m['black_player_id'] === $userId);

            if ($playable && $live === null) {
                $live = ['queue_id' => $row['id'], 'match' => $m];
                $stale = false;
            }
        }

        if ($stale) $sdb->delete('matchmaking_queue', ['id' => 'eq.' . $row['id']]);
    }

    return $live;
}

function handleDequeue($db, string $userId, array $input): void {
    $sdb = supabaseService();

    // A row that has already been matched must survive: deleting it here (which
    // scene teardown does on every navigation) would destroy the hand-off and
    // strand the opponent in a game by themselves. Report the match instead.
    $claimed = $sdb->get('matchmaking_queue', [
        'player_id' => 'eq.' . $userId,
        'status' => 'eq.matched',
        'select' => 'match_id',
        'limit' => 1,
    ]);
    if (is_array($claimed) && !empty($claimed) && !isset($claimed['error']) && !empty($claimed[0]['match_id'])) {
        jsonResponse(['success' => false, 'matched' => true, 'match_id' => $claimed[0]['match_id']]);
    }

    $sdb->delete('matchmaking_queue', [
        'player_id' => 'eq.' . $userId,
        'status' => 'eq.waiting',
    ]);
    jsonResponse(['success' => true]);
}

function mpLog(string $matchId, string $gameKey, string $playerId, string $event, $payload = []): void {
    supabaseRpc('log_mp_event', [
        'p_match_id' => $matchId,
        'p_game_key' => $gameKey,
        'p_player_id' => $playerId,
        'p_event' => $event,
        'p_payload' => is_array($payload) ? $payload : []
    ]);
}
