<?php
if (session_status() !== PHP_SESSION_ACTIVE) {
    $isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (($_SERVER['SERVER_PORT'] ?? '') === '443');
    $sessionLifetime = 60 * 60 * 24 * 365;
    ini_set('session.gc_maxlifetime', (string)$sessionLifetime);
    ini_set('session.cookie_lifetime', (string)$sessionLifetime);
    session_set_cookie_params([
        'lifetime' => $sessionLifetime,
        'path' => '/',
        'domain' => '',
        'secure' => $isHttps,
        'httponly' => true,
        'samesite' => 'Lax',
    ]);
    ini_set('session.use_strict_mode', '1');
    ini_set('session.cookie_httponly', '1');
    session_start();
}

const ADMIN_SESSION_PERSIST_SECONDS = 31536000;

if (isLoggedInSessionRaw()) {
    $_SESSION['admin_last_activity'] = time();
    refreshAdminSessionCookie();
}

function isLoggedInSessionRaw() {
    return !empty($_SESSION['admin_user_id']);
}

function refreshAdminSessionCookie() {
    if (!ini_get('session.use_cookies')) return;
    $params = session_get_cookie_params();
    setcookie(session_name(), session_id(), [
        'expires' => time() + ADMIN_SESSION_PERSIST_SECONDS,
        'path' => $params['path'] ?: '/',
        'domain' => $params['domain'] ?: '',
        'secure' => (bool)$params['secure'],
        'httponly' => (bool)$params['httponly'],
        'samesite' => $params['samesite'] ?? 'Lax',
    ]);
}

function adminCount() {
    try {
        $pdo = getDBConnection();
        return (int)$pdo->query("SELECT COUNT(*) FROM admin_users WHERE is_active = 1")->fetchColumn();
    } catch (Throwable $e) {
        return 0;
    }
}

function isAdminSetupRequired() {
    return adminCount() === 0;
}

function isLoggedIn() {
    return !empty($_SESSION['admin_user_id']);
}

function currentAdminUser() {
    if (!isLoggedIn()) return null;
    try {
        $pdo = getDBConnection();
        $stmt = $pdo->prepare("SELECT id, username, display_name, role, is_active, last_login_at FROM admin_users WHERE id = :id AND is_active = 1");
        $stmt->execute([':id' => (int)$_SESSION['admin_user_id']]);
        $user = $stmt->fetch();
        if (!$user) {
            logoutAdmin(false);
            return null;
        }
        return $user;
    } catch (Throwable $e) {
        return null;
    }
}

function adminRoleLabels() {
    return [
        'super_admin' => '최고관리자',
        'admin' => '최고관리자',
        'manager' => '일반관리자',
        'music_manager' => '음악관리자',
        'metadata_manager' => '메타데이터관리자',
        'backup_manager' => '백업관리자',
        'log_viewer' => '로그조회자',
        'readonly' => '읽기전용관리자',
    ];
}

function adminPermissionMap() {
    $all = [
        'admin_home', 'diagnostics_view', 'performance_view', 'alert_view', 'announcement_manage', 'patch_note_manage', 'service_status_manage', 'job_queue_view', 'feedback_manage', 'playback_failure_manage', 'metadata_request_manage',
        'music_scan', 'music_manage', 'missing_files_manage', 'duplicates_manage',
        'metadata_view', 'metadata_edit', 'metadata_bulk_edit', 'tag_manage', 'lyrics_manage', 'rating_manage',
        'smart_organize', 'name_merge', 'playlist_manage',
        'backup_view', 'backup_create', 'backup_download', 'backup_delete', 'backup_restore', 'export_data', 'import_data',
        'log_view', 'log_delete', 'audit_view',
        'settings_manage', 'maintenance_run', 'admin_manage',
    ];
    return [
        'super_admin' => $all,
        'admin' => $all,
        'manager' => array_values(array_diff($all, ['admin_manage', 'backup_restore', 'log_delete'])),
        'music_manager' => ['admin_home', 'diagnostics_view', 'music_scan', 'music_manage', 'missing_files_manage', 'duplicates_manage', 'playlist_manage'],
        'metadata_manager' => ['admin_home', 'diagnostics_view', 'metadata_view', 'metadata_edit', 'metadata_bulk_edit', 'metadata_request_manage', 'tag_manage', 'lyrics_manage', 'rating_manage', 'smart_organize', 'name_merge'],
        'backup_manager' => ['admin_home', 'diagnostics_view', 'backup_view', 'backup_create', 'backup_download', 'backup_delete', 'backup_restore', 'export_data', 'import_data'],
        'log_viewer' => ['admin_home', 'diagnostics_view', 'performance_view', 'alert_view', 'job_queue_view', 'feedback_manage', 'playback_failure_manage', 'log_view', 'audit_view'],
        'readonly' => ['admin_home', 'diagnostics_view', 'performance_view', 'alert_view', 'job_queue_view', 'log_view', 'audit_view', 'metadata_view', 'backup_view'],
    ];
}

function currentAdminRole() {
    $user = currentAdminUser();
    $role = $user['role'] ?? 'readonly';
    return array_key_exists($role, adminPermissionMap()) ? $role : 'readonly';
}

function hasPermission($permission) {
    if (!isLoggedIn()) return false;
    $role = currentAdminRole();
    $permissions = adminPermissionMap()[$role] ?? [];
    return in_array((string)$permission, $permissions, true);
}

function maskSensitiveLogMessage($message) {
    $message = (string)$message;
    $message = preg_replace('/[A-Z]:[\\\\\/][^\s"\']+/i', '[path]', $message);
    return $message;
}

function authSafeMessage($message, $fallback = '요청을 처리할 수 없습니다.') {
    $message = trim((string)$message);
    if ($message === '') return $fallback;
    if (function_exists('maskInternalPathText')) {
        $message = maskInternalPathText($message);
    }
    $message = preg_replace('/SQLSTATE\[[^\]]+\][^\r\n]*/i', '', $message);
    $message = preg_replace('/(?:PDOException|Stack trace|Fatal error|Warning:|Notice:|Parse error)[^\r\n]*/i', '', $message);
    $message = preg_replace('/[A-Z]:[\\\\\/][^\s<>"\']*/i', '[path]', $message);
    $message = preg_replace('/\/(?:Users|laragon|xampp|var|home|srv|www)\/[^\s<>"\']*/i', '[path]', $message);
    $message = trim(preg_replace('/\s+/', ' ', (string)$message));
    return $message !== '' ? $message : $fallback;
}

function authJsonError($message, $code = 'FORBIDDEN', $statusCode = 403, array $data = []) {
    $code = strtoupper(preg_replace('/[^A-Z0-9_]+/', '_', (string)$code));
    if ($code === '') $code = 'FORBIDDEN';
    $message = authSafeMessage($message, $statusCode === 401 ? '로그인이 필요합니다.' : '요청을 처리할 수 없습니다.');
    if (!headers_sent()) {
        while (ob_get_level() > 0) {
            @ob_end_clean();
        }
        http_response_code($statusCode);
        header('Content-Type: application/json; charset=utf-8');
        header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
        header('Pragma: no-cache');
        header('X-MelonLite-Auth-Code: ' . $code);
    }
    echo json_encode([
        'success' => false,
        'error_code' => $code,
        'code' => $code,
        'data' => empty($data) ? new stdClass() : $data,
        'message' => $message,
    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE);
    exit;
}

function writeAuditLog($action, $summary = '', $result = 'success', $severity = 'info', $targetType = null, $targetId = null) {
    $summary = maskSensitiveLogMessage($summary);
    logAdminAction((string)$action, $summary);
    if (function_exists('writeAppLog')) {
        writeAppLog($severity, 'audit', $summary !== '' ? $summary : (string)$action, [
            'action' => (string)$action,
            'result' => (string)$result,
            'target_type' => $targetType,
            'target_id' => $targetId,
            'admin_id' => $_SESSION['admin_user_id'] ?? null,
            'admin_name' => $_SESSION['admin_username'] ?? null,
        ]);
    }
}

function denyAdminAccess($permission = null) {
    $message = '권한이 없습니다.';
    writeAuditLog('permission_denied', 'Permission denied: ' . (string)$permission . ' / ' . currentRelativeUrl(), 'denied', 'warning', 'permission', $permission);
    if (isAjaxRequest()) {
        authJsonError($message, 'FORBIDDEN', 403, [
            'permission' => (string)$permission,
        ]);
    }
    http_response_code(403);
    $base = authBasePath();
    echo '<!doctype html><html lang="ko"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>권한 없음</title><link rel="stylesheet" href="' . htmlspecialchars($base, ENT_QUOTES, 'UTF-8') . 'assets/css/style.css"></head><body><main class="auth-page"><section class="auth-card"><div class="auth-heading"><i class="fa-solid fa-lock"></i><h1>권한이 없습니다</h1><p>이 관리자 기능을 사용할 권한이 없습니다.</p></div><a class="btn btn-primary" href="' . htmlspecialchars($base, ENT_QUOTES, 'UTF-8') . 'admin/index.php">관리 허브로 이동</a></section></main></body></html>';
    exit;
}

function requirePermission($permission) {
    requireAdmin();
    if (!hasPermission($permission)) denyAdminAccess($permission);
}

function adminDisplayName() {
    $user = currentAdminUser();
    if (!$user) return '';
    return trim((string)($user['display_name'] ?: $user['username']));
}

function safeReturnUrl($returnUrl, $default = 'admin/index.php') {
    $returnUrl = trim((string)$returnUrl);
    if ($returnUrl === '') return $default;
    if (preg_match('/^[a-z][a-z0-9+.-]*:/i', $returnUrl)) return $default;
    if (strpos($returnUrl, '//') === 0) return $default;
    $returnUrl = str_replace('\\', '/', $returnUrl);
    $returnUrl = ltrim($returnUrl, '/');
    if (strpos($returnUrl, '..') !== false) return $default;
    return $returnUrl;
}

function currentRelativeUrl() {
    $script = str_replace('\\', '/', (string)($_SERVER['SCRIPT_NAME'] ?? ''));
    $parts = explode('/music/', $script, 2);
    $path = isset($parts[1]) ? $parts[1] : ltrim($script, '/');
    $query = (string)($_SERVER['QUERY_STRING'] ?? '');
    return $query !== '' ? $path . '?' . $query : $path;
}

function requireAdmin() {
    if (function_exists('sendSecurityHeaders')) {
        sendSecurityHeaders(true);
    }
    if (isAdminSetupRequired()) {
        if (isAjaxRequest()) {
            authJsonError('관리자 초기 설정이 필요합니다.', 'UNAUTHORIZED', 401, [
                'setup_url' => authBasePath() . 'admin/setup_admin.php',
            ]);
        }
        header('Location: ' . authBasePath() . 'admin/setup_admin.php');
        exit;
    }
    if (!isLoggedIn() || !currentAdminUser()) {
        $return = safeReturnUrl(currentRelativeUrl());
        if (isAjaxRequest()) {
            authJsonError('로그인이 필요합니다.', 'UNAUTHORIZED', 401, [
                'login_url' => authBasePath() . 'login.php?return=' . urlencode($return),
            ]);
        }
        header('Location: ' . authBasePath() . 'login.php?return=' . urlencode($return));
        exit;
    }
    header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
    header('Pragma: no-cache');
}

function requireLogin() {
    requireAdmin();
}

function redirectIfLoggedIn() {
    if (isLoggedIn() && currentAdminUser()) {
        header('Location: admin/index.php');
        exit;
    }
}

function loginAdmin($user) {
    session_regenerate_id(true);
    $_SESSION['admin_user_id'] = (int)$user['id'];
    $_SESSION['admin_username'] = (string)$user['username'];
    $_SESSION['admin_last_activity'] = time();
    refreshAdminSessionCookie();
}

function logoutAdmin($log = true) {
    if ($log && isLoggedIn()) {
        logAdminAction('logout', '관리자 로그아웃');
    }
    if ($log && function_exists('writeAppLog')) {
        writeAppLog('info', 'audit', 'Admin logout', [
            'admin_user_id' => (int)($_SESSION['admin_user_id'] ?? 0),
        ]);
    }
    $_SESSION = [];
    if (ini_get('session.use_cookies')) {
        $params = session_get_cookie_params();
        setcookie(session_name(), '', time() - 42000, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
    }
    session_destroy();
}

function getCsrfToken() {
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

function csrfInput() {
    return '<input type="hidden" name="csrf_token" value="' . htmlspecialchars(getCsrfToken(), ENT_QUOTES, 'UTF-8') . '">';
}

function verifyCsrfToken($token = null) {
    $token = $token ?? ($_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '');
    return is_string($token) && isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}

function requireCsrfToken() {
    if (!verifyCsrfToken()) {
        if (isAjaxRequest()) {
            authJsonError('보안 토큰이 올바르지 않습니다. 새로고침 후 다시 시도하세요.', 'CSRF_FAILED', 403);
        }
        http_response_code(403);
        die('보안 토큰이 올바르지 않습니다. 새로고침 후 다시 시도하세요.');
    }
}

function isAjaxRequest() {
    $accept = (string)($_SERVER['HTTP_ACCEPT'] ?? '');
    $requestedWith = (string)($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '');
    $pjax = (string)($_SERVER['HTTP_X_MELONLITE_PJAX'] ?? ($_SERVER['HTTP_X_MELON_LITE_PJAX'] ?? ($_SERVER['HTTP_X_PJAX'] ?? '')));
    return stripos($accept, 'application/json') !== false
        || strtolower($requestedWith) === 'xmlhttprequest'
        || in_array(strtolower($pjax), ['1', 'true', 'yes'], true);
}

function authBasePath() {
    $script = str_replace('\\', '/', (string)($_SERVER['SCRIPT_NAME'] ?? ''));
    return strpos($script, '/admin/') !== false ? '../' : './';
}

function logAdminAction($actionType, $message = null, $adminUserId = null) {
    try {
        $pdo = getDBConnection();
        if ($adminUserId === null && !empty($_SESSION['admin_user_id'])) {
            $adminUserId = (int)$_SESSION['admin_user_id'];
        }
        $stmt = $pdo->prepare("
            INSERT INTO admin_logs (admin_user_id, action_type, message, ip_address, user_agent)
            VALUES (:admin_user_id, :action_type, :message, :ip_address, :user_agent)
        ");
        $stmt->execute([
            ':admin_user_id' => $adminUserId,
            ':action_type' => $actionType,
            ':message' => $message,
            ':ip_address' => $_SERVER['REMOTE_ADDR'] ?? null,
            ':user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null,
        ]);
    } catch (Throwable $e) {
        // Logging must never break user-facing flows.
    }
}
