<?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 streamError($statusCode, $message, $context = []) {
    if (function_exists('writeAppLog')) {
        $level = $statusCode >= 500 ? 'error' : 'warning';
        writeAppLog($level, 'stream_error', $message, $context, __FILE__);
    }
    http_response_code($statusCode);
    header('Cache-Control: private, no-store, no-cache, must-revalidate, max-age=0');
    header('Pragma: no-cache');
    header('Expires: 0');
    header('Vary: Cookie');
    header('Content-Type: text/plain; charset=utf-8');
    echo $message;
    exit;
}

function previewByteLimit($song, $fileSize) {
    $previewSeconds = 30;
    $bitrate = (int)($song['bitrate'] ?? 0);
    if ($bitrate > 0) {
        // Some scans store kbps, some store bps. Normalize conservatively.
        $bitsPerSecond = $bitrate < 10000 ? $bitrate * 1000 : $bitrate;
        $bytes = (int)ceil(($bitsPerSecond / 8) * $previewSeconds);
        return max(256 * 1024, min($fileSize, $bytes + (96 * 1024)));
    }
    $ext = strtolower((string)($song['extension'] ?? ''));
    $fallbackByExt = [
        'mp3' => 1024 * 1024,
        'm4a' => 1024 * 1024,
        'aac' => 1024 * 1024,
        'ogg' => 1024 * 1024,
        'oga' => 1024 * 1024,
        'wav' => 6 * 1024 * 1024,
        'flac' => 6 * 1024 * 1024,
    ];
    return min($fileSize, $fallbackByExt[$ext] ?? (1024 * 1024));
}

$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
if ($id <= 0) {
    streamError(400, '잘못된 음악 요청입니다.');
}

try {
    $pdo = getDBConnection();
    $stmt = $pdo->prepare("SELECT file_path, extension, duration, bitrate FROM songs WHERE id = :id AND is_missing = 0");
    $stmt->execute([':id' => $id]);
    $song = $stmt->fetch();
    if (!$song) {
        streamError(404, '곡을 찾을 수 없거나 파일 없음 상태입니다.');
    }

    $winFilePath = toWinPath($song['file_path']);
    if (!is_file($winFilePath)) {
        $mark = $pdo->prepare("UPDATE songs SET is_missing = 1 WHERE id = :id");
        $mark->execute([':id' => $id]);
        streamError(404, '음악 파일을 찾을 수 없습니다.');
    }

    $realFile = realpath($winFilePath);
    $realScanDir = realpath(toWinPath(getScanPathSetting()));
    if (!$realFile || !$realScanDir || strpos(strtolower(str_replace('\\', '/', $realFile)), strtolower(str_replace('\\', '/', $realScanDir))) !== 0) {
        streamError(403, '허용되지 않은 파일 경로입니다.');
    }

    $fileSize = filesize($realFile);
    if ($fileSize === false || $fileSize <= 0) {
        streamError(416, '스트리밍할 수 없는 파일입니다.');
    }

    $ext = strtolower((string)$song['extension']);
    $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[$ext] ?? 'application/octet-stream';
    $isPreview = !isLoggedIn();
    $streamSize = $isPreview ? previewByteLimit($song, $fileSize) : $fileSize;

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

    $start = 0;
    $end = $streamSize - 1;
    $status = 200;

    if (!empty($_SERVER['HTTP_RANGE']) && preg_match('/bytes=(\d*)-(\d*)/i', $_SERVER['HTTP_RANGE'], $matches)) {
        if ($matches[1] === '' && $matches[2] === '') {
            header("Content-Range: bytes */$streamSize");
            streamError(416, '잘못된 Range 요청입니다.');
        }

        if ($matches[1] === '') {
            $suffixLength = (int)$matches[2];
            $start = max(0, $streamSize - $suffixLength);
        } else {
            $start = (int)$matches[1];
        }

        if ($matches[2] !== '') {
            $end = min((int)$matches[2], $streamSize - 1);
        }

        if ($start > $end || $start >= $streamSize) {
            header("Content-Range: bytes */$streamSize");
            streamError(416, '요청한 구간을 재생할 수 없습니다.');
        }
        $status = 206;
    }

    $length = $end - $start + 1;
    http_response_code($status);
    header('Accept-Ranges: bytes');
    header('Content-Type: ' . $mimeType);
    header('Content-Length: ' . $length);
    if ($status === 206) {
        header('Content-Range: bytes ' . $start . '-' . $end . '/' . $streamSize);
    }
    header('Cache-Control: private, no-store, no-cache, must-revalidate, max-age=0');
    header('Pragma: no-cache');
    header('Expires: 0');
    header('Vary: Cookie');
    if ($isPreview) {
        header('X-MelonLite-Preview: 1');
        header('X-MelonLite-Preview-Seconds: 30');
    }
    header('X-Content-Type-Options: nosniff');

    $fp = fopen($realFile, 'rb');
    if (!$fp) {
        streamError(500, '파일을 열 수 없습니다.');
    }
    fseek($fp, $start);
    $sent = 0;
    $bufferSize = 1024 * 256;
    while (!feof($fp) && $sent < $length && connection_status() === CONNECTION_NORMAL) {
        $read = min($bufferSize, $length - $sent);
        $data = fread($fp, $read);
        if ($data === false || $data === '') break;
        $sent += strlen($data);
        echo $data;
        flush();
    }
    fclose($fp);
    exit;
} catch (Exception $e) {
    streamError(500, '스트리밍 오류가 발생했습니다.');
}
