<?php
require_once __DIR__ . '/functions.php';
require_once __DIR__ . '/auth.php';

if (!defined('MELON_LITE_API_HELPER_LOADED')) {
    define('MELON_LITE_API_HELPER_LOADED', true);
}

if (!headers_sent() && ob_get_level() === 0) {
    ob_start();
}

function apiErrorCode(string $code): string {
    $code = strtoupper(trim($code));
    $code = preg_replace('/[^A-Z0-9_]+/', '_', $code);
    $code = trim((string)$code, '_');
    if ($code === '') return 'SERVER_ERROR';

    $aliases = [
        'API_ERROR' => 'SERVER_ERROR',
        'AUTH_REQUIRED' => 'UNAUTHORIZED',
        'METHOD_NOT_ALLOWED' => 'INVALID_REQUEST',
        'INVALID_REQUEST' => 'INVALID_REQUEST',
        'INVALID_SONG_ID' => 'INVALID_REQUEST',
        'INVALID_PLAYLIST_ID' => 'INVALID_REQUEST',
        'INVALID_NAME' => 'VALIDATION_ERROR',
        'INVALID_QUEUE' => 'VALIDATION_ERROR',
        'INVALID_ACTION' => 'INVALID_REQUEST',
        'EMPTY_QUEUE' => 'VALIDATION_ERROR',
        'NO_VALID_SONGS' => 'VALIDATION_ERROR',
        'SERVER_ERROR' => 'SERVER_ERROR',
        'SONG_NOT_FOUND' => 'NOT_FOUND',
        'PLAYLIST_NOT_FOUND' => 'NOT_FOUND',
        'PLAYLIST_SONG_NOT_FOUND' => 'NOT_FOUND',
        'PLAYLIST_DUPLICATE_SONG' => 'VALIDATION_ERROR',
        'TOKEN_EXPIRED' => 'UNAUTHORIZED',
        'TOKEN_REVOKED' => 'UNAUTHORIZED',
        'INVALID_TOKEN' => 'UNAUTHORIZED',
        'TOKEN_VALIDATION_FAILED' => 'UNAUTHORIZED',
        'USER_DISABLED' => 'FORBIDDEN',
    ];

    return $aliases[$code] ?? $code;
}

function apiFallbackMessage(string $code, int $statusCode = 400): string {
    $code = apiErrorCode($code);
    $messages = [
        'INVALID_REQUEST' => '요청을 처리할 수 없습니다.',
        'UNAUTHORIZED' => '로그인이 필요합니다.',
        'FORBIDDEN' => '권한이 없습니다.',
        'CSRF_FAILED' => '보안 토큰이 올바르지 않습니다. 새로고침 후 다시 시도하세요.',
        'NOT_FOUND' => '요청한 데이터를 찾을 수 없습니다.',
        'VALIDATION_ERROR' => '입력값을 확인해 주세요.',
        'RATE_LIMITED' => '요청이 너무 많습니다. 잠시 후 다시 시도해 주세요.',
        'PLAYER_ERROR' => '재생 요청을 처리할 수 없습니다.',
        'SCAN_ERROR' => '스캔 요청을 처리할 수 없습니다.',
        'BACKUP_ERROR' => '백업 요청을 처리할 수 없습니다.',
        'SERVER_ERROR' => '요청을 처리하지 못했습니다. 잠시 후 다시 시도해 주세요.',
    ];
    if (isset($messages[$code])) return $messages[$code];
    return $statusCode >= 500 ? $messages['SERVER_ERROR'] : $messages['INVALID_REQUEST'];
}

function apiSafeMessage(string $message, string $code = 'SERVER_ERROR', int $statusCode = 400): string {
    $message = trim($message);
    if ($message === '') return apiFallbackMessage($code, $statusCode);
    $message = function_exists('maskInternalPathText') ? maskInternalPathText($message) : $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));
    if ($message === '' || preg_match('/\b(select|insert|update|delete|from|where)\b/i', $message)) {
        return apiFallbackMessage($code, $statusCode);
    }
    return function_exists('limitLogText') ? limitLogText($message, 240) : mb_substr($message, 0, 240);
}

function apiNormalizePayload(array $payload, int $statusCode = 200): array {
    $success = array_key_exists('success', $payload) ? (bool)$payload['success'] : ($statusCode < 400);
    $payload['success'] = $success;

    if ($success) {
        if (!array_key_exists('data', $payload)) {
            $payload['data'] = new stdClass();
        }
        if (!array_key_exists('message', $payload)) {
            $payload['message'] = '';
        }
        return $payload;
    }

    $code = apiErrorCode((string)($payload['error_code'] ?? $payload['code'] ?? 'SERVER_ERROR'));
    $payload['error_code'] = $code;
    $payload['code'] = $payload['code'] ?? $code;
    $payload['message'] = apiSafeMessage((string)($payload['message'] ?? ''), $code, $statusCode);
    if (!array_key_exists('data', $payload)) {
        $payload['data'] = new stdClass();
    }
    return $payload;
}

function apiSend(array $payload, int $statusCode = 200): void {
    jsonResponse(apiNormalizePayload($payload, $statusCode), $statusCode);
    exit;
}

function apiSuccess($data = null, string $message = '', array $meta = [], array $legacy = []): void {
    $payload = array_merge([
        'success' => true,
        'data' => $data ?? new stdClass(),
        'message' => $message,
        'meta' => $meta,
    ], $legacy);
    apiSend($payload);
}

function apiError(string $message, string $code = 'API_ERROR', int $statusCode = 400, array $errors = [], array $logContext = []): void {
    $safeCode = apiErrorCode($code);
    $safeMessage = apiSafeMessage($message, $safeCode, $statusCode);
    if ($statusCode >= 500 && function_exists('writeAppLog')) {
        writeAppLog('error', 'api_error', apiFallbackMessage($safeCode, $statusCode), array_merge([
            'code' => $safeCode,
            'raw_message' => $message,
        ], $logContext));
    }
    apiSend([
        'success' => false,
        'message' => $safeMessage,
        'errors' => $errors,
        'error_code' => $safeCode,
        'code' => $safeCode,
    ], $statusCode);
}

function apiHandleThrowable(Throwable $e, string $code = 'SERVER_ERROR', int $statusCode = 500, array $context = []): void {
    $safeCode = apiErrorCode($code);
    $safeMessage = apiFallbackMessage($safeCode, $statusCode);
    if (function_exists('writeAppLog')) {
        writeAppLog('error', 'api_error', $safeMessage, array_merge([
            'code' => $safeCode,
            'exception' => get_class($e),
            'raw_message' => $e->getMessage(),
        ], $context));
    }
    apiSend([
        'success' => false,
        'message' => $safeMessage,
        'errors' => [],
        'error_code' => $safeCode,
        'code' => $safeCode,
    ], $statusCode);
}

function apiRequireMethod(string $method): void {
    if (strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET') !== strtoupper($method)) {
        apiError('허용되지 않는 요청 방식입니다.', 'METHOD_NOT_ALLOWED', 405);
    }
}

function apiBearerToken(): ?string {
    $header = $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? '';
    if ($header === '' && function_exists('getallheaders')) {
        $headers = getallheaders();
        foreach ($headers as $key => $value) {
            if (strtolower((string)$key) === 'authorization') {
                $header = (string)$value;
                break;
            }
        }
    }
    if (stripos($header, 'Bearer ') !== 0) return null;
    $token = trim(substr($header, 7));
    return $token !== '' ? $token : null;
}

function apiTokenFailure(string $code, string $message): array {
    return [
        'authenticated' => false,
        'auth_type' => 'bearer',
        'token_present' => true,
        'token_supported' => true,
        'token_error' => $code,
        'message' => $message,
        'admin_user' => null,
        'token_id' => null,
    ];
}

function apiValidateBearerToken(string $token): array {
    if ($token === '' || strlen($token) < 24 || !function_exists('getDBConnection')) {
        return apiTokenFailure('INVALID_TOKEN', 'API 토큰이 올바르지 않습니다.');
    }

    try {
        $pdo = getDBConnection();
        $stmt = $pdo->prepare("
            SELECT
                t.id AS token_id,
                t.admin_user_id,
                t.token_name,
                t.token_prefix,
                t.expires_at,
                t.is_active AS token_active,
                t.revoked_at,
                u.id,
                u.username,
                u.display_name,
                u.role,
                u.is_active AS user_active,
                u.last_login_at
            FROM api_tokens t
            INNER JOIN admin_users u ON u.id = t.admin_user_id
            WHERE t.token_hash = :token_hash
            LIMIT 1
        ");
        $stmt->execute([':token_hash' => hash('sha256', $token)]);
        $row = $stmt->fetch();
        if (!$row) {
            if (function_exists('writeAppLog')) {
                writeAppLog('warning', 'api_auth', 'Invalid API token prefix used', [
                    'token_prefix' => substr($token, 0, 12),
                ]);
            }
            return apiTokenFailure('INVALID_TOKEN', 'API 토큰이 올바르지 않습니다.');
        }
        if ((int)$row['token_active'] !== 1 || !empty($row['revoked_at'])) {
            return apiTokenFailure('TOKEN_REVOKED', '폐기된 API 토큰입니다.');
        }
        if ((int)$row['user_active'] !== 1) {
            return apiTokenFailure('USER_DISABLED', '비활성화된 관리자 계정입니다.');
        }
        if (!empty($row['expires_at']) && strtotime((string)$row['expires_at']) < time()) {
            return apiTokenFailure('TOKEN_EXPIRED', '만료된 API 토큰입니다.');
        }

        $update = $pdo->prepare("UPDATE api_tokens SET last_used_at = NOW() WHERE id = :id");
        $update->execute([':id' => (int)$row['token_id']]);

        return [
            'authenticated' => true,
            'auth_type' => 'bearer',
            'token_present' => true,
            'token_supported' => true,
            'token_error' => null,
            'token_id' => (int)$row['token_id'],
            'admin_user' => [
                'id' => (int)$row['id'],
                'username' => (string)$row['username'],
                'display_name' => (string)($row['display_name'] ?? ''),
                'role' => (string)($row['role'] ?? 'readonly'),
                'is_active' => (int)$row['user_active'],
                'last_login_at' => $row['last_login_at'] ?? null,
            ],
        ];
    } catch (Throwable $e) {
        if (function_exists('writeAppLog')) {
            writeAppLog('error', 'api_auth', 'API token validation failed', [
                'error' => apiSafeMessage($e->getMessage(), 'TOKEN_VALIDATION_FAILED', 401),
            ]);
        }
        return apiTokenFailure('TOKEN_VALIDATION_FAILED', 'API 토큰을 확인하지 못했습니다.');
    }
}

function apiAuthContext(): array {
    $token = apiBearerToken();
    if ($token !== null) {
        return apiValidateBearerToken($token);
    }

    return [
        'authenticated' => function_exists('isLoggedIn') ? isLoggedIn() : false,
        'auth_type' => 'session',
        'token_present' => false,
        'token_supported' => true,
        'token_error' => null,
        'token_id' => null,
        'admin_user' => (function_exists('currentAdminUser') ? currentAdminUser() : null),
    ];
}

function apiRequireSession(): array {
    $auth = apiAuthContext();
    if (!empty($auth['authenticated'])) return $auth;
    $code = !empty($auth['token_error']) ? (string)$auth['token_error'] : 'AUTH_REQUIRED';
    apiError((string)($auth['message'] ?? '로그인이 필요합니다.'), $code, 401);
}

function apiRequireAdminPermission(string $permission): array {
    $auth = apiRequireSession();
    if (($auth['auth_type'] ?? '') === 'bearer') {
        $role = (string)($auth['admin_user']['role'] ?? 'readonly');
        $permissions = function_exists('adminPermissionMap') ? (adminPermissionMap()[$role] ?? []) : [];
        if (in_array((string)$permission, $permissions, true)) return $auth;
    } elseif (function_exists('hasPermission') && hasPermission($permission)) {
        return $auth;
    }
    apiError('권한이 없습니다.', 'FORBIDDEN', 403);
}

function apiRequireCsrf(): void {
    $token = apiBearerToken();
    if ($token !== null) {
        $auth = apiAuthContext();
        if (!empty($auth['authenticated']) && ($auth['auth_type'] ?? '') === 'bearer') return;
        $code = !empty($auth['token_error']) ? (string)$auth['token_error'] : 'AUTH_REQUIRED';
        apiError((string)($auth['message'] ?? 'API 토큰 인증이 필요합니다.'), $code, 401);
    }
    if (!function_exists('verifyCsrfToken') || verifyCsrfToken()) return;
    apiError('보안 토큰이 올바르지 않습니다.', 'CSRF_FAILED', 403);
}

function apiSongPublicPayload(array $song, array $options = []): array {
    $payload = apiSongPayload($song, $options);
    $payload['bitrate'] = isset($song['bitrate']) ? (int)$song['bitrate'] : null;
    $payload['last_played_at'] = ensureUtf8String($song['last_played_at'] ?? '');
    $payload['is_liked'] = !empty($song['is_liked']) ? 1 : 0;
    return $payload;
}

function apiSongSelectSql(string $extraSelect = ''): string {
    $extra = trim($extraSelect);
    return "
        SELECT
            s.id,
            s.title,
            s.artist,
            s.album,
            s.album_artist,
            s.genre,
            s.year,
            s.track_no,
            s.disc_no,
            s.duration,
            s.bitrate,
            s.extension,
            s.play_count,
            s.updated_at,
            s.cover_hash,
            s.metadata_updated_at,
            l.lyrics_text,
            l.updated_at AS lyrics_updated_at,
            CASE WHEN ls.id IS NULL THEN 0 ELSE 1 END AS is_liked
            " . ($extra !== '' ? ', ' . $extra : '') . "
        FROM songs s
        LEFT JOIN lyrics l ON l.song_id = s.id
        LEFT JOIN liked_songs ls ON ls.song_id = s.id
    ";
}
