<?php
declare(strict_types=1);

require __DIR__ . '/lib.php';

header('Content-Type: application/json; charset=UTF-8');
header('Cache-Control: no-store');
header('X-Content-Type-Options: nosniff');

function leaderboard_reply(array $payload, int $status = 200): never {
    http_response_code($status);
    echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}

function leaderboard_game(string $key): array {
    $catalog = game_leaderboard_catalog();
    if (!isset($catalog[$key])) {
        leaderboard_reply(['ok' => false, 'error' => '未知游戏'], 404);
    }
    return $catalog[$key];
}

function leaderboard_mode(array $game, string $mode): string {
    if ($mode === '' || !isset($game['modes'][$mode])) {
        leaderboard_reply(['ok' => false, 'error' => '未知游戏模式'], 422);
    }
    return $mode;
}

function leaderboard_row(array $row): array {
    return [
        'rank' => (int)($row['rank'] ?? 0),
        'username' => (string)$row['username'],
        'level' => credit_level((int)$row['credit']),
        'vip' => (int)$row['is_vip'] === 1,
        'score' => (float)$row['score_value'],
        'duration_ms' => (int)$row['duration_ms'],
        'achieved_at' => (string)$row['achieved_at'],
    ];
}

$pdo = db();

if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    $gameKey = trim((string)($_GET['game'] ?? ''));
    $game = leaderboard_game($gameKey);
    $mode = leaderboard_mode($game, trim((string)($_GET['mode'] ?? array_key_first($game['modes']))));
    $order = $game['metric'] === 'time'
        ? 'gs.duration_ms ASC, gs.score_value ASC, gs.achieved_at ASC'
        : 'gs.score_value DESC, gs.duration_ms ASC, gs.achieved_at ASC';
    $st = $pdo->prepare("SELECT gs.*, u.username, u.credit, u.is_vip
        FROM game_scores gs JOIN users u ON u.id=gs.user_id
        WHERE gs.game_key=? AND gs.mode=? ORDER BY $order");
    $st->execute([$gameKey, $mode]);
    $rows = [];
    $lastResult = null;
    $lastRank = 0;
    foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $index => $row) {
        $resultKey = $game['metric'] === 'time'
            ? (string)$row['duration_ms'] . ':' . (string)$row['score_value']
            : (string)$row['score_value'] . ':' . (string)$row['duration_ms'];
        if ($resultKey !== $lastResult) {
            $lastRank = $index + 1;
            $lastResult = $resultKey;
        }
        $row['rank'] = $lastRank;
        $rows[] = leaderboard_row($row);
    }

    $me = null;
    if (is_logged_in()) {
        $st = $pdo->prepare('SELECT gs.*, u.username, u.credit, u.is_vip FROM game_scores gs JOIN users u ON u.id=gs.user_id WHERE gs.user_id=? AND gs.game_key=? AND gs.mode=?');
        $st->execute([current_user_id(), $gameKey, $mode]);
        $mine = $st->fetch(PDO::FETCH_ASSOC);
        if ($mine) {
            $condition = $game['metric'] === 'time'
                ? '(duration_ms < ? OR (duration_ms = ? AND score_value < ?))'
                : '(score_value > ? OR (score_value = ? AND duration_ms < ?))';
            $values = $game['metric'] === 'time'
                ? [(int)$mine['duration_ms'], (int)$mine['duration_ms'], (float)$mine['score_value']]
                : [(float)$mine['score_value'], (float)$mine['score_value'], (int)$mine['duration_ms']];
            $rankSt = $pdo->prepare("SELECT COUNT(*) FROM game_scores WHERE game_key=? AND mode=? AND $condition");
            $rankSt->execute(array_merge([$gameKey, $mode], $values));
            $mine['rank'] = (int)$rankSt->fetchColumn() + 1;
            $me = leaderboard_row($mine);
        }
    }

    $user = current_user();
    leaderboard_reply([
        'ok' => true,
        'game' => $gameKey,
        'title' => $game['title'],
        'metric' => $game['metric'],
        'direction' => $game['direction'],
        'mode' => $mode,
        'modes' => $game['modes'],
        'rows' => $rows,
        'me' => $me,
        'user' => $user ? ['username' => $user['username'], 'level' => credit_level((int)$user['credit']), 'vip' => (int)$user['is_vip'] === 1] : null,
        'csrf' => csrf_token(),
        'login_url' => BASE_URL . '/login.php?next=' . urlencode('/'),
    ]);
}

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    header('Allow: GET, POST');
    leaderboard_reply(['ok' => false, 'error' => 'Method Not Allowed'], 405);
}

$raw = file_get_contents('php://input');
$data = json_decode($raw ?: '', true);
if (!is_array($data)) {
    leaderboard_reply(['ok' => false, 'error' => '请求格式无效'], 400);
}
if (!is_logged_in()) {
    leaderboard_reply(['ok' => false, 'error' => '登录网站账号后，成绩才会进入全站榜。', 'login' => BASE_URL . '/login.php'], 401);
}
$token = (string)($data['csrf'] ?? '');
if ($token === '' || !hash_equals((string)($_SESSION['csrf'] ?? ''), $token)) {
    leaderboard_reply(['ok' => false, 'error' => '页面已过期，请刷新后再试'], 403);
}

$gameKey = trim((string)($data['game'] ?? ''));
$game = leaderboard_game($gameKey);
$mode = leaderboard_mode($game, trim((string)($data['mode'] ?? '')));
$action = (string)($data['action'] ?? '');

$_SESSION['arena_runs'] = is_array($_SESSION['arena_runs'] ?? null) ? $_SESSION['arena_runs'] : [];
$now = microtime(true);
foreach ($_SESSION['arena_runs'] as $runToken => $run) {
    if (!is_array($run) || $now - (float)($run['started'] ?? 0) > 86400) {
        unset($_SESSION['arena_runs'][$runToken]);
    }
}

if ($action === 'start') {
    if (count($_SESSION['arena_runs']) >= 24) {
        array_shift($_SESSION['arena_runs']);
    }
    $runToken = bin2hex(random_bytes(18));
    $_SESSION['arena_runs'][$runToken] = ['game' => $gameKey, 'mode' => $mode, 'started' => $now];
    leaderboard_reply(['ok' => true, 'run_token' => $runToken]);
}

if ($action !== 'submit') {
    leaderboard_reply(['ok' => false, 'error' => '未知操作'], 422);
}

$lastSubmit = (float)($_SESSION['arena_last_submit'] ?? 0);
if ($now - $lastSubmit < 1.2) {
    leaderboard_reply(['ok' => false, 'error' => '成绩提交得太快，请稍后再试'], 429);
}

$runToken = (string)($data['run_token'] ?? '');
$run = $_SESSION['arena_runs'][$runToken] ?? null;
unset($_SESSION['arena_runs'][$runToken]);
if (!is_array($run) || ($run['game'] ?? '') !== $gameKey || ($run['mode'] ?? '') !== $mode) {
    leaderboard_reply(['ok' => false, 'error' => '本局凭证无效，请重新开始一局'], 422);
}

$score = filter_var($data['score'] ?? null, FILTER_VALIDATE_FLOAT);
$duration = filter_var($data['duration_ms'] ?? null, FILTER_VALIDATE_INT);
if ($score === false || $duration === false || !is_finite((float)$score)) {
    leaderboard_reply(['ok' => false, 'error' => '成绩数据无效'], 422);
}
$score = (float)$score;
$duration = (int)$duration;
if ($score < $game['min_score'] || $score > $game['max_score'] || $duration < $game['min_duration'] || $duration > $game['max_duration']) {
    leaderboard_reply(['ok' => false, 'error' => '成绩超出合理范围，未写入排行榜'], 422);
}
$serverElapsed = (int)round(($now - (float)$run['started']) * 1000);
if ($duration + 2500 < $serverElapsed) {
    leaderboard_reply(['ok' => false, 'error' => '本局计时与服务器记录不一致，未写入排行榜'], 422);
}

$meta = is_array($data['meta'] ?? null) ? $data['meta'] : [];
$metaJson = json_encode($meta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($metaJson === false || strlen($metaJson) > 1000) {
    $metaJson = '{}';
}

$uid = (int)current_user_id();
$st = $pdo->prepare('SELECT * FROM game_scores WHERE user_id=? AND game_key=? AND mode=?');
$st->execute([$uid, $gameKey, $mode]);
$old = $st->fetch(PDO::FETCH_ASSOC);
$better = !$old;
if ($old) {
    $better = $game['metric'] === 'time'
        ? ($duration < (int)$old['duration_ms'] || ($duration === (int)$old['duration_ms'] && $score < (float)$old['score_value']))
        : ($score > (float)$old['score_value'] || ($score === (float)$old['score_value'] && $duration < (int)$old['duration_ms']));
}

if ($better) {
    $st = $pdo->prepare("INSERT INTO game_scores(user_id,game_key,mode,score_value,duration_ms,meta_json)
        VALUES(?,?,?,?,?,?)
        ON CONFLICT(user_id,game_key,mode) DO UPDATE SET
            score_value=excluded.score_value,
            duration_ms=excluded.duration_ms,
            meta_json=excluded.meta_json,
            achieved_at=datetime('now','localtime'),
            updated_at=datetime('now','localtime')");
    $st->execute([$uid, $gameKey, $mode, $score, $duration, $metaJson]);
}
$_SESSION['arena_last_submit'] = $now;

$st = $pdo->prepare('SELECT * FROM game_scores WHERE user_id=? AND game_key=? AND mode=?');
$st->execute([$uid, $gameKey, $mode]);
$personal = $st->fetch(PDO::FETCH_ASSOC);
$condition = $game['metric'] === 'time'
    ? '(duration_ms < ? OR (duration_ms = ? AND score_value < ?))'
    : '(score_value > ? OR (score_value = ? AND duration_ms < ?))';
$values = $game['metric'] === 'time'
    ? [(int)$personal['duration_ms'], (int)$personal['duration_ms'], (float)$personal['score_value']]
    : [(float)$personal['score_value'], (float)$personal['score_value'], (int)$personal['duration_ms']];
$rankSt = $pdo->prepare("SELECT COUNT(*) FROM game_scores WHERE game_key=? AND mode=? AND $condition");
$rankSt->execute(array_merge([$gameKey, $mode], $values));

leaderboard_reply([
    'ok' => true,
    'saved' => $better,
    'rank' => (int)$rankSt->fetchColumn() + 1,
    'message' => $better ? '新纪录已进入全站排行榜' : '本局已完成，个人最佳纪录未被刷新',
]);
