<?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';

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

$db = supabase($token);

switch ($action) {
    case 'start':
        handleStart($db, $userId, $input);
        break;
    case 'move':
        handleGameMove($db, $userId, $input);
        break;
    case 'resign':
        handleResign($db, $userId, $input);
        break;
    case 'draw':
        handleDraw($db, $userId, $input);
        break;
    case 'complete':
        handleComplete($db, $userId, $input);
        break;
    case 'get':
        handleGet($db, $userId, $input);
        break;
    case 'heartbeat':
        handleChessHeartbeat($userId, $input);
        break;
    case 'leave':
        handleChessLeave($userId, $input);
        break;
    case 'find-active-match':
        handleFindActiveMatch($userId, $input);
        break;
    default:
        jsonError('Invalid action');
}

function handleGet($db, string $userId, array $input): void {
    $matchId = $_GET['match_id'] ?? ($input['match_id'] ?? '');
    if (!$matchId) jsonError('match_id required');
    $sdb = supabaseService();
    $matches = $sdb->get('matches', ['id' => 'eq.' . $matchId, 'select' => '*', 'limit' => 1]);
    $match = is_array($matches) && !empty($matches) && !isset($matches['error']) ? $matches[0] : null;
    if (!$match) jsonError('Match not found', 404);

    // Server-side turn timeout enforcement (chess uses time control, fallback 120s)
    if ($match['status'] === 'in_progress') {
        $timeout = supabaseRpc('enforce_turn_timeout', [
            'p_game_key' => 'chess',
            'p_match_id' => $matchId,
            'p_timeout_seconds' => 120
        ]);
        if (!empty($timeout['timeout'])) {
            $match['_turn_timed_out'] = true;
            $match['_timeout_player'] = $timeout['player_index'] ?? null;
            $match['_replace_with_bot'] = $timeout['replace_with_bot'] ?? false;
            $matches = $sdb->get('matches', ['id' => 'eq.' . $matchId, 'select' => '*', 'limit' => 1]);
            if (!empty($matches) && !isset($matches['error'])) {
                $match = $matches[0];
                $match['_turn_timed_out'] = true;
                $match['_timeout_player'] = $timeout['player_index'] ?? null;
                $match['_replace_with_bot'] = $timeout['replace_with_bot'] ?? false;
            }
        }
    }

    jsonResponse($match);
}

function handleStart($db, string $userId, array $input): void {
    $gameKey = $input['game_key'] ?? 'chess';
    $mode = $input['mode'] ?? 'bot';
    $timeControl = $input['time_control'] ?? 'rapid_10_0';
    $validTimeControls = ['bullet_1_0','bullet_2_1','blitz_3_0','blitz_3_2','blitz_5_0','blitz_5_3','rapid_10_0','rapid_10_5','rapid_15_10','classical_30_0','classical_30_20','standard'];
    if (!in_array($timeControl, $validTimeControls, true)) $timeControl = 'rapid_10_0';
    $opponentId = $input['opponent_id'] ?? null;
    $botId = $input['bot_id'] ?? null;

    $data = [
        'game_key' => $gameKey,
        'white_player_id' => $userId,
        'black_player_id' => $opponentId,
        'status' => 'in_progress',
        'time_control' => $timeControl,
        'current_fen' => 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
        'moves' => [],
        'metadata' => ['mode' => $mode, 'bot_id' => $botId]
    ];

    $result = $db->insert('matches', $data);
    if (isset($result['error'])) jsonError($result['error']);
    jsonResponse($result[0] ?? $result);
}

function handleGameMove($db, string $userId, array $input): void {
    $matchId = $input['match_id'] ?? '';
    if (!$matchId) jsonError('match_id required');

    $sdb = supabaseService();

    // Auth: verify caller is a player in this match
    $match = $sdb->get('matches', ['id' => 'eq.' . $matchId, 'select' => 'white_player_id,black_player_id,status', 'limit' => 1]);
    $match = (is_array($match) && !empty($match) && !isset($match['error'])) ? $match[0] : null;
    if (!$match) jsonError('Match not found', 404);
    if ($match['white_player_id'] !== $userId && $match['black_player_id'] !== $userId) {
        jsonError('Not authorized for this match', 403);
    }
    if ($match['status'] !== 'in_progress') jsonError('Match not in progress');

    $update = ['updated_at' => date('c')];

    if (!empty($input['fen'])) $update['current_fen'] = $input['fen'];
    if (!empty($input['move'])) {
        $moves = is_string($input['move']) ? json_decode($input['move'], true) : $input['move'];
        $update['moves'] = is_array($moves) ? $moves : [];
    }
    if (isset($input['move_count'])) $update['move_count'] = intval($input['move_count']);
    if (isset($input['white_time_remaining_ms'])) $update['white_time_remaining_ms'] = intval($input['white_time_remaining_ms']);
    if (isset($input['black_time_remaining_ms'])) $update['black_time_remaining_ms'] = intval($input['black_time_remaining_ms']);

    // game_state: use atomic merge via DB function (prevents race conditions with draw offers, emotes)
    $gameStatePatch = null;
    if (!empty($input['game_state'])) {
        $gameStatePatch = is_string($input['game_state']) ? (json_decode($input['game_state'], true) ?: []) : $input['game_state'];
    }

    // If we only have game_state (no board columns), use atomic merge only
    $hasBoardUpdate = isset($update['current_fen']) || isset($update['moves']) || isset($update['move_count']);

    if ($gameStatePatch && !$hasBoardUpdate) {
        // Pure game_state update (draw offer, emote, etc.) — use atomic merge
        supabaseRpc('merge_game_state', [
            'p_table' => 'matches',
            'p_match_id' => $matchId,
            'p_patch' => $gameStatePatch
        ]);
        mpLog($matchId, 'chess', $userId, 'game_state_merge', $gameStatePatch);
        jsonResponse(['success' => true]);
        return;
    }

    if ($gameStatePatch && $hasBoardUpdate) {
        // Move + game_state: atomic merge game_state first, then update board columns
        supabaseRpc('merge_game_state', [
            'p_table' => 'matches',
            'p_match_id' => $matchId,
            'p_patch' => $gameStatePatch
        ]);
    }

    $result = $sdb->update('matches', $update, ['id' => 'eq.' . $matchId]);

    if (isset($result['error'])) jsonError($result['error']);

    mpLog($matchId, 'chess', $userId, 'move', [
        'move_count' => $input['move_count'] ?? null,
        'fen_short' => substr($input['fen'] ?? '', 0, 30)
    ]);
    jsonResponse(['success' => true]);
}

function handleResign($db, string $userId, array $input): void {
    $matchId = $input['match_id'] ?? '';
    if (!$matchId) jsonError('match_id required');

    $sdb = supabaseService();
    $matches = $sdb->get('matches', ['id' => 'eq.' . $matchId, 'select' => 'id,white_player_id,black_player_id,status', 'limit' => 1]);
    $match = (is_array($matches) && !empty($matches) && !isset($matches['error'])) ? $matches[0] : null;
    if (!$match) jsonError('Match not found', 404);
    if ($match['status'] === 'completed') jsonError('Match already ended');
    if ($match['white_player_id'] !== $userId && $match['black_player_id'] !== $userId) {
        jsonError('Not authorized for this match', 403);
    }

    $isWhite = $match['white_player_id'] === $userId;
    $result = $isWhite ? 'black_wins' : 'white_wins';

    $sdb->update('matches', [
        'status' => 'completed',
        'result' => $result,
        'completed_at' => date('c'),
        'updated_at' => date('c')
    ], ['id' => 'eq.' . $matchId]);

    mpLog($matchId, 'chess', $userId, 'resign', ['result' => $result]);
    jsonResponse(['result' => $result]);
}

function handleDraw($db, string $userId, array $input): void {
    $matchId = $input['match_id'] ?? '';
    if (!$matchId) jsonError('match_id required');

    $sdb = supabaseService();
    $matches = $sdb->get('matches', ['id' => 'eq.' . $matchId, 'select' => 'id,white_player_id,black_player_id,status', 'limit' => 1]);
    $match = (is_array($matches) && !empty($matches) && !isset($matches['error'])) ? $matches[0] : null;
    if (!$match) jsonError('Match not found', 404);
    if ($match['status'] === 'completed') jsonError('Match already ended');
    if ($match['white_player_id'] !== $userId && $match['black_player_id'] !== $userId) {
        jsonError('Not authorized for this match', 403);
    }

    // Use merge_game_state to preserve heartbeat/connections data
    supabaseRpc('merge_game_state', [
        'p_table' => 'matches',
        'p_match_id' => $matchId,
        'p_patch' => ['draw_accepted' => true]
    ]);

    $sdb->update('matches', [
        'status' => 'completed',
        'result' => 'draw',
        'completed_at' => date('c'),
        'updated_at' => date('c')
    ], ['id' => 'eq.' . $matchId]);

    mpLog($matchId, 'chess', $userId, 'draw_accepted', []);
    jsonResponse(['result' => 'draw']);
}

function handleComplete($db, string $userId, array $input): void {
    $matchId = $input['match_id'] ?? '';
    $result = $input['result'] ?? '';
    $fen = $input['fen'] ?? '';
    $pgn = $input['pgn'] ?? '';
    $winners = $input['winners'] ?? [];
    $reason = $input['reason'] ?? 'normal';

    if (!$matchId) jsonError('match_id required');

    // Validate result against known enum values
    $validResults = ['white_wins','black_wins','draw','stalemate','timeout_white','timeout_black',
        'abandon_white','abandon_black','resign_white','resign_black','checkmate_white','checkmate_black',
        'bot_win','bot_loss','bot_draw'];
    if ($result && !in_array($result, $validResults, true)) {
        jsonError('Invalid result value');
    }

    $sdb = supabaseService();

    // Auth: verify caller is a player in this match
    $matchData = $sdb->get('matches', ['id' => 'eq.' . $matchId, 'select' => 'white_player_id,black_player_id,status', 'limit' => 1]);
    $matchData = (is_array($matchData) && !empty($matchData) && !isset($matchData['error'])) ? $matchData[0] : null;
    if (!$matchData) jsonError('Match not found', 404);
    if ($matchData['white_player_id'] !== $userId && $matchData['black_player_id'] !== $userId) {
        jsonError('Not authorized for this match', 403);
    }
    if ($matchData['status'] === 'completed') jsonError('Match already ended');

    // Validate winners are actual participants (prevent coin exploit)
    $validPlayers = [$matchData['white_player_id'], $matchData['black_player_id']];
    if (!empty($winners)) {
        foreach ($winners as $w) {
            if (!in_array($w, $validPlayers, true)) {
                jsonError('Invalid winner — not a match participant', 403);
            }
        }
    }

    $sdb->update('matches', [
        'status' => 'completed',
        'result' => $result,
        'current_fen' => $fen,
        'pgn' => $pgn,
        'completed_at' => date('c'),
        'updated_at' => date('c')
    ], ['id' => 'eq.' . $matchId]);

    // Use universal rewards function
    $rewardResult = supabaseRpc('complete_match', [
        'p_game_key' => 'chess',
        'p_match_id' => $matchId,
        'p_winners' => json_encode($winners ?: [$userId]),
        'p_reason' => $reason
    ]);

    // Tournament result reporting hook
    reportTournamentResult($db, $matchId, $result, $userId);

    // Check achievements
    $profiles = $sdb->get('profiles', ['id' => 'eq.' . $userId, 'select' => 'games_played,total_wins,win_streak,elo_rapid', 'limit' => 1]);
    $profile = is_array($profiles) && !empty($profiles) ? $profiles[0] : null;
    if ($profile) {
        checkGameAchievements($sdb, $userId, $profile, $profile['elo_rapid'] ?? 1200);
    }

    jsonResponse($rewardResult ?: ['success' => true, 'result' => $result]);
}

function calculateElo(int $playerRating, int $opponentRating, float $score): int {
    $k = 32;
    if ($playerRating > 2400) $k = 16;
    elseif ($playerRating > 2000) $k = 24;

    $expected = 1.0 / (1.0 + pow(10, ($opponentRating - $playerRating) / 400.0));
    $newRating = round($playerRating + $k * ($score - $expected));
    return max(100, $newRating);
}

function getRatingColumn(string $timeControl): string {
    if (strpos($timeControl, 'bullet') !== false) return 'elo_bullet';
    if (strpos($timeControl, 'blitz') !== false) return 'elo_blitz';
    if (strpos($timeControl, 'classical') !== false) return 'elo_classical';
    return 'elo_rapid';
}

function getTimeControlType(string $timeControl): string {
    if (strpos($timeControl, 'bullet') !== false) return 'bullet';
    if (strpos($timeControl, 'blitz') !== false) return 'blitz';
    if (strpos($timeControl, 'classical') !== false) return 'classical';
    return 'rapid';
}

function reportTournamentResult($db, string $matchId, string $result, string $userId): void {
    $sdb = supabaseService();
    $matches = $sdb->get('matches', ['id' => 'eq.' . $matchId, 'select' => 'tournament_id,metadata', 'limit' => 1]);
    $match = is_array($matches) && !empty($matches) && !isset($matches['error']) ? $matches[0] : null;

    if (!$match || empty($match['tournament_id'])) return;

    $metadata = json_decode($match['metadata'] ?? '{}', true);
    if (!empty($metadata['tournament_reported'])) return;

    // Delegate to tournament-match.php logic via internal call
    $payload = json_encode([
        'action' => 'report-result',
        'match_id' => $matchId,
        'tournament_id' => $match['tournament_id'],
        'result' => $result
    ]);

    // Use stream context for internal request
    $url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http')
        . '://' . ($_SERVER['HTTP_HOST'] ?? 'localhost') . '/api/tournament-match.php';

    $ctx = stream_context_create(['http' => [
        'method' => 'POST',
        'header' => "Content-Type: application/json\r\nAuthorization: Bearer " . (getAuthToken() ?? '') . "\r\n",
        'content' => $payload,
        'timeout' => 5
    ]]);

    @file_get_contents($url, false, $ctx);
}

function checkGameAchievements($sdb, string $userId, array $profileUpdates, int $newRating): void {
    $achievements = $sdb->get('achievements', ['select' => 'id,condition,coins_reward,xp_reward']);
    if (!is_array($achievements) || isset($achievements['error'])) return;

    $stats = [
        'wins' => $profileUpdates['total_wins'] ?? 0,
        'games_played' => $profileUpdates['games_played'] ?? 0,
        'win_streak' => $profileUpdates['win_streak'] ?? 0,
        'elo' => $newRating,
    ];

    foreach ($achievements as $a) {
        $condition = $a['condition'];
        if (is_string($condition)) $condition = json_decode($condition, true);
        if (!$condition) continue;

        $type = $condition['type'] ?? '';
        $target = $condition['count'] ?? 1;

        if (!isset($stats[$type])) continue;
        if ($stats[$type] < $target) continue;

        // Check if already completed
        $existing = $sdb->get('player_achievements', [
            'player_id' => 'eq.' . $userId,
            'achievement_id' => 'eq.' . $a['id'],
            'limit' => 1
        ]);

        if (is_array($existing) && !isset($existing['error']) && !empty($existing)) {
            if ($existing[0]['completed'] ?? false) continue;
            $sdb->update('player_achievements', [
                'progress' => $stats[$type],
                'completed' => true,
                'completed_at' => gmdate('c')
            ], ['player_id' => 'eq.' . $userId, 'achievement_id' => 'eq.' . $a['id']]);
        } else {
            $sdb->insert('player_achievements', [
                'player_id' => $userId,
                'achievement_id' => $a['id'],
                'progress' => $stats[$type],
                'completed' => true,
                'completed_at' => gmdate('c')
            ]);
        }

        // Grant rewards
        $coins = intval($a['coins_reward'] ?? 0);
        if ($coins > 0) {
            $sdb->rpc('award_coins', ['p_player_id' => $userId, 'p_amount' => $coins, 'p_reason' => 'Achievement: ' . $a['id']]);
        }
        $xp = intval($a['xp_reward'] ?? 0);
        if ($xp > 0) {
            $profiles = $sdb->get('profiles', ['id' => 'eq.' . $userId, 'select' => 'xp,level', 'limit' => 1]);
            if (is_array($profiles) && !empty($profiles)) {
                $currentXp = ($profiles[0]['xp'] ?? 0) + $xp;
                $newLevel = floor($currentXp / 500) + 1;
                $sdb->update('profiles', ['xp' => $currentXp, 'level' => $newLevel], ['id' => 'eq.' . $userId]);
            }
        }
    }
}

function handleChessHeartbeat(string $userId, array $input): void {
    $matchId = $input['match_id'] ?? '';
    if (!$matchId) jsonError('match_id required');

    $result = supabaseRpc('match_heartbeat', [
        'p_game_key' => 'chess',
        'p_match_id' => $matchId,
        'p_player_id' => $userId
    ]);

    jsonResponse($result ?: ['status' => 'ok']);
}

function handleChessLeave(string $userId, array $input): void {
    $matchId = $input['match_id'] ?? '';
    if (!$matchId) jsonError('match_id required');

    $sdb = supabaseService();
    $matches = $sdb->get('matches', ['id' => 'eq.' . $matchId, 'select' => 'white_player_id,black_player_id,game_state', 'limit' => 1]);
    if (empty($matches) || isset($matches['error'])) jsonError('Not found');

    $raw = $matches[0]['game_state'] ?? null;
    $gameState = is_string($raw) ? (json_decode($raw, true) ?: []) : (is_array($raw) && !array_is_list($raw) ? $raw : []);
    $gameState['bot_replaced'] = $gameState['bot_replaced'] ?? [];
    $gameState['bot_replaced'][] = [
        'player_id' => $userId,
        'replaced_at' => date('c'),
        'permanent' => false
    ];

    $sdb->update('matches', [
        'game_state' => $gameState,
        'updated_at' => date('c')
    ], ['id' => 'eq.' . $matchId]);

    jsonResponse(['success' => true, 'bot_replaced' => true]);
}

function handleFindActiveMatch(string $userId, array $input): void {
    // Only allow finding your own active matches (prevent info disclosure)
    $playerId = $userId;

    $sdb = supabaseService();

    // Check chess matches
    $chess = $sdb->get('matches', [
        'or' => "(white_player_id.eq.{$playerId},black_player_id.eq.{$playerId})",
        'status' => 'eq.in_progress',
        'select' => 'id,game_key,white_player_id,black_player_id',
        'order' => 'started_at.desc',
        'limit' => 1
    ]);
    if (is_array($chess) && !isset($chess['error']) && !empty($chess)) {
        jsonResponse([
            'match_id' => $chess[0]['id'],
            'game_key' => $chess[0]['game_key'] ?? 'chess',
            'white_player_id' => $chess[0]['white_player_id'],
            'black_player_id' => $chess[0]['black_player_id']
        ]);
    }

    // Check ludo matches
    $ludo = $sdb->get('ludo_matches', [
        'status' => 'eq.in_progress',
        'select' => 'id,players',
        'order' => 'started_at.desc',
        'limit' => 10
    ]);
    if (is_array($ludo) && !isset($ludo['error'])) {
        foreach ($ludo as $m) {
            $players = is_string($m['players']) ? json_decode($m['players'], true) : ($m['players'] ?? []);
            foreach ($players as $p) {
                if (($p['id'] ?? '') === $playerId) {
                    jsonResponse(['match_id' => $m['id'], 'game_key' => 'ludo']);
                }
            }
        }
    }

    // Check domino matches
    $domino = $sdb->get('domino_matches', [
        'or' => "(player1_id.eq.{$playerId},player2_id.eq.{$playerId})",
        'status' => 'eq.in_progress',
        'select' => 'id',
        'order' => 'created_at.desc',
        'limit' => 1
    ]);
    if (is_array($domino) && !isset($domino['error']) && !empty($domino)) {
        jsonResponse(['match_id' => $domino[0]['id'], 'game_key' => 'domino']);
    }

    jsonResponse(['match_id' => null]);
}

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' => json_encode(is_array($payload) ? $payload : [])
    ]);
}
