<?php
// API 路由入口：/user/API/index.php
// 约定：一个函数一个文件，函数名为 api_<文件名>
// 后端函数文件目录：/user/API/API/
// 主配置文件：/user/config.ini

session_start();

// 基础路径
define('API_ROOT', __DIR__);
define('FUNC_DIR', API_ROOT . DIRECTORY_SEPARATOR . 'API');
define('USER_ROOT', dirname(__DIR__));
define('DATA_DIR', USER_ROOT . DIRECTORY_SEPARATOR . 'data');
define('CONFIG_INI', USER_ROOT . DIRECTORY_SEPARATOR . 'config.ini');
define('RATE_FILE', API_ROOT . DIRECTORY_SEPARATOR . 'rate_limit.json');

// 确保数据目录存在
if (!is_dir(DATA_DIR)) { @mkdir(DATA_DIR, 0775, true); }

// 安全HTTP头
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
header('Referrer-Policy: no-referrer-when-downgrade');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');

// 工具：统一JSON响应
function json_out($code, $msg, $data = null) {
    header('Content-Type: application/json; charset=UTF-8');
    echo json_encode(['code' => $code, 'msg' => $msg, 'data' => $data], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}

function safe_name($name) {
    return preg_match('/^[a-zA-Z0-9_\-\.]+$/', $name);
}

function load_config() {
    $defaults = [
        'encryption_method' => 'auto', // auto|argon2|bcrypt
        'encryption_level' => 'strong',
        'storage_format' => 'json',
        'users_file' => DATA_DIR . DIRECTORY_SEPARATOR . 'users.json',
        'error_policy' => 'safe',
        'allow_email' => '1',
        'rate_limit_per_minute' => '10',
        'allow_list_extensions' => 'php,js,css',
    ];
    if (!file_exists(CONFIG_INI)) return $defaults;
    $cfg = @parse_ini_file(CONFIG_INI, false, INI_SCANNER_TYPED);
    if (!is_array($cfg)) return $defaults;
    return array_merge($defaults, $cfg);
}

function load_json($path, $fallback = []) {
    if (!file_exists($path)) return $fallback;
    $raw = @file_get_contents($path);
    if ($raw === false || $raw === '') return $fallback;
    $data = json_decode($raw, true);
    return is_array($data) ? $data : $fallback;
}

function atomic_write_json($path, $data) {
    $json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
    $tmp = $path . '.tmp';
    if (@file_put_contents($tmp, $json, LOCK_EX) === false) return false;
    $ok = @rename($tmp, $path);
    if (!$ok) { @unlink($tmp); }
    return $ok;
}

function rate_limit_check($cfg) {
    $limit = (int)($cfg['rate_limit_per_minute'] ?? 10);
    $window = 60; $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
    $rates = load_json(RATE_FILE, []);
    $now = time();
    $list = isset($rates[$ip]) && is_array($rates[$ip]) ? $rates[$ip] : [];
    $list = array_values(array_filter($list, function($t) use ($now, $window){ return ($now - (int)$t) < $window; }));
    if (count($list) >= $limit) return [false, $rates];
    $list[] = $now; $rates[$ip] = $list; return [true, $rates];
}

function ensure_csrf() {
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(16));
    }
    // 对写操作进行校验
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $t = $_POST['csrf_token'] ?? '';
        if (!hash_equals($_SESSION['csrf_token'], (string)$t)) {
            json_out(403, 'CSRF token invalid');
        }
    }
}

function action_csrf() {
    ensure_csrf();
    json_out(200, 'ok', ['token' => $_SESSION['csrf_token']]);
}

function action_list_files($cfg) {
    $path = trim((string)($_GET['path'] ?? ''));
    $exts = strtolower((string)($_GET['ext'] ?? $cfg['allow_list_extensions']));
    $allow = array_filter(array_map('trim', explode(',', $exts)));
    $allow = array_intersect($allow, array_filter(array_map('trim', explode(',', strtolower($cfg['allow_list_extensions'])))));
    $base = realpath(FUNC_DIR);
    $target = $base;
    if ($path !== '') {
        $candidate = realpath(FUNC_DIR . DIRECTORY_SEPARATOR . $path);
        if ($candidate === false || strpos($candidate, $base) !== 0) json_out(400, 'Invalid path');
        $target = $candidate;
    }
    $out = [];
    $it = @scandir($target);
    if ($it === false) json_out(404, 'Path not found');
    foreach ($it as $name) {
        if ($name === '.' || $name === '..') continue;
        $full = $target . DIRECTORY_SEPARATOR . $name;
        if (is_dir($full)) {
            $out[] = ['type' => 'dir', 'name' => $name];
        } else {
            $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
            if (in_array($ext, $allow)) {
                $out[] = ['type' => 'file', 'name' => $name, 'ext' => $ext, 'size' => filesize($full)];
            }
        }
    }
    json_out(200, 'ok', ['files' => $out, 'path' => str_replace($base, '', $target)]);
}

function content_type_by_ext($ext) {
    switch (strtolower($ext)) {
        case 'js': return 'application/javascript; charset=UTF-8';
        case 'css': return 'text/css; charset=UTF-8';
        case 'php': return 'text/plain; charset=UTF-8'; // 出于安全仅以纯文本返回源码
        default: return 'text/plain; charset=UTF-8';
    }
}

function action_get_file($cfg) {
    $file = trim((string)($_GET['file'] ?? ''));
    if ($file === '' || !safe_name($file)) json_out(400, 'Invalid file');
    $base = realpath(FUNC_DIR);
    $full = realpath(FUNC_DIR . DIRECTORY_SEPARATOR . $file);
    if ($full === false || strpos($full, $base) !== 0 || !is_file($full)) json_out(404, 'Not found');
    $ext = strtolower(pathinfo($full, PATHINFO_EXTENSION));
    $allow = array_filter(array_map('trim', explode(',', strtolower($cfg['allow_list_extensions']))));
    if (!in_array($ext, $allow)) json_out(403, 'Extension not allowed');
    header('Content-Type: ' . content_type_by_ext($ext));
    readfile($full);
    exit;
}

function action_call($cfg) {
    ensure_csrf();
    // 速率限制（仅对POST）
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        list($allowed, $rates) = rate_limit_check($cfg);
        if (!$allowed) json_out(429, 'Too Many Requests');
        atomic_write_json(RATE_FILE, $rates);
    }
    $fn = trim((string)($_REQUEST['fn'] ?? ''));
    if ($fn === '' || !safe_name($fn)) json_out(400, 'Invalid fn');
    $file = FUNC_DIR . DIRECTORY_SEPARATOR . $fn . '.php';
    if (!is_file($file)) json_out(404, 'Function file not found');
    // 加载配置与上下文
    $config = load_config();
    $ctx = [
        'config' => $config,
        'users_file' => $config['users_file'],
        'data_dir' => DATA_DIR,
        'request' => $_REQUEST,
        'method' => $_SERVER['REQUEST_METHOD'] ?? 'GET',
        'session_id' => session_id(),
    ];
    require_once $file;
    $callable = 'api_' . str_replace(['-', '.'], '_', $fn);
    if (!function_exists($callable)) json_out(500, 'Function not defined');
    try {
        $result = call_user_func($callable, $ctx);
        json_out(200, 'ok', $result);
    } catch (Throwable $e) {
+        // 将服务端异常也写入 error-watch
+        $logctx = $ctx;
+        $logctx['request'] = [
+            'type' => 'server_exception',
+            'message' => $e->getMessage(),
+            'stack' => $e->getTraceAsString(),
+            'page_url' => $_SERVER['REQUEST_URI'] ?? '',
+            'time' => date('c'),
+        ];
+        $logFile = FUNC_DIR . DIRECTORY_SEPARATOR . 'error_watch_log.php';
+        if (is_file($logFile)) {
+            require_once $logFile;
+            if (function_exists('api_error_watch_log')) {
+                try { api_error_watch_log($logctx); } catch (Throwable $ignore) { /* 忽略日志写入异常 */ }
+            }
+        }
         json_out(500, 'Exception: ' . $e->getMessage());
    }
}

$action = strtolower(trim((string)($_GET['action'] ?? $_POST['action'] ?? '')));
if ($action === '') {
    // 默认返回API说明
    header('Content-Type: application/json; charset=UTF-8');
    echo json_encode([
        'name' => 'User API Router',
        'endpoints' => [
            'GET action=csrf' => '获取CSRF令牌',
            'POST action=call&fn=auth_register' => '注册用户',
            'POST action=call&fn=auth_login' => '用户登录',
            'POST action=call&fn=auth_logout' => '退出登录',
            'POST action=call&fn=update_user' => '更新资料（邮箱/密码）',
            'GET action=call&fn=get_user&username=xxx' => '获取单个用户（脱敏）',
            'GET action=call&fn=list_users' => '列出用户',
            'POST action=call&fn=check_password_strength&password=xxx' => '检查密码强度',
            'GET action=list_files&path=&ext=php,js,css' => '列出后端目录内的文件',
            'GET action=get_file&file=xxx.php' => '获取指定后端文件的源代码/内容',
        ],
        'note' => '所有写操作需携带csrf_token字段（先调用action=csrf获取）',
    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}

switch ($action) {
    case 'csrf': action_csrf(); break;
    case 'call': action_call(load_config()); break;
    case 'list_files': action_list_files(load_config()); break;
    case 'get_file': action_get_file(load_config()); break;
    default: json_out(404, 'Unknown action');
}