<?php
declare(strict_types=1);

/**
 * /functional/video/_lib.php
 * Спільні хелпери для "Мої відеозаписи":
 * - i18n (uk/ru/en) локально
 * - квота 10GB на користувача (рахуємо швидко через таблицю myvideos.size_bytes)
 * - max 500MB на один файл
 * - format_bytes()
 * - flash/redirect/csrf фолбеки (сумісні з твоїми /includes/helpers.php: _flash + _csrf/csrf_token)
 * - створення upload/<username>/video + (.htaccess, index.html)
 * - безпечний rel_path -> filesystem path (+ legacy fallback)
 */

if (!function_exists('h')) {
  function h(?string $s): string { return htmlspecialchars((string)$s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
}

if (!function_exists('mv_ensure_session_started')) {
  function mv_ensure_session_started(): void {
    if (session_status() === PHP_SESSION_ACTIVE) return;
    // якщо в проекті є start_app_session() — хай він керує cookie-параметрами
    if (function_exists('start_app_session')) {
      try { start_app_session(); return; } catch (Throwable $e) {}
    }
    @session_start();
  }
}

if (!function_exists('is_post')) {
  function is_post(): bool { return (string)($_SERVER['REQUEST_METHOD'] ?? '') === 'POST'; }
}

if (!function_exists('redirect')) {
  function redirect(string $to): void {
    $code = is_post() ? 303 : 302;
    if (!headers_sent()) {
      header('Location: ' . $to, true, $code);
      exit;
    }
    echo '<meta http-equiv="refresh" content="0;url=' . h($to) . '">';
    echo '<script>location.href=' . json_encode($to) . ';</script>';
    exit;
  }
}

if (!function_exists('flash_set')) {
  function flash_set(string $k, string $v): void {
    mv_ensure_session_started();
    if (!isset($_SESSION['_flash']) || !is_array($_SESSION['_flash'])) $_SESSION['_flash'] = [];
    $_SESSION['_flash'][$k] = $v;

    // compat
    if (!isset($_SESSION['flash']) || !is_array($_SESSION['flash'])) $_SESSION['flash'] = [];
    $_SESSION['flash'][$k] = $v;
    if ($k === 'err') $_SESSION['flash']['error'] = $v;
    if ($k === 'ok')  $_SESSION['flash']['success'] = $v;
  }
}

if (!function_exists('flash_get')) {
  function flash_get(string $k): ?string {
    mv_ensure_session_started();
    $v = null;

    if (isset($_SESSION['_flash']) && is_array($_SESSION['_flash']) && array_key_exists($k, $_SESSION['_flash'])) {
      $v = $_SESSION['_flash'][$k];
      unset($_SESSION['_flash'][$k]);
    }
    if ($v === null && isset($_SESSION['flash']) && is_array($_SESSION['flash']) && array_key_exists($k, $_SESSION['flash'])) {
      $v = $_SESSION['flash'][$k];
      unset($_SESSION['flash'][$k]);
    }

    if (!is_string($v)) return null;
    $v = trim($v);
    return $v === '' ? null : $v;
  }
}

if (!function_exists('require_login')) {
  function require_login(): void {
    $u = function_exists('current_user') ? current_user() : null;
    if (!$u) { header('Location: /login.php'); exit; }
  }
}

/* ==== CSRF (сумісно з /includes/helpers.php: _csrf + csrf_token) ==== */
if (!function_exists('csrf_token')) {
  function csrf_token(): string {
    mv_ensure_session_started();

    $a = (string)($_SESSION['_csrf'] ?? '');
    $b = (string)($_SESSION['csrf_token'] ?? '');

    if ($a === '' && $b === '') {
      $tok = bin2hex(random_bytes(32));
      $_SESSION['_csrf'] = $tok;
      $_SESSION['csrf_token'] = $tok;
      return $tok;
    }
    if ($a === '' && $b !== '') { $_SESSION['_csrf'] = $b; return $b; }
    if ($a !== '' && $b === '') { $_SESSION['csrf_token'] = $a; return $a; }

    return $a; // пріоритет _csrf
  }
}

if (!function_exists('csrf_check')) {
  function csrf_check(?string $t): bool {
    mv_ensure_session_started();
    $t = is_string($t) ? trim($t) : '';
    if ($t === '') return false;

    $a = (string)($_SESSION['_csrf'] ?? '');
    $b = (string)($_SESSION['csrf_token'] ?? '');

    if ($a !== '' && hash_equals($a, $t)) return true;
    if ($b !== '' && hash_equals($b, $t)) return true;
    return false;
  }
}

if (!function_exists('csrf_field')) {
  function csrf_field(): string {
    return '<input type="hidden" name="csrf" value="' . h(csrf_token()) . '">';
  }
}

/* ==== compat: starts_with for older php ==== */
if (!function_exists('mv_starts_with')) {
  function mv_starts_with(string $haystack, string $needle): bool {
    if ($needle === '') return true;
    if (function_exists('str_starts_with')) return str_starts_with($haystack, $needle);
    return substr($haystack, 0, strlen($needle)) === $needle;
  }
}

const MV_QUOTA_MAX_BYTES = 10737418240; // 10 * 1024^3
const MV_MAX_FILE_BYTES  = 524288000;   // 500 * 1024^2
const MV_VIDEOS_TABLE    = 'myvideos';  // з дампа БД

function mv_detect_lang(?array $me): string {
  $lang = (string)($_SESSION['lang'] ?? ($me['lang'] ?? 'uk'));
  $lang = strtolower(trim($lang));
  if ($lang === 'ua') $lang = 'uk';
  if (!in_array($lang, ['uk','ru','en'], true)) $lang = 'uk';
  return $lang;
}

function mv_i18n(): array {
  return [
    'uk' => [
      'title' => 'Мої відеозаписи',
      'used' => 'Використано',
      'of' => 'з',
      'free' => 'Вільно',
      'upload' => 'Завантажити',
      'add_row' => 'Додати ще відео',
      'video_title' => 'Назва відео',
      'choose_file' => 'Відеофайл',
      'max500' => 'max 500MB / файл',
      'quota10' => 'Квота: 10GB на користувача',
      'library' => 'Відеотека',
      'empty' => 'Поки що порожньо',
      'watch' => 'Переглянути',
      'delete' => 'Видалити',
      'back' => 'Назад',
      'prev' => 'Попереднє',
      'next' => 'Наступне',
      'created' => 'Створено',
      'size' => 'Розмір',
      'confirm_del' => 'Видалити відео?',
      'err_csrf' => 'Помилка безпеки (CSRF).',
      'err_title' => 'Вкажи назву для кожного відео.',
      'err_file' => 'Файл(и) не завантажено.',
      'err_type' => 'Неприпустимий формат відео.',
      'err_size' => 'Один з файлів більший за 500MB.',
      'err_quota' => 'Не вистачає місця по квоті.',
      'ok_uploaded' => 'Відео завантажено.',
      'ok_deleted' => 'Відео видалено.',
      'not_found' => 'Відео не знайдено.',
    ],
    'ru' => [
      'title' => 'Мои видеозаписи',
      'used' => 'Использовано',
      'of' => 'из',
      'free' => 'Свободно',
      'upload' => 'Загрузить',
      'add_row' => 'Добавить ещё видео',
      'video_title' => 'Название видео',
      'choose_file' => 'Видеофайл',
      'max500' => 'max 500MB / файл',
      'quota10' => 'Квота: 10GB на пользователя',
      'library' => 'Видеотека',
      'empty' => 'Пока пусто',
      'watch' => 'Смотреть',
      'delete' => 'Удалить',
      'back' => 'Назад',
      'prev' => 'Предыдущее',
      'next' => 'Следующее',
      'created' => 'Создано',
      'size' => 'Размер',
      'confirm_del' => 'Удалить видео?',
      'err_csrf' => 'Ошибка безопасности (CSRF).',
      'err_title' => 'Укажи название для каждого видео.',
      'err_file' => 'Файл(ы) не загружено.',
      'err_type' => 'Недопустимый формат видео.',
      'err_size' => 'Один из файлов больше 500MB.',
      'err_quota' => 'Недостаточно места по квоте.',
      'ok_uploaded' => 'Видео загружено.',
      'ok_deleted' => 'Видео удалено.',
      'not_found' => 'Видео не найдено.',
    ],
    'en' => [
      'title' => 'My videos',
      'used' => 'Used',
      'of' => 'of',
      'free' => 'Free',
      'upload' => 'Upload',
      'add_row' => 'Add one more',
      'video_title' => 'Video title',
      'choose_file' => 'Video file',
      'max500' => 'max 500MB / file',
      'quota10' => 'Quota: 10GB per user',
      'library' => 'Video library',
      'empty' => 'Empty',
      'watch' => 'Watch',
      'delete' => 'Delete',
      'back' => 'Back',
      'prev' => 'Previous',
      'next' => 'Next',
      'created' => 'Created',
      'size' => 'Size',
      'confirm_del' => 'Delete video?',
      'err_csrf' => 'Security error (CSRF).',
      'err_title' => 'Title is required for each video.',
      'err_file' => 'No file uploaded.',
      'err_type' => 'Unsupported video type.',
      'err_size' => 'One of the files is larger than 500MB.',
      'err_quota' => 'Not enough quota space.',
      'ok_uploaded' => 'Video uploaded.',
      'ok_deleted' => 'Video deleted.',
      'not_found' => 'Video not found.',
    ],
  ];
}

function mv_t(string $k, ?string $lang = null): string {
  $lang = $lang ?: (string)($GLOBALS['mv_lang'] ?? 'uk');
  $i18n = mv_i18n();
  return $i18n[$lang][$k] ?? $i18n['uk'][$k] ?? $k;
}

function mv_format_bytes(int $b): string {
  $units = ['B','KB','MB','GB','TB'];
  $i = 0;
  $v = (float)$b;
  while ($v >= 1024 && $i < count($units)-1) { $v /= 1024; $i++; }
  return rtrim(rtrim(number_format($v, 2, '.', ''), '0'), '.') . ' ' . $units[$i];
}

function mv_mb_substr(string $s, int $start, int $len): string {
  if (function_exists('mb_substr')) return (string)mb_substr($s, $start, $len, 'UTF-8');
  return (string)substr($s, $start, $len);
}

/**
 * Визначаємо корінь сайту (де лежить /includes/start.php і /upload).
 * Робимо пошук по батьківських директоріях (надійніше за "вгадування" ../../..).
 */
function mv_root_dir(): string {
  static $cached = null;
  if (is_string($cached) && $cached !== '') return $cached;

  $dir = realpath(__DIR__);
  if ($dir === false) $dir = __DIR__;

  for ($i = 0; $i < 8; $i++) {
    if (is_file($dir . '/includes/start.php') && is_dir($dir . '/upload')) {
      $cached = $dir;
      return $dir;
    }
    $parent = dirname($dir);
    if ($parent === $dir) break;
    $dir = $parent;
  }

  // fallback: functional/video -> 2 рівні вгору = root
  $fallback = realpath(__DIR__ . '/../../');
  $cached = $fallback ?: dirname(__DIR__, 2);
  return $cached;
}

/**
 * Нікнейм для шляху:
 * - дозволяємо кирилицю/латиницю/цифри/_/-
 * - забороняємо / \ та будь-які “підйоми” типу ..
 */
function mv_safe_username(string $username): string {
  $u = trim($username);
  $u = str_replace(["\0", "/", "\\", ":", "*", "?", "\"", "<", ">", "|"], '', $u);
  $u = preg_replace('~[^\p{L}\p{N}_\-]+~u', '', $u) ?: '';
  if ($u === '' || $u === '.' || $u === '..') $u = 'user';
  return mv_mb_substr($u, 0, 64);
}

function mv_video_dirs(string $username): array {
  $root = mv_root_dir();
  $u = mv_safe_username($username);

  $root = rtrim($root, '/');
  $fsDir  = $root . '/upload/' . $u . '/video';
  $webDir = '/upload/' . $u . '/video';
  return [$fsDir, $webDir, $u];
}

function mv_ensure_video_dir(string $username): array {
  [$fsDir, $webDir, $u] = mv_video_dirs($username);

  if (!is_dir($fsDir)) {
    if (!@mkdir($fsDir, 0755, true) && !is_dir($fsDir)) {
      throw new RuntimeException('Cannot create video dir: ' . $fsDir);
    }
  }

  // захист від лістингу/прямого доступу (стрім через stream.php)
  $ht = $fsDir . '/.htaccess';
  if (!is_file($ht)) {
    $txt = "Options -Indexes\n"
         . "<IfModule mod_authz_core.c>\n"
         . "  Require all denied\n"
         . "</IfModule>\n"
         . "<IfModule !mod_authz_core.c>\n"
         . "  Deny from all\n"
         . "</IfModule>\n";
    @file_put_contents($ht, $txt, LOCK_EX);
  }

  $idx = $fsDir . '/index.html';
  if (!is_file($idx)) {
    @file_put_contents($idx, "<!doctype html><meta charset=\"utf-8\"><title>403</title>", LOCK_EX);
  }

  return [$fsDir, $webDir, $u];
}

/**
 * rel_path (/upload/xxx/video/file.mp4 або upload/xxx/video/file.mp4) -> FS шлях.
 * Додаємо legacy fallback, якщо файл був збережений “вище кореня” (старий баг root_dir).
 */
function mv_rel_to_fs(string $relPath): string {
  $rel = trim((string)$relPath);
  $rel = str_replace(["\\", "\0"], ['/', ''], $rel);
  $rel = preg_replace('~[\r\n]~', '', $rel) ?? $rel;

  $rel = ltrim($rel, '/');
  $root = rtrim(mv_root_dir(), '/');

  $p1 = $root . '/' . $rel;
  if (is_file($p1)) return $p1;

  // legacy: на рівень вище
  $legacyRoot = realpath($root . '/..');
  if ($legacyRoot) {
    $p2 = rtrim($legacyRoot, '/') . '/' . $rel;
    if (is_file($p2)) return $p2;
  }

  return $p1;
}

function mv_fs_is_safe(string $fsPath): bool {
  $root = rtrim(mv_root_dir(), '/');
  $real = realpath($fsPath);
  if ($real === false) return false;

  $upload1 = $root . '/upload/';
  if (mv_starts_with($real, $upload1)) return true;

  $legacyRoot = realpath($root . '/..');
  if ($legacyRoot) {
    $upload2 = rtrim($legacyRoot, '/') . '/upload/';
    if (mv_starts_with($real, $upload2)) return true;
  }

  return false;
}

function mv_allowed_video_mimes(): array {
  return [
    'video/mp4',
    'video/webm',
    'video/ogg',
    'video/quicktime',
    'video/x-m4v',
    'video/x-matroska',
    'application/octet-stream', // інколи приходить так
  ];
}

function mv_allowed_video_exts(): array {
  return ['mp4','webm','ogg','mov','m4v','mkv'];
}

function mv_guess_mime(string $tmpFile, string $fallback = 'application/octet-stream'): string {
  $mime = '';
  if (function_exists('finfo_open')) {
    try {
      $fi = finfo_open(FILEINFO_MIME_TYPE);
      if ($fi) {
        $m = finfo_file($fi, $tmpFile);
        finfo_close($fi);
        if (is_string($m)) $mime = trim($m);
      }
    } catch (Throwable $e) {}
  }
  if ($mime === '') $mime = $fallback;
  $mime = trim(explode(';', $mime, 2)[0]);
  return $mime !== '' ? $mime : $fallback;
}

function mv_ext_from_name(string $name): string {
  $name = trim($name);
  $name = str_replace(["\0", "\r", "\n"], '', $name);
  $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
  $ext = preg_replace('~[^a-z0-9]+~', '', $ext) ?? '';
  return $ext;
}

function mv_is_allowed_video(string $ext, string $mime): bool {
  $ext = strtolower(trim($ext));
  $mime = strtolower(trim($mime));
  if ($mime !== '') $mime = trim(explode(';', $mime, 2)[0]);

  $okExt = in_array($ext, mv_allowed_video_exts(), true);
  $okMime = in_array($mime, mv_allowed_video_mimes(), true);

  // якщо mime "octet-stream", але ext норм — приймаємо
  if ($mime === 'application/octet-stream' && $okExt) return true;

  // якщо mime норм, але ext пустий/дивний — теж приймаємо
  if ($okMime && $ext === '') return true;

  return $okExt && $okMime;
}

/* ===== DB schema helpers (fallback) ===== */
if (!function_exists('db_has_table')) {
  function db_has_table(PDO $pdo, string $table): bool {
    try {
      $st = $pdo->prepare("SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ? LIMIT 1");
      $st->execute([$table]);
      return (bool)$st->fetchColumn();
    } catch (Throwable $e) { return false; }
  }
}
if (!function_exists('db_has_column')) {
  function db_has_column(PDO $pdo, string $table, string $col): bool {
    try {
      $st = $pdo->prepare("SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ? LIMIT 1");
      $st->execute([$table, $col]);
      return (bool)$st->fetchColumn();
    } catch (Throwable $e) { return false; }
  }
}

/**
 * Використано по квоті:
 * - швидко через БД: SUM(myvideos.size_bytes)
 * - fallback: через розмір директорії /upload/<u>/video (якщо БД недоступна)
 */
function mv_user_used_bytes(?PDO $pdo, int $userId, string $fsDirFallback = ''): int {
  if ($pdo instanceof PDO && $userId > 0) {
    try {
      if (db_has_table($pdo, MV_VIDEOS_TABLE) && db_has_column($pdo, MV_VIDEOS_TABLE, 'size_bytes')) {
        $st = $pdo->prepare("SELECT COALESCE(SUM(size_bytes),0) FROM " . MV_VIDEOS_TABLE . " WHERE user_id=?");
        $st->execute([$userId]);
        $sum = $st->fetchColumn();
        return (int)$sum;
      }
    } catch (Throwable $e) {
      // fallback нижче
    }
  }

  // filesystem fallback
  $dir = $fsDirFallback;
  if ($dir === '' || !is_dir($dir)) return 0;

  $total = 0;
  try {
    $it = new RecursiveIteratorIterator(
      new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
      RecursiveIteratorIterator::SELF_FIRST
    );
    foreach ($it as $f) {
      /** @var SplFileInfo $f */
      if ($f->isFile()) {
        $sz = $f->getSize();
        if (is_int($sz) && $sz > 0) $total += $sz;
      }
    }
  } catch (Throwable $e) {
    return 0;
  }
  return $total;
}

function mv_quota_left_bytes(int $used): int {
  $left = MV_QUOTA_MAX_BYTES - max(0, $used);
  return $left > 0 ? $left : 0;
}

function mv_can_fit_quota(int $used, int $addBytes): bool {
  if ($addBytes <= 0) return true;
  return ($used + $addBytes) <= MV_QUOTA_MAX_BYTES;
}

/**
 * Рекомендовано викликати на сторінках модуля відео (це персональна зона):
 * - noindex/nofollow (через X-Robots-Tag)
 * - за бажанням сторінки можуть ще виставити $seo_noindex=true
 */
function mv_private_seo_headers(): void {
  $GLOBALS['seo_noindex'] = true;
  if (!headers_sent()) {
    header('X-Robots-Tag: noindex, nofollow, noarchive, nosnippet', true);
  }
}

/**
 * Генерує безпечне ім'я файлу для збереження (тільки латиниця/цифри/._-).
 */
function mv_make_stored_name(string $ext): string {
  $ext = strtolower(trim($ext));
  $ext = preg_replace('~[^a-z0-9]+~', '', $ext) ?? '';
  $ext = $ext !== '' ? ('.' . $ext) : '';

  $stamp = date('Ymd_His');
  $rnd = bin2hex(random_bytes(8));
  return $stamp . '_' . $rnd . $ext;
}