/**
 * Mobile-side DoodStream resolver.
 *
 * Powód: VPS Hetzner IP dostaje Cloudflare Turnstile gate dla playmogo/dood
 * embed pages (5KB challenge response). Mobile IP user'a (T-Mobile/PLAY/wifi)
 * pewnie nie triggers CAPTCHA — embed page renderuje pełny HTML z player JS,
 * w którym jest `pass_md5` token + `splash_error` key. To wystarczy do
 * obliczenia direct mp4 URL.
 *
 * Algorithm (reverse-engineered z porn-app APK, com.streamdev.aiostreamer
 * via jadx — class `defpackage/rs0.java`):
 *
 *   1. Replace /d/ → /e/ w URL (playmogo używa obu)
 *   2. Fetch embed page z Chrome UA + Referer = source page
 *   3. Regex w HTML:
 *        token  = $.get('/pass_md5/<TOKEN>',  ← pierwszy parametr
 *        key    = $.get('/?op=splash_error', '<KEY>');  ← drugi parametr
 *   4. Fetch https://<host>/pass_md5/<TOKEN> → response body = base URL
 *   5. Final URL = base + key (od porn-app: append key to base)
 *      + ?token=<KEY> jak w starszym format też się próbuje
 *   6. ExoPlayer plays z headers Referer = embed page
 *
 * Wspierane DoodStream variants (sync z mobile/src/lib/realdebrid.ts):
 *   playmogo, doodstream, dood.*, d0000d, dooood, do0od, do7go, ds2play
 *
 * Returns: { url, headers } gdy sukces, lub { error } gdy fail. Fail może
 * znaczyć CAPTCHA gate dla user IP — wtedy fallback do WebView.
 */

const DOOD_HOSTS = new Set([
  'playmogo.com',
  // doply.net 301 → playmogo.com (verified 2026-05-15 — doodcdn.io scripts +
  // pass_md5 endpoint w body). 205 mango movies origin='mangoporn:doply'.
  'doply.net',
  'doodstream.com', 'doodporn.com',
  'dood.la', 'dood.li', 'dood.ws', 'dood.so', 'dood.to',
  'dood.watch', 'dood.work', 'dood.yt', 'dood.re',
  'ds2play.com', 'ds2video.com',
  'd000d.com', 'd0o0d.com', 'do0od.com', 'do7go.com',
  'dooood.com', 'd0000d.com',
]);

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';

export function isDoodStream(url: string): boolean {
  if (!url) return false;
  try {
    const u = new URL(url);
    const host = u.hostname.toLowerCase().replace(/^www\./, '');
    if (DOOD_HOSTS.has(host)) return true;
    for (const h of DOOD_HOSTS) {
      if (host.endsWith('.' + h)) return true;
    }
  } catch {}
  return false;
}

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

function randomString(n: number): string {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  let out = '';
  for (let i = 0; i < n; i++) out += chars[Math.floor(Math.random() * chars.length)];
  return out;
}

// Błędy które warto powtórzyć (świeży fetch embeda = nowy token / inny CF routing).
// playmogo/dood są FLAKY: ten sam URL daje raz Turnstile-gate (brak pass_md5), raz
// pass_md5='RELOAD' (token zużyty/wygasł), raz pełny player. Zweryfikowane 2026-06-06
// cross-IP (Bright Data): 5 prób = 2× gate, 1× RELOAD, 1× zły token (200 text/html),
// 1× 206 video/mp4. Bez retry mobile trafiał na fail → WebView „loading w nieskończoność"
// (bug-report 62e78c9a porndish). Realne IP telefonu (carrier) gating'uje rzadziej niż
// Bright Data, więc 3 próby zwykle łapią sukces.
const _RETRYABLE = new Set([
  'no_pass_md5_in_html',
  'pass_md5_empty_response',
  'pass_md5_reload',
  'no_splash_error_or_token',
  'captcha_gate (mobile IP also blocked?)',
]);

function _isRetryable(err?: string): boolean {
  return !!err && _RETRYABLE.has(err);
}

export async function resolveDoodStream(
  embedUrl: string,
  sourceUrl?: string,
): Promise<ResolveResult> {
  if (!isDoodStream(embedUrl)) return { error: 'not_doodstream' };
  let last: ResolveResult = { error: 'no_attempt' };
  for (let attempt = 0; attempt < 3; attempt++) {
    last = await _resolveDoodStreamOnce(embedUrl, sourceUrl);
    if (last.url) return last; // sukces
    if (!_isRetryable(last.error)) return last; // permanentny błąd (deleted, http 4xx/5xx) — nie retry
    // retryable (gate/RELOAD/stale token) → kolejna próba pobiera embed na nowo (nowy token)
  }
  return last;
}

async function _resolveDoodStreamOnce(
  embedUrl: string,
  sourceUrl?: string,
): Promise<ResolveResult> {
  // /d/ (download page) → /e/ (embed player page). Embed ma JS z pass_md5,
  // download page ma countdown + button. Porn-app rs0.java: linia 38.
  const url = embedUrl.replace('/d/', '/e/');
  const initialHost = new URL(url).hostname;
  const referer = sourceUrl || `https://${initialHost}/`;

  // Fetch embed page. WAŻNE: d0000d.com (i kilka innych dood mirrorów) robi
  // server-side 30x redirect do playmogo.com gdy CF challenge solved — wtedy
  // pass_md5 endpoint musi iść na host PO redirekcie (playmogo), nie na
  // original (d0000d). Bug-report e9ba57d9 + 001a72c7 2026-05-16: scena
  // "przekierowuje na stronę" bo resolver kierował pass_md5 na d0000d/
  // post-redirect 404.
  //
  // RN/Hermes quirk: `Response.url` często jest pustym stringiem na Android
  // po follow-redirects (bug w native networking stack). Manualnie chodzimy
  // po Location headerach żeby host PO redirekcie był deterministyczny.
  let html: string;
  let host = initialHost;
  let currentUrl = url;
  try {
    for (let hops = 0; hops < 5; hops++) {
      const r = await fetch(currentUrl, {
        method: 'GET',
        headers: { 'User-Agent': UA, 'Referer': referer },
        redirect: 'manual',
      });
      // 30x → resolve Location, kontynuuj. Inaczej traktuj jako final.
      if (r.status >= 300 && r.status < 400) {
        const loc = r.headers.get('location');
        if (!loc) return { error: `redirect ${r.status} without Location` };
        try {
          currentUrl = new URL(loc, currentUrl).toString();
        } catch {
          return { error: `bad Location url: ${loc}` };
        }
        continue;
      }
      if (!r.ok) return { error: `embed http ${r.status}` };
      html = await r.text();
      // Final host = post-redirect URL. r.url też preferujemy jeśli niepuste
      // (na iOS bywa wypełnione), inaczej parse currentUrl.
      const finalUrl = r.url || currentUrl;
      try {
        host = new URL(finalUrl).hostname;
      } catch {}
      return await _continueAfterFetch(html, host, referer);
    }
    return { error: 'too many redirects (5)' };
  } catch (e: any) {
    return { error: `embed fetch fail: ${e?.message || e}` };
  }
}

async function _continueAfterFetch(
  html: string,
  host: string,
  referer: string,
): Promise<ResolveResult> {

  if (html.toLowerCase().includes('video not found')) {
    return { error: 'video_deleted' };
  }

  // pass_md5: '$.get('/pass_md5/<HASH>/<KEY>', ...)'. KEY = last path segment;
  // identical do `?token=<KEY>` w makePlay/splash_error. 2026-05-15 playmogo
  // przeszło z 2-arg splash_error (key jako drugi parametr) na 1-arg z token
  // inside URL — trzymamy KEY z path zamiast osobnego regex matchu.
  const passMatch = html.match(/\$\.get\(\s*['"]\/(pass_md5\/[^'"]+)['"]/);
  if (!passMatch) {
    // Brak pass_md5 = pełny captcha gate (5KB body z VPS IP), pusta strona, etc.
    // Tu odsyłamy do WebView fallback. Sam `turnstile` w HTML nie wystarcza —
    // pełna strona playera ZAWIERA opcjonalny turnstile container.
    if (html.length < 2000 || /challenge-platform/i.test(html)) {
      return { error: 'captcha_gate (mobile IP also blocked?)' };
    }
    return { error: 'no_pass_md5_in_html' };
  }

  // Extract KEY = last segment of pass_md5 path: "pass_md5/<HASH>/<KEY>"
  const passPath = passMatch[1];
  const keyFromPath = passPath.split('/').pop();
  if (keyFromPath) {
    return await fetchFinalUrl(host, passPath, keyFromPath, referer);
  }

  // Legacy fallback: 2-arg splash_error (stary format, jeszcze może być na dood mirrorach)
  const splashMatch = html.match(/\$\.get\(\s*['"]\/\?op=splash_error[^'"]*['"]\s*,\s*['"]([^'"]+)['"]/);
  if (splashMatch) {
    return await fetchFinalUrl(host, passPath, splashMatch[1], referer);
  }
  const tokenMatch = html.match(/\?token=([^&"']+)/);
  if (!tokenMatch) return { error: 'no_splash_error_or_token' };
  return await fetchFinalUrl(host, passPath, tokenMatch[1], referer);
}

async function fetchFinalUrl(
  host: string,
  passMd5Path: string,
  key: string,
  referer: string,
): Promise<ResolveResult> {
  const passUrl = `https://${host}/${passMd5Path}`;

  let baseUrl: string;
  try {
    const r = await fetch(passUrl, {
      method: 'GET',
      headers: { 'User-Agent': UA, 'Referer': referer },
    });
    if (!r.ok) return { error: `pass_md5 http ${r.status}` };
    baseUrl = (await r.text()).trim();
    if (!baseUrl) return { error: 'pass_md5_empty_response' };
    // DoodStream zwraca literalnie "RELOAD" (lub inny nie-URL) gdy token pass_md5
    // jest zużyty/wygasł — trzeba przeładować embed po świeży token. Bez tej walidacji
    // budowaliśmy `RELOAD<suffix>?token=...` → ExoPlayer „no extractors" → WebView.
    if (!/^https?:\/\//i.test(baseUrl)) return { error: 'pass_md5_reload' };
  } catch (e: any) {
    return { error: `pass_md5 fetch fail: ${e?.message || e}` };
  }

  // Final URL construction. Porn-app format (z rs0.java): base + randomChars + ?token=<key>&expiry=<timestamp>
  // (animdl używa randomChars per dood spec; key idzie jako token query param).
  const randomSuffix = randomString(10);
  const expiry = Date.now();
  const finalUrl = `${baseUrl}${randomSuffix}?token=${key}&expiry=${expiry}`;

  return {
    url: finalUrl,
    headers: {
      'User-Agent': UA,
      'Referer': referer,
    },
  };
}
