<?php
/**
 * mtdk_api.php — みとどけ PHP APIゲートウェイ
 * 
 * 設置先: /html/zinro/mtdk_api.php
 * 
 * Docker FastAPI (api.py) では処理できないPHP固有のエンドポイントを集約。
 * ?mode=xxx で振り分け。
 * 
 * ── API構成 全体図 ──────────────────────────────────────
 * 
 *  api.php → Docker FastAPI (api.py)
 *    データ系: personal, multi_summary, monthly_*_rank 等
 *    認証系:   login, logout, me
 *    Wiki系:   wiki_save, comment_add, profile_get 等
 *    通知系:   notice
 *    全40+エンドポイント
 * 
 *  mtdk_api.php → このファイル（PHP直接）
 *    server_status   サーバハードウェア情報（/proc読み取り）
 *    ware_trips      割れ酉リスト（JSONファイル）
 *    notice          お知らせ取得（PHPフォールバック）
 *    health          簡易ヘルスチェック
 * 
 * ─────────────────────────────────────────────────────────
 * 
 * Chrome拡張での使用:
 *   MITDOK_API  = "https://mitdok.dokasen.com/zinro/api.php";      // Docker
 *   MITDOK_PHP  = "https://mitdok.dokasen.com/zinro/mtdk_api.php"; // このファイル
 */
declare(strict_types=1);
date_default_timezone_set('Asia/Tokyo');
mb_internal_encoding('UTF-8');

// ── CORS ──
header('Content-Type: application/json; charset=UTF-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }

$mode = trim($_GET['mode'] ?? $_POST['mode'] ?? '');

// ══════════════════════════════════════════════════
//  health — ヘルスチェック
// ══════════════════════════════════════════════════
if ($mode === 'health') {
    echo json_encode(['ok' => true, 'status' => 'ok', 'gateway' => 'php', 'ts' => date('Y-m-d H:i:s')]);
    exit;
}

// ══════════════════════════════════════════════════
//  server_status — CPU / MEM / DISK / Load / Uptime
// ══════════════════════════════════════════════════
if ($mode === 'server_status') {
    $r = ['ok' => true];

    // CPU使用率（0.25秒サンプリング）
    try {
        $s1 = file_get_contents('/proc/stat'); usleep(250000); $s2 = file_get_contents('/proc/stat');
        $parse = function($s) { $p = preg_split('/\s+/', trim(strtok($s, "\n"))); array_shift($p); return array_map('intval', $p); };
        $c1 = $parse($s1); $c2 = $parse($s2);
        $dt = array_sum($c2) - array_sum($c1); $di = ($c2[3] ?? 0) - ($c1[3] ?? 0);
        $r['cpu_pct'] = $dt > 0 ? round(100 * (1 - $di / $dt), 1) : 0;
    } catch (Throwable $e) { $r['cpu_pct'] = null; }

    // メモリ
    try {
        $mem = file_get_contents('/proc/meminfo');
        $total = 0; $avail = 0;
        if (preg_match('/MemTotal:\s+(\d+)/', $mem, $m)) $total = (int)$m[1];
        if (preg_match('/MemAvailable:\s+(\d+)/', $mem, $m)) $avail = (int)$m[1];
        $used = $total - $avail;
        $r['mem_used_mb'] = round($used / 1024);
        $r['mem_total_mb'] = round($total / 1024);
        $r['mem_pct'] = $total > 0 ? round(100 * $used / $total, 1) : 0;
    } catch (Throwable $e) { $r['mem_pct'] = null; }

    // ディスク
    try {
        $dtk = disk_total_space('/'); $df = disk_free_space('/');
        $r['disk_used_gb'] = round(($dtk - $df) / (1024 ** 3), 1);
        $r['disk_total_gb'] = round($dtk / (1024 ** 3), 1);
        $r['disk_pct'] = $dtk > 0 ? round(100 * ($dtk - $df) / $dtk, 1) : 0;
    } catch (Throwable $e) { $r['disk_pct'] = null; }

    // ロードアベレージ
    try {
        $la = sys_getloadavg();
        $r['load_1m'] = round($la[0], 2);
        $r['load_5m'] = round($la[1], 2);
    } catch (Throwable $e) { $r['load_1m'] = null; }

    // アップタイム
    try {
        $up = (float)trim(explode(' ', file_get_contents('/proc/uptime'))[0]);
        $r['uptime'] = floor($up / 86400) . '日' . floor(($up % 86400) / 3600) . '時間';
    } catch (Throwable $e) { $r['uptime'] = null; }

    echo json_encode($r, JSON_UNESCAPED_UNICODE);
    exit;
}

// ══════════════════════════════════════════════════
//  ware_trips — 割れ酉リスト（mtdk_ctl.php で管理）
// ══════════════════════════════════════════════════
if ($mode === 'ware_trips') {
    $file = __DIR__ . '/shared_trips.json';
    $data = ['ok' => true, 'auto_kick' => [], 'warn_only' => [], 'updated_at' => ''];
    if (file_exists($file)) {
        try {
            $raw = json_decode(file_get_contents($file), true);
            if (is_array($raw)) {
                $data['auto_kick']  = $raw['auto_kick'] ?? [];
                $data['warn_only']  = $raw['warn_only'] ?? [];
                $data['updated_at'] = $raw['updated_at'] ?? '';
            }
        } catch (Throwable $e) { /* silent */ }
    }
    // RM実績のあるトリップは既定で自動追放を免除する（拡張側でON/OFF可能）
    $data['rm_exempt'] = [];
    $data['rm_exempt_min'] = 10;
    if (count($data['auto_kick']) > 0) {
        try {
            $pdo = mtdk_db();
            $ph = implode(',', array_fill(0, count($data['auto_kick']), '?'));
            $stmt = $pdo->prepare("SELECT trip FROM shared_trips WHERE rm_games >= 10 AND trip IN ({$ph})");
            $stmt->execute($data['auto_kick']);
            $data['rm_exempt'] = array_map(fn($r) => $r['trip'], $stmt->fetchAll());
        } catch (Throwable $e) { /* DB不可時は免除なし（従来動作） */ }
    }
    echo json_encode($data, JSON_UNESCAPED_UNICODE);
    exit;
}

// ══════════════════════════════════════════════════
//  notice — お知らせ（api.pyと同等のPHPフォールバック）
// ══════════════════════════════════════════════════
if ($mode === 'notice') {
    $file = __DIR__ . '/notice.txt';
    $text = ''; $updated_at = '';
    if (file_exists($file)) {
        $text = trim(file_get_contents($file));
        $updated_at = date('Y-m-d H:i:s', filemtime($file));
    }
    echo json_encode(['ok' => true, 'notice' => ['text' => $text, 'updated_at' => $updated_at]], JSON_UNESCAPED_UNICODE);
    exit;
}


// ══════════════════════════════════════════════════
//  rm_list / rm_detail — RM集計タブ（PHP直結フォールバック）
// ══════════════════════════════════════════════════
function mtdk_db(): PDO {
    static $pdo = null;
    if ($pdo instanceof PDO) { return $pdo; }
    $pdo = new PDO(
        getenv('DB_DSN') ?: 'pgsql:host=localhost;dbname=zinro_meta',
        getenv('DB_USER') ?: 'zinro',
        getenv('DB_PASS') ?: '',
        [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]);
    return $pdo;
}

function mtdk_int_param(string $key, int $default, int $min, int $max): int {
    $v = isset($_GET[$key]) ? (int)$_GET[$key] : $default;
    return max($min, min($max, $v));
}

if ($mode === 'rm_list') {
    try {
        $pdo = mtdk_db();
        $q = trim((string)($_GET['q'] ?? ''));
        $limit = mtdk_int_param('limit', 50, 1, 100);
        $offset = mtdk_int_param('offset', 0, 0, 1000000);
        // 夜間バッチ(zinro_chains.py)が事前計算した rm_stats_cache から返す（爆速化）
        $where = "1=1";
        $params = [];
        if ($q !== '') {
            $where .= " AND rm_name ILIKE :q";
            $params[':q'] = '%' . $q . '%';
        }

        $stmt = $pdo->prepare("SELECT COUNT(*) FROM rm_stats_cache WHERE {$where}");
        foreach ($params as $k => $v) { $stmt->bindValue($k, $v, PDO::PARAM_STR); }
        $stmt->execute();
        $total = (int)$stmt->fetchColumn();

        $stmt = $pdo->prepare("\n            SELECT rm_name, games, first_ts, last_ts\n            FROM rm_stats_cache\n            WHERE {$where}\n            ORDER BY games DESC, last_ts DESC, rm_name ASC\n            LIMIT :limit OFFSET :offset\n        ");
        foreach ($params as $k => $v) { $stmt->bindValue($k, $v, PDO::PARAM_STR); }
        $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
        $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
        $stmt->execute();
        $rows = [];
        foreach ($stmt->fetchAll() as $r) {
            $rows[] = [
                'rm_name' => $r['rm_name'],
                'games' => (int)$r['games'],
                'first_ts' => $r['first_ts'] !== null ? (int)$r['first_ts'] : null,
                'last_ts' => $r['last_ts'] !== null ? (int)$r['last_ts'] : null,
            ];
        }
        echo json_encode(['ok' => true, 'total' => $total, 'limit' => $limit, 'offset' => $offset, 'rows' => $rows], JSON_UNESCAPED_UNICODE);
    } catch (Throwable $e) {
        http_response_code(500);
        echo json_encode(['ok' => false, 'error' => 'rm_list failed'], JSON_UNESCAPED_UNICODE);
    }
    exit;
}

if ($mode === 'rm_detail') {
    try {
        $pdo = mtdk_db();
        $rm = trim((string)($_GET['rm_name'] ?? ''));
        if ($rm === '') {
            echo json_encode(['ok' => false, 'error' => 'rm_name required'], JSON_UNESCAPED_UNICODE);
            exit;
        }
        $limit = mtdk_int_param('limit', 20, 1, 100);
        $offset = mtdk_int_param('offset', 0, 0, 1000000);

        $stmt = $pdo->prepare('SELECT COUNT(*) AS total_games, MIN(started_at) AS first_ts, MAX(started_at) AS last_ts FROM games WHERE rm_name = :rm');
        $stmt->execute([':rm' => $rm]);
        $summary = $stmt->fetch() ?: [];
        $total = (int)($summary['total_games'] ?? 0);
        if ($total <= 0) {
            echo json_encode(['ok' => true, 'found' => false, 'rm_name' => $rm], JSON_UNESCAPED_UNICODE);
            exit;
        }

        $stmt = $pdo->prepare('SELECT village_name, COUNT(*) AS games FROM games WHERE rm_name = :rm GROUP BY village_name ORDER BY games DESC, village_name ASC LIMIT 20');
        $stmt->execute([':rm' => $rm]);
        $villageTop = array_map(fn($r) => ['village_name' => $r['village_name'], 'games' => (int)$r['games']], $stmt->fetchAll());

        $stmt = $pdo->prepare('SELECT haiyaku, COUNT(*) AS games FROM games WHERE rm_name = :rm GROUP BY haiyaku ORDER BY games DESC, haiyaku ASC LIMIT 20');
        $stmt->execute([':rm' => $rm]);
        $haiyakuTop = array_map(fn($r) => ['haiyaku' => $r['haiyaku'], 'games' => (int)$r['games']], $stmt->fetchAll());

        $stmt = $pdo->prepare("\n            SELECT g.log_id, g.started_at, g.village_name, g.haiyaku,\n                   (SELECT COUNT(*) FROM players p WHERE p.game_id = g.id AND p.is_spectator = 0) AS player_count\n            FROM games g\n            WHERE g.rm_name = :rm\n            ORDER BY g.started_at DESC, g.log_id DESC\n            LIMIT :limit OFFSET :offset\n        ");
        $stmt->bindValue(':rm', $rm, PDO::PARAM_STR);
        $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
        $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
        $stmt->execute();
        $games = [];
        foreach ($stmt->fetchAll() as $r) {
            $games[] = [
                'log_id' => $r['log_id'] !== null ? (int)$r['log_id'] : null,
                'started_at' => $r['started_at'] !== null ? (int)$r['started_at'] : null,
                'village_name' => $r['village_name'],
                'haiyaku' => $r['haiyaku'],
                'player_count' => (int)$r['player_count'],
            ];
        }

        echo json_encode([
            'ok' => true,
            'found' => true,
            'rm_name' => $rm,
            'total_games' => $total,
            'first_ts' => $summary['first_ts'] !== null ? (int)$summary['first_ts'] : null,
            'last_ts' => $summary['last_ts'] !== null ? (int)$summary['last_ts'] : null,
            'village_top' => $villageTop,
            'haiyaku_top' => $haiyakuTop,
            'games' => $games,
            'limit' => $limit,
            'offset' => $offset,
        ], JSON_UNESCAPED_UNICODE);
    } catch (Throwable $e) {
        http_response_code(500);
        echo json_encode(['ok' => false, 'error' => 'rm_detail failed'], JSON_UNESCAPED_UNICODE);
    }
    exit;
}

if ($mode === 'rm_participants') {
    try {
        $pdo = mtdk_db();
        $rm = trim((string)($_GET['rm_name'] ?? ''));
        if ($rm === '') {
            echo json_encode(['ok' => false, 'error' => 'rm_name required'], JSON_UNESCAPED_UNICODE);
            exit;
        }
        $limit = mtdk_int_param('limit', 50, 1, 200);

        $stmt = $pdo->prepare('SELECT id FROM games WHERE rm_name = :rm ORDER BY started_at DESC');
        $stmt->execute([':rm' => $rm]);
        $gameIds = array_map('intval', array_column($stmt->fetchAll(), 'id'));
        if (!$gameIds) {
            echo json_encode(['ok' => true, 'rm_name' => $rm, 'total_games' => 0, 'players' => []], JSON_UNESCAPED_UNICODE);
            exit;
        }

        $placeholders = implode(',', array_fill(0, count($gameIds), '?'));
        $stmt = $pdo->prepare("
            SELECT p.name, COALESCE(p.trip, '') AS trip, COUNT(*) AS cnt,
                   ARRAY_AGG(DISTINCT g.log_id) AS log_ids
              FROM players p
              JOIN games g ON g.id = p.game_id
             WHERE p.game_id IN ({$placeholders})
               AND p.is_spectator = 0
             GROUP BY p.name, p.trip
             ORDER BY cnt DESC, p.name ASC
             LIMIT ?
        ");
        $i = 1;
        foreach ($gameIds as $gid) {
            $stmt->bindValue($i++, $gid, PDO::PARAM_INT);
        }
        $stmt->bindValue($i, $limit, PDO::PARAM_INT);
        $stmt->execute();

        $players = [];
        foreach ($stmt->fetchAll() as $r) {
            $rawIds = trim((string)($r['log_ids'] ?? ''), '{}');
            $ids = $rawIds === '' ? [] : array_values(array_filter(array_map('intval', explode(',', $rawIds))));
            rsort($ids);
            $players[] = [
                'name' => $r['name'],
                'trip' => $r['trip'],
                'count' => (int)$r['cnt'],
                'log_ids' => array_slice($ids, 0, 10),
            ];
        }
        echo json_encode(['ok' => true, 'rm_name' => $rm, 'total_games' => count($gameIds), 'players' => $players], JSON_UNESCAPED_UNICODE);
    } catch (Throwable $e) {
        http_response_code(500);
        echo json_encode(['ok' => false, 'error' => 'rm_participants failed'], JSON_UNESCAPED_UNICODE);
    }
    exit;
}

// ══════════════════════════════════════════════════
//  不明モード
// ══════════════════════════════════════════════════
http_response_code(400);
echo json_encode(['ok' => false, 'error' => "unknown mode: {$mode}", 'hint' => 'データ系APIはapi.php経由でDockerに送ってください'], JSON_UNESCAPED_UNICODE);
