/**
 * Mobile-side voe.sx resolver.
 *
 * VOE CDN URL (cloudwindow-route.com) ma `i=<2-oktetowy prefix IP>` — token bound do /16
 * IP które POBRAŁO embed (audit 2026-06-11: z VPS `i=46.62`, master.m3u8 z innego /16 →
 * 403). Backend resolvuje z VPS → telefon dostaje URL bound do /16 VPS → direct 403 →
 * pełny proxy → CAŁE wideo przez Hetzner (131 hitów/48h).
 *
 * Fix: telefon sam pobiera embed (phone IP) → token bound do jego /16 → gra direct,
 * zero VPS. Port 7-stopniowego dekodera z app/extractors/hosters/voe.py (rot13 → replace
 * 7 magic-sepów → strip _ → atob → shift -3 → reverse → atob → JSON.parse).
 */
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 _REDIRECT_RE = /window\.location\.href\s*=\s*['"]([^'"]+)['"]/;
const _PAYLOAD_RE = /<script\s+type=["']application\/json["']>(\[[\s\S]+?\])<\/script>/;
const _MAGIC_SEPS = ['@$', '^^', '~@', '%?', '*~', '!!', '#&'];
const _B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

/** True dla embed/page voe.sx (origin *:voe). */
export function isVoeUrl(url: string | null | undefined): boolean {
  return !!url && /(?:^|\/\/|\.)voe\.sx\//i.test(url);
}

function rot13(s: string): string {
  let out = '';
  for (let i = 0; i < s.length; i++) {
    const c = s.charCodeAt(i);
    if (c >= 0x41 && c <= 0x5a) out += String.fromCharCode(((c - 0x41 + 13) % 26) + 0x41);
    else if (c >= 0x61 && c <= 0x7a) out += String.fromCharCode(((c - 0x61 + 13) % 26) + 0x61);
    else out += s[i];
  }
  return out;
}

/** Standard base64 → bytes (self-contained, bez atob/Buffer). */
function b64ToBytes(b64: string): Uint8Array {
  const lookup = new Int16Array(128).fill(-1);
  for (let i = 0; i < _B64.length; i++) lookup[_B64.charCodeAt(i)] = i;
  const clean = b64.replace(/=+$/, '');
  const out = new Uint8Array(Math.floor((clean.length * 6) / 8));
  let bits = 0;
  let acc = 0;
  let oi = 0;
  for (let i = 0; i < clean.length; i++) {
    const v = lookup[clean.charCodeAt(i) & 0x7f];
    if (v < 0) continue; // pomiń whitespace/nieznane
    acc = (acc << 6) | v;
    bits += 6;
    if (bits >= 8) {
      bits -= 8;
      out[oi++] = (acc >>> bits) & 0xff;
    }
  }
  return out.subarray(0, oi);
}

/** bytes → UTF-8 string (minimalny dekoder, bez TextDecoder). */
function utf8Decode(bytes: Uint8Array): string {
  let out = '';
  let i = 0;
  while (i < bytes.length) {
    const b = bytes[i++];
    if (b < 0x80) out += String.fromCharCode(b);
    else if (b >= 0xc0 && b < 0xe0) out += String.fromCharCode(((b & 0x1f) << 6) | (bytes[i++] & 0x3f));
    else if (b >= 0xe0 && b < 0xf0)
      out += String.fromCharCode(((b & 0x0f) << 12) | ((bytes[i++] & 0x3f) << 6) | (bytes[i++] & 0x3f));
    else {
      const cp = ((b & 0x07) << 18) | ((bytes[i++] & 0x3f) << 12) | ((bytes[i++] & 0x3f) << 6) | (bytes[i++] & 0x3f);
      const c = cp - 0x10000;
      out += String.fromCharCode(0xd800 + (c >> 10), 0xdc00 + (c & 0x3ff));
    }
  }
  return out;
}

/** 7-stopniowy dekoder payloadu (port _decode_payload z voe.py). */
function decodePayload(payload: string): any | null {
  try {
    let s = rot13(payload);
    for (const sep of _MAGIC_SEPS) s = s.split(sep).join('_');
    s = s.split('_').join('');
    const bytes1 = b64ToBytes(s); // 1st atob (latin-1)
    let shifted = '';
    for (let i = 0; i < bytes1.length; i++) shifted += String.fromCharCode(bytes1[i] - 3);
    const reversed = shifted.split('').reverse().join('');
    const bytes2 = b64ToBytes(reversed); // 2nd atob
    return JSON.parse(utf8Decode(bytes2));
  } catch {
    return null;
  }
}

/**
 * Pobiera voe.sx embed NA TELEFONIE → m3u8 (+ mp4 fallback) StreamLink-i (phone-IP-bound,
 * grają direct). Zwraca [] gdy brak/dead (caller spada na backend). Mirror voe.py.
 */
export async function resolveVoePage(embedUrl: string): Promise<StreamLink[]> {
  const referer = 'https://voe.sx/';
  let html: string;
  try {
    const r = await fetch(embedUrl, { headers: { 'User-Agent': UA, Accept: 'text/html' } });
    if (!r.ok) return [];
    html = await r.text();
  } catch {
    return [];
  }
  // Stage 1: JS redirect do losowego mirroru.
  if (html.includes('window.location.href')) {
    const m = _REDIRECT_RE.exec(html);
    if (!m) return [];
    try {
      const r2 = await fetch(m[1], { headers: { 'User-Agent': UA, Accept: 'text/html' } });
      if (!r2.ok) return [];
      html = await r2.text();
    } catch {
      return [];
    }
  }
  // Stage 2: extract + decode JSON payload.
  const pm = _PAYLOAD_RE.exec(html);
  if (!pm) return [];
  let payload: string | null = null;
  try {
    const arr = JSON.parse(pm[1]);
    payload = Array.isArray(arr) && arr.length ? arr[0] : null;
  } catch {
    return [];
  }
  if (typeof payload !== 'string') return [];
  const config = decodePayload(payload);
  if (!config) return [];

  const links: StreamLink[] = [];
  const source = (config.source || '').trim();
  if (source) {
    links.push({
      stream_url: source,
      direct_url: source,
      headers: { Referer: referer, 'User-Agent': UA },
      quality: 'auto',
      type: 'm3u8',
    });
  }
  let fallback = config.fallback || [];
  if (fallback && !Array.isArray(fallback)) fallback = [fallback];
  for (const fb of fallback) {
    if (fb && typeof fb === 'object' && fb.file) {
      links.push({
        stream_url: fb.file,
        direct_url: fb.file,
        headers: { Referer: referer, 'User-Agent': UA },
        quality: fb.label || 'auto',
        type: 'mp4',
      });
    }
  }
  return links;
}
