<?php
declare(strict_types=1);

/**
 * functional/audio/upload.php
 * Відповідає за:
 * - завантаження аудіо-файлів (multiple)
 * - перевірка: 25MB/файл, 5GB квота користувача
 * - збереження у /upload/{username}/music/YYYY/MM/...
 * - запис у audio_tracks
 *
 * SEO/UX:
 * - редірект назад (back) або у /functional/audio/ зі збереженням lang
 */

require_once __DIR__ . '/_boot.php';

$langNow = function_exists('lang') ? (string)lang() : (function_exists('audio_lang') ? audio_lang() : (string)($_SESSION['lang'] ?? 'uk'));
if (!in_array($langNow, ['uk','ru','en'], true)) $langNow = 'uk';

/* safe back */
$back = function_exists('post_str') ? post_str('back', '') : (string)($_POST['back'] ?? '');
if (!function_exists('safe_local_path')) {
    function safe_local_path(string $path, string $fallback = '/'): string
    {
        $path = trim(str_replace(["\0", "\r", "\n"], '', $path));
        if ($path === '') return $fallback;

        $p = parse_url($path);
        $pathOnly = (string)($p['path'] ?? '');
        $q = isset($p['query']) && $p['query'] !== '' ? ('?' . $p['query']) : '';
        $out = $pathOnly . $q;

        if ($out === '' || $out[0] !== '/') $out = '/' . ltrim($out, '/');
        if (str_contains($out, "\0")) return $fallback;

        return $out;
    }
}

$defaultBack = '/functional/audio/?lang=' . urlencode($langNow);
$backUrl = safe_local_path($back, $defaultBack);

/* flash fallback (якщо в проекті немає flash_set) */
if (!function_exists('audio_flash_set')) {
    function audio_flash_set(string $key, string $msg): void
    {
        if (function_exists('flash_set')) {
            flash_set($key, $msg);
            return;
        }
        if (session_status() !== PHP_SESSION_ACTIVE) @session_start();
        if (!isset($_SESSION['_flash']) || !is_array($_SESSION['_flash'])) $_SESSION['_flash'] = [];
        $_SESSION['_flash'][$key] = $msg;
    }
}

function audio_msg(string $code, int $ok = 0, int $skipped = 0): string
{
    $lang = function_exists('audio_lang') ? audio_lang() : 'uk';
    return match ($code) {
        'CSRF' => match ($lang) {
            'ru' => 'Ошибка CSRF.',
            'en' => 'CSRF error.',
            default => 'Помилка CSRF.',
        },
        'NO_FILES' => match ($lang) {
            'ru' => 'Файлы не выбраны.',
            'en' => 'No files selected.',
            default => 'Файли не обрані.',
        },
        'DIR_FAIL' => match ($lang) {
            'ru' => 'Не удалось создать папку для загрузки.',
            'en' => 'Failed to create upload folder.',
            default => 'Не вдалося створити папку для завантаження.',
        },
        'QUOTA' => match ($lang) {
            'ru' => 'Достигнута квота хранения.',
            'en' => 'Storage quota reached.',
            default => 'Досягнуто квоти зберігання.',
        },
        'UPLOADED' => match ($lang) {
            'ru' => "Загружено: {$ok}. Пропущено: {$skipped}.",
            'en' => "Uploaded: {$ok}. Skipped: {$skipped}.",
            default => "Завантажено: {$ok}. Пропущено: {$skipped}.",
        },
        default => match ($lang) {
            'ru' => 'Загрузка не удалась.',
            'en' => 'Upload failed.',
            default => 'Завантаження не вдалося.',
        }
    };
}

/* method + csrf */
if (!is_post() || !csrf_check(post_str('csrf'))) {
    audio_flash_set('err', audio_msg('CSRF'));
    if (function_exists('redirect')) redirect($backUrl);
    header('Location: ' . $backUrl, true, 302);
    exit;
}

$userId = (int)($me['id'] ?? 0);
if ($userId <= 0) {
    if (function_exists('redirect')) redirect('/login.php');
    header('Location: /login.php', true, 302);
    exit;
}

$quota  = audio_quota_bytes();
$maxOne = audio_max_file_bytes();

/* files */
$files = $_FILES['files'] ?? null;
if (!$files || !isset($files['name'])) {
    audio_flash_set('err', audio_msg('NO_FILES'));
    if (function_exists('redirect')) redirect($backUrl);
    header('Location: ' . $backUrl, true, 303);
    exit;
}

$names = (array)$files['name'];
$tmp   = (array)$files['tmp_name'];
$errs  = (array)$files['error'];
$sizes = (array)$files['size'];

/* security caps */
$maxFilesPerRequest = 30;
$totalIncoming = count($names);
$toProcess = min($totalIncoming, $maxFilesPerRequest);

/* allowed mimes/ext */
$allowedMimes = [
    'audio/mpeg'   => 'mp3',
    'audio/mp3'    => 'mp3',
    'audio/mp4'    => 'm4a',
    'audio/x-m4a'  => 'm4a',
    'audio/aac'    => 'aac',
    'audio/ogg'    => 'ogg',
    'audio/opus'   => 'opus',
    'audio/webm'   => 'webm',
    'audio/wav'    => 'wav',
    'audio/x-wav'  => 'wav',
    'audio/flac'   => 'flac',
    'audio/x-flac' => 'flac',
];

$allowedExts = array_values($allowedMimes);
$allowedExts = array_values(array_unique($allowedExts));

/* reverse map ext -> mime (для fallback) */
$extToMime = [
    'mp3'  => 'audio/mpeg',
    'm4a'  => 'audio/mp4',
    'aac'  => 'audio/aac',
    'ogg'  => 'audio/ogg',
    'wav'  => 'audio/wav',
    'flac' => 'audio/flac',
    'opus' => 'audio/opus',
    'webm' => 'audio/webm',
];

$finfo = null;
if (class_exists('finfo')) {
    try {
        $finfo = new finfo(FILEINFO_MIME_TYPE);
    } catch (Throwable $e) {
        $finfo = null;
    }
}

$used = audio_used_bytes($pdo, $userId);

$baseRel = audio_user_music_dir_rel($me); // /upload/{username}/music
$baseRel = '/' . trim((string)$baseRel, '/');

$okCount = 0;
$skipped = 0;
$quotaStop = false;
$dirFail = false;

$now = date('Y-m-d H:i:s');

for ($i = 0; $i < $toProcess; $i++) {
    $origName = (string)($names[$i] ?? '');
    $tmpName  = (string)($tmp[$i] ?? '');
    $err      = (int)($errs[$i] ?? UPLOAD_ERR_NO_FILE);
    $sizeHint = (int)($sizes[$i] ?? 0);

    if ($err !== UPLOAD_ERR_OK) { $skipped++; continue; }
    if ($sizeHint <= 0 || $sizeHint > $maxOne) { $skipped++; continue; }

    if (($used + $sizeHint) > $quota) {
        $quotaStop = true;
        break;
    }

    if ($tmpName === '' || !is_uploaded_file($tmpName)) { $skipped++; continue; }

    $extGuess = strtolower((string)pathinfo($origName, PATHINFO_EXTENSION));
    $extGuess = preg_replace('~[^a-z0-9]+~', '', $extGuess) ?? '';

    $mime = '';
    if ($finfo) {
        try {
            $m = $finfo->file($tmpName);
            if (is_string($m)) $mime = strtolower(trim($m));
        } catch (Throwable $e) {
            $mime = '';
        }
    }

    $ext = '';
    if ($mime !== '' && isset($allowedMimes[$mime])) {
        $ext = $allowedMimes[$mime];
    } elseif ($extGuess !== '' && in_array($extGuess, $allowedExts, true)) {
        // fallback by extension
        $ext = $extGuess;
        $mime = $extToMime[$ext] ?? 'application/octet-stream';
    } else {
        $skipped++;
        continue;
    }

    if ($ext === '') { $skipped++; continue; }

    $y = date('Y');
    $m = date('m');

    $subRel = rtrim($baseRel, '/') . '/' . $y . '/' . $m; // /upload/u/music/YYYY/MM
    $subAbs = audio_rel_to_abs($subRel);

    if (!is_dir($subAbs)) {
        @mkdir($subAbs, 0775, true);
    }
    if (!is_dir($subAbs)) {
        $dirFail = true;
        $skipped++;
        continue;
    }

    $safeBase = bin2hex(random_bytes(16));
    $stored = $safeBase . '.' . $ext;

    $destAbs = rtrim($subAbs, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $stored;
    $destRel = $subRel . '/' . $stored;

    if (!move_uploaded_file($tmpName, $destAbs)) {
        $skipped++;
        continue;
    }

    // real file size after move
    $realSize = (int)@filesize($destAbs);
    if ($realSize <= 0 || $realSize > $maxOne) {
        @unlink($destAbs);
        $skipped++;
        continue;
    }

    if (($used + $realSize) > $quota) {
        // quota reached after real size check
        @unlink($destAbs);
        $quotaStop = true;
        break;
    }

    // title from original filename (safe)
    $title = (string)pathinfo($origName, PATHINFO_FILENAME);
    $title = trim($title);
    $title = preg_replace('/[\x00-\x1F\x7F]/u', ' ', $title) ?? $title;
    $title = preg_replace('/\s+/u', ' ', $title) ?? $title;
    $title = trim($title);
    if ($title === '') $title = 'Track';
    $title = mb_substr($title, 0, 255);

    // original name safe
    $origSafe = trim($origName);
    $origSafe = preg_replace('/[\x00-\x1F\x7F]/u', ' ', $origSafe) ?? $origSafe;
    $origSafe = preg_replace('/\s+/u', ' ', $origSafe) ?? $origSafe;
    $origSafe = trim($origSafe);
    if ($origSafe === '') $origSafe = $stored;
    $origSafe = mb_substr($origSafe, 0, 255);

    try {
        $st = $pdo->prepare("
            INSERT INTO audio_tracks
              (user_id, title, artist, album, duration_sec, rel_path, stored_name, original_name, mime, size_bytes, created_at, updated_at)
            VALUES
              (?, ?, NULL, NULL, NULL, ?, ?, ?, ?, ?, ?, ?)
        ");
        $st->execute([
            $userId,
            $title,
            $destRel,
            $stored,
            $origSafe,
            $mime !== '' ? $mime : 'application/octet-stream',
            $realSize,
            $now,
            $now
        ]);

        $used += $realSize;
        $okCount++;
    } catch (Throwable $e) {
        // DB fail -> remove file to avoid orphan
        @unlink($destAbs);
        $skipped++;
        continue;
    }
}

/* flash result */
if ($okCount > 0) {
    audio_flash_set('ok', audio_msg('UPLOADED', $okCount, $skipped) . ($quotaStop ? ' ' . audio_msg('QUOTA') : ''));
} elseif ($dirFail) {
    audio_flash_set('err', audio_msg('DIR_FAIL'));
} elseif ($quotaStop) {
    audio_flash_set('err', audio_msg('QUOTA'));
} else {
    audio_flash_set('err', ($totalIncoming > $maxFilesPerRequest)
        ? (audio_msg('NO_FILES') . ' (too many files)')
        : 'SKIPPED');
}

if (function_exists('redirect')) redirect($backUrl);
header('Location: ' . $backUrl, true, 303);
exit;