/**
 * Mobile-side filemoon resolver.
 *
 * filemoon (i jego mirrory: filemoon.sx/to/in/nl, kerapoxy.cc, lvturbo.com,
 * bysezoxexe.com, bysezejataos.com, ...) przeszedł ~2026-05 rebrand na SPA
 * "Byse Frontend" (Vite). Stary P.A.C.K.E.R.-JWPlayer embed zniknął, więc
 * `extract_stream_from_hoster` na backendzie zwraca pustkę → wcześniej filemoon
 * był na DEAD_HOSTER_RE blacklist (błędnie uznany za "globalny shutdown").
 *
 * RE bundla `index-ChwZgmXV.js` / `videoPagesBundle`: SPA pobiera stream przez
 * prywatne JSON API (potwierdzone 2026-05-22):
 *
 *   POST https://<host>/api/videos/<code>/embed/playback
 *        body: {"fingerprint":{}}            ← pusty obiekt wystarcza,
 *                                              browser-attestation jest opcjonalny
 *   → {"playback":{"key_parts":[b64url,b64url],"iv":b64url,"payload":b64url}}
 *
 *   key       = concat(b64urlDecode(p) for p in key_parts)   → 32 bajty (AES-256)
 *   plaintext = AES-256-GCM(key, iv, payload)                → JSON
 *   plaintext.sources[*].url                                 → HLS master.m3u8
 *
 * Stream URL (sprintcdn `…/hls2/…/master.m3u8?t=…&asn=…`) jest TIME+IP-bound:
 * token wiąże się z IP które zrobiło POST /playback. Dlatego resolve MUSI iść
 * z urządzenia użytkownika (jak doodstream.ts / packerHoster.ts) — VPS-side
 * resolve dałby URL działający tylko z VPS IP (= VPS-proxied bandwidth).
 *
 * AES-GCM liczone w czystym JS (`aesGcm.ts`) — RN/Hermes nie ma Web Crypto.
 */

import { aesGcmDecrypt, b64urlDecode } from './aesGcm';

// Znane domeny rodziny filemoon. Rebrand rotuje domeny — gdy pojawi się nowa,
// dopisać tutaj (i usunąć z backendowego DEAD_HOSTER_RE jeśli tam wpadła).
const FILEMOON_RE =
  /(?:^|\.)(?:filemoon\.[a-z]{2,4}|kerapoxy\.[a-z]{2,4}|lvturbo\.[a-z]{2,4}|emturbovid\.[a-z]{2,4}|bysezoxexe\.[a-z]{2,4}|bysezejataos\.[a-z]{2,4}|moonseries\.[a-z]{2,4})$/i;

// Kod video w ścieżce: /e/<code>, /d/<code>, /download/<code>, /dwn/<code>, /f/<code>.
const CODE_RE = /\/(?:e|d|f|v|download|dwn)\/([a-zA-Z0-9]{6,})/;

const UA =
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';

export interface ResolveResult {
  url?: string;
  headers?: Record<string, string>;
  error?: string;
}

interface FilemoonSource {
  url?: string;
  label?: string;
  height?: number;
  mime_type?: string;
}

export function isFilemoonHoster(url: string): boolean {
  if (!url) return false;
  try {
    const host = new URL(url).hostname.toLowerCase().replace(/^www\./, '');
    return FILEMOON_RE.test(host);
  } catch {
    return false;
  }
}

function parsePlaybackKey(parts: string[]): Uint8Array {
  const decoded = parts.map(b64urlDecode);
  const total = decoded.reduce((acc, p) => acc + p.length, 0);
  const key = new Uint8Array(total);
  let off = 0;
  for (const p of decoded) {
    key.set(p, off);
    off += p.length;
  }
  return key;
}

export async function resolveFilemoonHoster(
  embedUrl: string,
  sourceUrl?: string,
): Promise<ResolveResult> {
  if (!isFilemoonHoster(embedUrl)) return { error: 'not_filemoon_hoster' };

  let host: string;
  try {
    host = new URL(embedUrl).hostname;
  } catch {
    return { error: 'bad_embed_url' };
  }
  const codeMatch = embedUrl.match(CODE_RE);
  if (!codeMatch) return { error: 'no_code_in_url' };
  const code = codeMatch[1];

  const origin = `https://${host}`;
  const playbackApi = `${origin}/api/videos/${encodeURIComponent(code)}/embed/playback`;

  // 1) POST /playback — pusty fingerprint omija browser-attestation.
  let payload: { key_parts?: string[]; iv?: string; payload?: string } | undefined;
  try {
    const r = await fetch(playbackApi, {
      method: 'POST',
      headers: {
        'User-Agent': UA,
        'Content-Type': 'application/json',
        Accept: 'application/json',
        Origin: origin,
        Referer: `${origin}/e/${code}/`,
      },
      body: JSON.stringify({ fingerprint: {} }),
    });
    if (!r.ok) {
      const body = await r.text();
      if (/not found/i.test(body)) return { error: 'video_deleted' };
      return { error: `playback http ${r.status}` };
    }
    const json = await r.json();
    payload = json?.playback;
  } catch (e: any) {
    return { error: `playback fetch fail: ${e?.message || e}` };
  }

  if (!payload || !Array.isArray(payload.key_parts) || !payload.iv || !payload.payload) {
    return { error: 'no_playback_payload' };
  }

  // 2) Rozszyfruj AES-256-GCM.
  let sources: FilemoonSource[];
  try {
    const key = parsePlaybackKey(payload.key_parts);
    const plain = aesGcmDecrypt(
      key,
      b64urlDecode(payload.iv),
      b64urlDecode(payload.payload),
    );
    const decoded = JSON.parse(new TextDecoder().decode(plain));
    sources = Array.isArray(decoded?.sources) ? decoded.sources : [];
  } catch (e: any) {
    return { error: `decrypt fail: ${e?.message || e}` };
  }

  // 3) Wybierz najlepsze źródło (najwyższy `height`). Każdy URL to master.m3u8
  //    z wariantami w środku — ExoPlayer i tak zrobi adaptację.
  const valid = sources.filter((s) => typeof s.url === 'string' && s.url.length > 0);
  if (valid.length === 0) return { error: 'no_sources' };
  valid.sort((a, b) => (b.height || 0) - (a.height || 0));
  const best = valid[0];

  // Stream sprintcdn nie wymaga Referera (token jest IP-bound) — przekazujemy
  // tylko UA. sourceUrl celowo ignorowany: CDN nie sprawdza pochodzenia.
  void sourceUrl;
  return {
    url: best.url,
    headers: { 'User-Agent': UA },
  };
}
