<?php
require_once __DIR__ . '/config/config.php';
require_once __DIR__ . '/includes/db.php';
require_once __DIR__ . '/includes/functions.php';
require_once __DIR__ . '/includes/auth.php';

ini_set('display_errors', 0);
error_reporting(E_ALL);

function downloadError($statusCode, $message, $context = []) {
    if (function_exists('writeAppLog')) {
        try {
            writeAppLog($statusCode >= 500 ? 'error' : 'warning', 'download_error', $message, $context, __FILE__);
        } catch (Throwable $ignored) {
        }
    }
    while (ob_get_level()) {
        @ob_end_clean();
    }
    http_response_code($statusCode);
    if (!headers_sent()) {
        header('Content-Type: text/plain; charset=utf-8');
        header('Cache-Control: private, no-store, no-cache, must-revalidate, max-age=0');
        header('Pragma: no-cache');
        header('Expires: 0');
        header('X-Content-Type-Options: nosniff');
    }
    echo $message;
    exit;
}

function normalizedRealPathForCompare($path) {
    return rtrim(strtolower(str_replace('\\', '/', toUtf8Path((string)$path))), '/');
}

function isRealPathInside($filePath, $rootPath) {
    $file = normalizedRealPathForCompare($filePath);
    $root = normalizedRealPathForCompare($rootPath);
    return $file !== '' && $root !== '' && $file !== $root && strpos($file, $root . '/') === 0;
}

function downloadFilenameFromSong(array $song, $extension) {
    $title = trim(ensureUtf8String($song['title'] ?? ''));
    $artist = trim(ensureUtf8String($song['artist'] ?? ''));
    $id = (int)($song['id'] ?? 0);
    $name = trim(($artist !== '' ? $artist . ' - ' : '') . ($title !== '' ? $title : 'track-' . $id));
    $name = preg_replace('/[\\\\\/:*?"<>|]+/u', ' ', $name);
    $name = preg_replace('/\s+/u', ' ', (string)$name);
    $name = trim((string)$name);
    if ($name === '') $name = 'track-' . max(1, $id);
    if (function_exists('mb_strlen') && function_exists('mb_substr') && mb_strlen($name, 'UTF-8') > 120) {
        $name = mb_substr($name, 0, 120, 'UTF-8');
    } elseif (strlen($name) > 180) {
        $name = substr($name, 0, 180);
    }
    $extension = strtolower(trim((string)$extension, ". \t\n\r\0\x0B"));
    return $extension !== '' ? $name . '.' . $extension : $name;
}

function downloadFilenameFromRequest($requestedName, array $song, $extension) {
    $requestedName = trim(ensureUtf8String($requestedName));
    if ($requestedName === '') {
        return downloadFilenameFromSong($song, $extension);
    }

    $requestedName = preg_replace('/[\\\\\/:*?"<>|]+/u', ' ', $requestedName);
    $requestedName = preg_replace('/\s+/u', ' ', (string)$requestedName);
    $requestedName = trim((string)$requestedName);
    if ($requestedName === '') {
        return downloadFilenameFromSong($song, $extension);
    }

    $extension = strtolower(trim((string)$extension, ". \t\n\r\0\x0B"));
    if ($extension !== '') {
        $currentExt = strtolower((string)pathinfo($requestedName, PATHINFO_EXTENSION));
        if ($currentExt === $extension) {
            $requestedName = pathinfo($requestedName, PATHINFO_FILENAME);
        }
        $requestedName = preg_replace('/\.[A-Za-z0-9]{1,8}$/u', '', $requestedName);
    }

    if (function_exists('mb_strlen') && function_exists('mb_substr') && mb_strlen($requestedName, 'UTF-8') > 120) {
        $requestedName = mb_substr($requestedName, 0, 120, 'UTF-8');
    } elseif (strlen($requestedName) > 180) {
        $requestedName = substr($requestedName, 0, 180);
    }

    return $extension !== '' ? $requestedName . '.' . $extension : $requestedName;
}

function asciiFallbackFilename($filename) {
    $ext = pathinfo($filename, PATHINFO_EXTENSION);
    $base = pathinfo($filename, PATHINFO_FILENAME);
    $base = preg_replace('/[^A-Za-z0-9._-]+/', '_', $base);
    $base = trim((string)$base, '._-');
    if ($base === '') $base = 'download';
    $ext = preg_replace('/[^A-Za-z0-9]+/', '', (string)$ext);
    return $ext !== '' ? $base . '.' . strtolower($ext) : $base;
}

requireAdmin();
if (!hasPermission('music_manage')) {
    denyAdminAccess('music_manage');
}

$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if ($id <= 0) {
    downloadError(400, '잘못된 다운로드 요청입니다.');
}

try {
    $pdo = getDBConnection();
    $stmt = $pdo->prepare("
        SELECT id, title, artist, album, extension, file_path, file_size
        FROM songs
        WHERE id = :id
          AND is_missing = 0
        LIMIT 1
    ");
    $stmt->execute([':id' => $id]);
    $song = $stmt->fetch();
    if (!$song) {
        downloadError(404, '다운로드할 곡을 찾을 수 없습니다.', ['song_id' => $id]);
    }

    $winFilePath = resolveFilesystemPath($song['file_path'], 'readable_file');
    if ($winFilePath === null) {
        $mark = $pdo->prepare("UPDATE songs SET is_missing = 1, missing_checked_at = NOW() WHERE id = :id");
        $mark->execute([':id' => $id]);
        downloadError(404, '음원 파일을 찾을 수 없습니다.', ['song_id' => $id]);
    }

    $scanDirPath = resolveFilesystemPath(getScanPathSetting(), 'dir') ?? toWinPath(getScanPathSetting());
    $realFile = realpath($winFilePath) ?: $winFilePath;
    $realScanDir = $scanDirPath ? (realpath($scanDirPath) ?: $scanDirPath) : false;
    if (!$realFile || !$realScanDir || !isRealPathInside($realFile, $realScanDir)) {
        downloadError(403, '허용되지 않은 파일입니다.', ['song_id' => $id]);
    }

    $extension = strtolower((string)($song['extension'] ?: pathinfo($realFile, PATHINFO_EXTENSION)));
    if (!in_array($extension, getAllowedExtensionsSetting(), true)) {
        downloadError(403, '허용되지 않은 음원 형식입니다.', ['song_id' => $id, 'extension' => $extension]);
    }

    $fileSize = filesize($realFile);
    if ($fileSize === false || $fileSize <= 0) {
        downloadError(416, '다운로드할 수 없는 파일입니다.', ['song_id' => $id]);
    }

    $mimeTypes = [
        'mp3' => 'audio/mpeg',
        'flac' => 'audio/flac',
        'wav' => 'audio/wav',
        'm4a' => 'audio/mp4',
        'mp4' => 'audio/mp4',
        'aac' => 'audio/aac',
        'ogg' => 'audio/ogg',
        'oga' => 'audio/ogg',
    ];
    $mimeType = $mimeTypes[$extension] ?? 'application/octet-stream';
    $downloadName = downloadFilenameFromRequest($_GET['filename'] ?? '', $song, $extension);
    $fallbackName = asciiFallbackFilename($downloadName);

    if (isset($_GET['meta']) && (string)$_GET['meta'] === '1') {
        jsonResponse([
            'success' => true,
            'data' => [
                'song_id' => (int)$song['id'],
                'title' => ensureUtf8String($song['title'] ?? ''),
                'artist' => artistName($song['artist'] ?? ''),
                'album' => albumName($song['album'] ?? ''),
                'extension' => $extension,
                'file_size' => (int)$fileSize,
                'filename' => downloadFilenameFromSong($song, $extension),
                'download_url' => 'download.php?id=' . (int)$song['id'],
            ],
        ]);
        exit;
    }

    if (function_exists('writeAuditLog')) {
        writeAuditLog('song_downloaded', 'Song downloaded: #' . $id . ' ' . ensureUtf8String($song['title'] ?? ''), 'success', 'info', 'song', $id);
    }

    if (session_status() === PHP_SESSION_ACTIVE) {
        session_write_close();
    }

    while (ob_get_level()) {
        @ob_end_clean();
    }

    @set_time_limit(0);
    http_response_code(200);
    header('Content-Type: ' . $mimeType);
    header('Content-Length: ' . $fileSize);
    header('Content-Disposition: attachment; filename="' . addcslashes($fallbackName, "\\\"") . '"; filename*=UTF-8\'\'' . rawurlencode($downloadName));
    header('Cache-Control: private, no-store, no-cache, must-revalidate, max-age=0');
    header('Pragma: no-cache');
    header('Expires: 0');
    header('Vary: Cookie');
    header('Accept-Ranges: none');
    header('X-Content-Type-Options: nosniff');
    header('X-MelonLite-Download: 1');

    $fp = @fopen($realFile, 'rb');
    if (!$fp) {
        downloadError(500, '파일을 열 수 없습니다.', ['song_id' => $id]);
    }

    $bufferSize = 1024 * 512;
    while (!feof($fp) && connection_status() === CONNECTION_NORMAL) {
        $chunk = @fread($fp, $bufferSize);
        if ($chunk === false || $chunk === '') break;
        echo $chunk;
        @flush();
    }
    fclose($fp);
    exit;
} catch (Throwable $e) {
    downloadError(500, '다운로드 처리 중 오류가 발생했습니다.', ['song_id' => $id]);
}
