"""
RAIO Course Downloader v3
Navega lecciones en ClickFunnels e intercepta URLs de video de Vidalytics en tiempo real.
"""
import asyncio
import json
import re
import subprocess
import sys
import unicodedata
from pathlib import Path

from playwright.async_api import async_playwright
from bs4 import BeautifulSoup

sys.stdout.reconfigure(encoding="utf-8")

BASE_URL    = "https://kaleanders.clickfunnels.com"
LOGIN_PAGE  = f"{BASE_URL}/login-raio31910526?page_id=31910527&page_key=kyy8t5uqnf4ym1ng&login_redirect=1"
MEMBERS_URL = f"{BASE_URL}/members-raio"
EMAIL    = "kokimobil35@gmail.com"
PASSWORD = "Korea123.@!@#"

BASE_DIR    = Path(__file__).parent.parent
VIDEOS_DIR  = BASE_DIR / "storage" / "app" / "course-media" / "curso-de-ingles-raio"
SCRIPTS_DIR = BASE_DIR / "scripts"
COOKIES_FILE = SCRIPTS_DIR / ".course_cookies.json"
STRUCTURE_FILE = SCRIPTS_DIR / "course_structure.json"

VIDEOS_DIR.mkdir(parents=True, exist_ok=True)

def log(msg, level="INFO"):
    icons = {"INFO": "  ", "OK": "[OK]", "SKIP": "[--]", "ERROR": "[!!]", "HEAD": "\n==="}
    print(f"{icons.get(level,'  ')} {msg}", flush=True)

def slugify(text):
    text = unicodedata.normalize("NFD", text.lower())
    text = "".join(c for c in text if unicodedata.category(c) != "Mn")
    return re.sub(r"[-\s]+", "-", re.sub(r"[^a-z0-9\s-]", "", text)).strip("-")

# ── Login ──────────────────────────────────────────────────────────────────────

async def do_login(ctx) -> bool:
    page = await ctx.new_page()
    await page.goto(LOGIN_PAGE, wait_until="domcontentloaded", timeout=30000)
    await page.wait_for_timeout(2500)
    await page.evaluate(f"""() => {{
        const f = document.querySelector('#login-form');
        if (!f) return;
        const e = f.querySelector('input[name="member[email]"]');
        const p = f.querySelector('input[name="member[password]"]');
        if (e) {{ e.value = "{EMAIL}"; e.dispatchEvent(new Event('input',{{bubbles:true}})); }}
        if (p) {{ p.value = "{PASSWORD}"; p.dispatchEvent(new Event('input',{{bubbles:true}})); }}
        const form = f.querySelector('form');
        if (form) form.submit();
    }}""")
    try:
        await page.wait_for_load_state("domcontentloaded", timeout=15000)
    except Exception:
        pass
    await page.wait_for_timeout(3000)
    ok = "members" in page.url
    log(f"Login {'OK' if ok else 'FALLO'} -> {page.url}", "OK" if ok else "ERROR")
    await page.close()
    return ok

# ── Extraccion de lecciones con intercepcion de red ───────────────────────────

async def scrape_with_interception(ctx) -> list:
    page = await ctx.new_page()

    # ── Intercepcion de red ────────────────────────────────────────────────────
    # Captura todas las URLs de video/stream que Vidalytics carga
    current_video_urls = {"mp4": [], "m3u8": [], "api": []}

    async def on_response(response):
        url = response.url
        ctype = response.headers.get("content-type", "")
        if ".mp4" in url or "video/mp4" in ctype:
            current_video_urls["mp4"].append(url)
        if ".m3u8" in url or "mpegurl" in ctype:
            current_video_urls["m3u8"].append(url)
        if "vidalytics" in url and ("api" in url or "config" in url or "token" in url):
            try:
                body = await response.body()
                current_video_urls["api"].append({"url": url, "body": body.decode("utf-8", errors="ignore")[:2000]})
            except Exception:
                pass

    page.on("response", on_response)

    log(f"Cargando curso...")
    await page.goto(MEMBERS_URL, wait_until="domcontentloaded", timeout=30000)
    await page.wait_for_timeout(5000)  # dejar que Vidalytics cargue

    lessons = []
    lesson_num = 0
    max_lessons = 80
    seen_video_ids = set()

    while lesson_num < max_lessons:
        lesson_num += 1

        # Reset URLs capturadas para esta leccion
        current_video_urls["mp4"].clear()
        current_video_urls["m3u8"].clear()
        current_video_urls["api"].clear()

        # Esperar un poco para que el video cargue
        await page.wait_for_timeout(3000)

        html = await page.content()
        soup = BeautifulSoup(html, "html.parser")

        # ── Titulo de la leccion ───────────────────────────────────────────────
        title = extract_lesson_title(soup, lesson_num)

        # ── Vidalytics embed ID ────────────────────────────────────────────────
        vid_id = extract_vidalytics_id(soup, html)

        # ── URLs de stream capturadas ─────────────────────────────────────────
        stream_url = None
        if current_video_urls["m3u8"]:
            stream_url = current_video_urls["m3u8"][0]
        elif current_video_urls["mp4"]:
            stream_url = current_video_urls["mp4"][0]

        # Extraer del API response si hay
        if not stream_url and current_video_urls["api"]:
            for api_resp in current_video_urls["api"]:
                found = re.findall(r'https?://[^"\'<>\s]+\.(?:mp4|m3u8)[^"\'<>\s]*', api_resp["body"])
                if found:
                    stream_url = found[0]
                    break

        log(f"\n[{lesson_num}] {title[:50]}")
        if vid_id:
            log(f"  Vidalytics: {vid_id}", "OK")
        if stream_url:
            log(f"  Stream: {stream_url[:80]}", "OK")

        # Evitar duplicados por ID de video
        if vid_id and vid_id in seen_video_ids:
            log(f"  Video ya registrado, avanzando...", "SKIP")
        else:
            if vid_id:
                seen_video_ids.add(vid_id)
            lesson_data = {
                "num": lesson_num,
                "title": title,
                "vidalytics_id": vid_id,
                "stream_url": stream_url,
                "mp4_urls": list(current_video_urls["mp4"]),
                "m3u8_urls": list(current_video_urls["m3u8"]),
                "api_responses": [r["url"] for r in current_video_urls["api"]],
            }
            lessons.append(lesson_data)

        # ── Siguiente leccion ──────────────────────────────────────────────────
        prev_html = html
        clicked = await click_next_lesson(page)
        if not clicked:
            log("Fin del curso (sin boton siguiente).", "OK")
            break
        await page.wait_for_timeout(3000)

        new_html = await page.content()
        if new_html == prev_html:
            log("La pagina no cambio. Fin.", "SKIP")
            break

    await page.close()
    return lessons

def extract_lesson_title(soup: BeautifulSoup, num: int) -> str:
    """Busca el titulo real de la leccion — ignora cabeceras genericas."""
    generic = {"importante", "tip", "ve el video", "consejo", "nota"}

    # Primero buscar en el div de descripcion de video
    desc = soup.find("div", class_=re.compile(r"video.description|lesson.desc|elContent", re.I))
    if desc:
        for tag in desc.find_all(["h1","h2","h3","strong","b"]):
            text = tag.get_text(strip=True)
            if 4 < len(text) < 150 and not any(g in text.lower() for g in generic):
                return text

    # Buscar headings en el body principal
    for tag in soup.find_all(["h1","h2","h3"]):
        text = tag.get_text(strip=True)
        if 4 < len(text) < 150 and not any(g in text.lower() for g in generic):
            return text

    # Usar el primer parrafo significativo
    for p in soup.find_all("p"):
        text = p.get_text(strip=True)
        if 10 < len(text) < 100 and not any(g in text.lower() for g in generic):
            return text[:80]

    return f"Leccion {num}"

def extract_vidalytics_id(soup: BeautifulSoup, html: str) -> str | None:
    # Div con id vidalytics_embed_*
    for div in soup.find_all("div", id=re.compile(r"vidalytics_embed_")):
        m = re.search(r"vidalytics_embed_([A-Za-z0-9_-]+)", div["id"])
        if m:
            return m.group(1)

    # Script con Vidalytics.embed o init
    patterns = [
        r'Vidalytics\.embed\(["\']([A-Za-z0-9_-]+)["\']',
        r'vid(?:id|Id|eo_id)\s*[:=]\s*["\']([A-Za-z0-9_-]{10,})["\']',
        r'vidalytics[^"\']*?[_/]embed_([A-Za-z0-9_-]{10,})',
    ]
    for p in patterns:
        m = re.search(p, html)
        if m:
            return m.group(1)
    return None

async def click_next_lesson(page) -> bool:
    selectors = [
        "a[href='#next-lesson']",
        "#next-lesson",
        "a:has-text('Siguiente')",
        "a:has-text('Next')",
        "button:has-text('Siguiente')",
    ]
    for sel in selectors:
        try:
            btn = await page.query_selector(sel)
            if btn:
                visible = await btn.is_visible()
                if visible:
                    await btn.click()
                    return True
                else:
                    # Intentar scroll y click via JS
                    await page.evaluate(f"document.querySelector('{sel}')?.click()")
                    return True
        except Exception:
            continue
    return False

# ── Descarga ───────────────────────────────────────────────────────────────────

def download_stream(url: str, output_path: Path) -> bool:
    if output_path.exists() and output_path.stat().st_size > 1_000_000:
        log(f"Ya existe ({output_path.stat().st_size//1_000_000}MB): {output_path.name}", "SKIP")
        return True

    log(f"Descargando -> {output_path.name}")

    # Intentar con yt-dlp (maneja m3u8 y mp4)
    cmd = [
        "yt-dlp",
        "--format", "bestvideo+bestaudio/best",
        "--merge-output-format", "mp4",
        "--output", str(output_path),
        "--no-playlist",
        "--progress",
        "--no-warnings",
        url,
    ]
    result = subprocess.run(cmd)
    if result.returncode == 0 and output_path.exists() and output_path.stat().st_size > 100_000:
        log(f"OK ({output_path.stat().st_size//1_000_000}MB)", "OK")
        return True

    # Fallback: ffmpeg para HLS
    if ".m3u8" in url:
        cmd_ffmpeg = [
            "ffmpeg", "-y", "-i", url,
            "-c", "copy", "-bsf:a", "aac_adtstoasc",
            str(output_path)
        ]
        result2 = subprocess.run(cmd_ffmpeg, capture_output=True)
        if result2.returncode == 0 and output_path.exists():
            log(f"OK via ffmpeg ({output_path.stat().st_size//1_000_000}MB)", "OK")
            return True

    log(f"Fallo: {output_path.name}", "ERROR")
    return False

# ── Main ───────────────────────────────────────────────────────────────────────

async def main():
    log("RAIO Course Downloader v3", "HEAD")

    async with async_playwright() as pw:
        browser = await pw.chromium.launch(headless=True)
        ctx = await browser.new_context(
            user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/124 Safari/537.36",
            viewport={"width": 1280, "height": 800},
        )

        log("Login...")
        await do_login(ctx)

        cookies = await ctx.cookies()
        COOKIES_FILE.write_text(json.dumps(cookies, indent=2), encoding="utf-8")

        log("Extrayendo lecciones con intercepcion de red...", "HEAD")
        lessons = await scrape_with_interception(ctx)
        await browser.close()

    # Guardar estructura
    STRUCTURE_FILE.write_text(json.dumps(lessons, ensure_ascii=False, indent=2), encoding="utf-8")
    log(f"\nEstructura guardada: {len(lessons)} lecciones", "OK")

    # Resumen
    log("", "HEAD")
    has_stream = [l for l in lessons if l.get("stream_url")]
    no_stream  = [l for l in lessons if not l.get("stream_url")]
    log(f"Con stream URL: {len(has_stream)}")
    log(f"Sin stream URL: {len(no_stream)}")

    if no_stream:
        log("\nLecciones sin URL de stream (solo Vidalytics ID):")
        for l in no_stream:
            log(f"  [{l['num']:2d}] {l['title'][:60]} | ID: {l.get('vidalytics_id','')}")

    # Descargar
    log("\nDescargando videos...", "HEAD")
    dl = sk = fl = 0
    for lesson in lessons:
        url = lesson.get("stream_url")
        if not url:
            log(f"Sin URL: {lesson['title'][:40]}", "SKIP"); sk += 1; continue
        slug = slugify(lesson["title"])
        out = VIDEOS_DIR / f"{slug}.mp4"
        if download_stream(url, out):
            dl += 1
        else:
            fl += 1

    log("", "HEAD")
    log(f"Total: {len(lessons)} | Descargados: {dl} | Sin URL: {sk} | Fallidos: {fl}")

if __name__ == "__main__":
    asyncio.run(main())
