/**
 * Mobile-side fpo.xxx (fpoxxx) resolver — KVS (kt_player) phone-side.
 *
 * fpoxxx to KVS: flashvars `video_url` (czasem `function/0/...` zaszyfrowane, czasem już
 * plain `.../get_file/...`) + `license_code`. get_file 302-redirectuje na CDN
 * (videosN.fpo.xxx/remote_control.php) z `acctoken` BOUND DO IP fetchera + SESSION-bound
 * (audit 2026-06-19: acctoken zawierał IP VPS, cross-IP = 403). Backend resolwuje z VPS →
 * telefon dostaje URL bound do IP VPS → 403. Dawniej fallback = WebView, ale ad-heavy
 * strona wstrzykuje VAST preroll → WebView łapie REKLAMĘ zamiast wideo (bug-report
 * f79beefb / cfa207c7).
 *
 * Fix: telefon sam pobiera stronę (phone IP + ta sama sesja co get_file), deobfuskuje
 * KVS i follow 302 → finalny CDN URL bound do telefonu → gra direct, bez WebView/reklam.
 * Port deobfuskacji z app/extractors/tubes/_kvs.py (real_url/_license_token, algo zgodny
 * z yt-dlp KVS). 302-follow reużyty z getfileResolver (resolveGetFile).
 */
import { resolveGetFile } from './getfileResolver';
import type { StreamLink } from '../types';

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

const _LICENSE_RE = /license_code\s*:\s*['"](\$[^'"]+)['"]/i;
const _URL_RE =
  /(video(?:_alt)?_url\d*)\s*:\s*['"](function\/0\/[^'"]+|https?:\/\/[^'"]*?\/get_file\/[^'"]*)['"]/gi;
const _TEXT_RE = /(video(?:_alt)?_url\d*)_text\s*:\s*['"]([^'"]*)['"]/gi;

const _HASH_LEN = 32;

/** Port _license_token z _kvs.py (algo KVS / yt-dlp). */
function licenseToken(licenseCode: string): number[] {
  const lc = licenseCode.replace(/\$/g, '');
  const values = [...lc].map((c) => parseInt(c, 10));
  let mod = lc.replace(/0/g, '1');
  const center = Math.floor(mod.length / 2);
  const left = parseInt(mod.slice(0, center + 1), 10);
  const right = parseInt(mod.slice(center), 10);
  mod = String(4 * Math.abs(left - right)).slice(0, center + 1);
  const out: number[] = [];
  for (let index = 0; index < mod.length; index++) {
    const current = parseInt(mod[index], 10);
    for (let offset = 0; offset < 4; offset++) {
      out.push(((values[index + offset] ?? 0) + current) % 10);
    }
  }
  return out;
}

/** Port real_url z _kvs.py — permutuje pierwsze 32 znaki hash-segmentu. Plain get_file
 *  (bez `function/0/`) zwraca bez zmian. */
function realUrl(videoUrl: string, licenseCode: string): string {
  const PREFIX = 'function/0/';
  if (!videoUrl.startsWith(PREFIX)) return videoUrl;
  let u: URL;
  try {
    u = new URL(videoUrl.slice(PREFIX.length));
  } catch {
    return videoUrl;
  }
  const lt = licenseToken(licenseCode);
  if (lt.length < _HASH_LEN) return videoUrl;
  const parts = u.pathname.split('/');
  if (parts.length <= 3) return videoUrl;
  const seg = parts[3];
  const h = seg.slice(0, _HASH_LEN);
  if (h.length < _HASH_LEN) return videoUrl;
  const idx = Array.from({ length: _HASH_LEN }, (_, i) => i);
  let acc = 0;
  for (let src = _HASH_LEN - 1; src >= 0; src--) {
    acc += lt[src];
    const dest = (src + acc) % _HASH_LEN;
    [idx[src], idx[dest]] = [idx[dest], idx[src]];
  }
  let permuted = '';
  for (let i = 0; i < _HASH_LEN; i++) permuted += h[idx[i]];
  parts[3] = permuted + seg.slice(_HASH_LEN);
  u.pathname = parts.join('/');
  return u.toString();
}

function qualityRank(label: string | null | undefined): number {
  if (!label) return -1;
  const m = /(\d{3,4})\s*p/i.exec(label);
  return m ? parseInt(m[1], 10) : -1;
}

/**
 * Pobiera fpo.xxx stronę NA TELEFONIE → deobfuskuje KVS → follow get_file 302 → mp4
 * StreamLink-i (phone-IP-bound, grają direct). Zwraca [] gdy brak (caller spada na
 * backend/WebView). Mirror resolve_kvs z _kvs.py.
 */
export async function resolveFpoxxxPage(pageUrl: string): Promise<StreamLink[]> {
  let html: string;
  try {
    const r = await fetch(pageUrl, { headers: { 'User-Agent': UA, Accept: 'text/html' } });
    if (!r.ok) return [];
    html = await r.text();
  } catch {
    return [];
  }
  const licm = _LICENSE_RE.exec(html);
  if (!licm) return [];
  const licenseCode = licm[1];

  // jakości po `<var>_text`
  const qualityByVar: Record<string, string> = {};
  let tm: RegExpExecArray | null;
  _TEXT_RE.lastIndex = 0;
  while ((tm = _TEXT_RE.exec(html)) !== null) {
    qualityByVar[tm[1].toLowerCase()] = tm[2].trim();
  }

  let origin: string;
  try {
    origin = new URL(pageUrl).origin;
  } catch {
    origin = 'https://www.fpo.xxx';
  }
  const referer = origin + '/';

  const seen = new Set<string>();
  const links: StreamLink[] = [];
  let fails = 0;
  let um: RegExpExecArray | null;
  _URL_RE.lastIndex = 0;
  while ((um = _URL_RE.exec(html)) !== null) {
    const varName = um[1].toLowerCase();
    const decoded = realUrl(um[2], licenseCode);
    if (seen.has(decoded)) continue;
    seen.add(decoded);
    const final = await resolveGetFile(decoded, referer);
    if (!final) {
      fails += 1;
      if (fails >= 2 && links.length === 0) break; // martwa scena — nie czekaj dalej
      continue;
    }
    links.push({
      stream_url: final,
      direct_url: final,
      headers: { Referer: referer, 'User-Agent': UA },
      quality: qualityByVar[varName] || null,
      type: 'mp4',
    });
  }

  links.sort((a, b) => qualityRank(b.quality) - qualityRank(a.quality));
  return links;
}
