#!/usr/bin/env python3
"""For each lesson, search YouTube for a likely public mirror.

Uses yt-dlp's `ytsearch` extractor (no API key needed). For each lesson:
- builds a search query from (title + speaker)
- runs yt-dlp in --dump-json --no-download mode
- takes the first hit, filters by simple match heuristics (speaker in
  uploader OR title overlap >= 50%)
- writes results to /root/aria/state/maven/yt-matches.jsonl

Usage:
    python3 maven-yt-search.py [--limit N] [--min-score N] [--top-aria N]
"""
from __future__ import annotations

import argparse
import json
import subprocess
import sys
import time
from pathlib import Path

CLASSIFIED = Path("/root/aria/state/maven/lessons-classified.jsonl")
OUT_PATH = Path("/root/aria/state/maven/yt-matches.jsonl")


def search_youtube(query: str, timeout: int = 30) -> dict | None:
    # FIX 2026-05-21: --default-search ytsearch1 versuchte volle Video-Extraction,
    # YouTube blockt das mit Bot-Check. Stattdessen --flat-playlist nur Metadata.
    cmd = [
        "yt-dlp",
        f"ytsearch3:{query}",
        "--flat-playlist",
        "--dump-json",
        "--no-warnings",
        "--quiet",
    ]
    try:
        r = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
        if r.returncode != 0 or not r.stdout.strip():
            return None
        # Return first hit
        lines = r.stdout.strip().splitlines()
        if not lines:
            return None
        return json.loads(lines[0])
    except (subprocess.TimeoutExpired, json.JSONDecodeError, Exception):
        return None


def title_overlap(a: str, b: str) -> float:
    aw = set(w.lower() for w in a.split() if len(w) > 3)
    bw = set(w.lower() for w in b.split() if len(w) > 3)
    if not aw:
        return 0.0
    return len(aw & bw) / len(aw)


def fuzzy_speaker_match(speaker: str, uploader: str, title: str) -> bool:
    """Lockerere Speaker-Erkennung: erstname+nachname tokenized."""
    if not speaker:
        return False
    parts = [p.lower().strip(",;.") for p in speaker.split() if len(p) > 2]
    if not parts:
        return False
    haystack = (uploader + " " + title).lower()
    # Min 1 token (z.B. Nachname allein reicht)
    return any(p in haystack for p in parts)


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--limit", type=int, default=None)
    ap.add_argument("--min-score", type=int, default=8, help="Minimum aria_score to include")
    ap.add_argument("--sleep", type=float, default=2.0)
    args = ap.parse_args()

    rows = [json.loads(line) for line in CLASSIFIED.read_text().splitlines() if line.strip()]
    candidates = [r for r in rows if r.get("aria_score", 0) >= args.min_score]
    candidates.sort(key=lambda r: r.get("aria_score", 0), reverse=True)
    if args.limit:
        candidates = candidates[: args.limit]
    print(f"# candidates: {len(candidates)}", file=sys.stderr)

    already = set()
    if OUT_PATH.exists():
        for line in OUT_PATH.read_text().splitlines():
            try:
                already.add(json.loads(line)["maven_url"])
            except Exception:
                pass

    found = 0
    skipped = 0
    miss = 0
    for i, c in enumerate(candidates):
        if c["url"] in already:
            skipped += 1
            continue
        title = c.get("title") or ""
        speaker = c.get("instructor_name") or ""
        if not title:
            continue
        query = f"{title} {speaker}".strip()
        hit = search_youtube(query)
        result = {"maven_url": c["url"], "maven_title": title, "maven_speaker": speaker, "query": query}
        if hit:
            yt_title = hit.get("title", "")
            yt_uploader = hit.get("uploader", "") or hit.get("channel", "")
            overlap = title_overlap(title, yt_title)
            speaker_match = speaker and (
                speaker.lower() in yt_uploader.lower() or speaker.lower() in yt_title.lower()
            )
            fuzzy_match = fuzzy_speaker_match(speaker, yt_uploader, yt_title)
            # Lockerer: 30% Title-Overlap ODER Speaker-Match ODER Fuzzy-Token-Match
            confident = overlap >= 0.30 or speaker_match or fuzzy_match
            result.update({
                "yt_url": hit.get("webpage_url") or hit.get("original_url"),
                "yt_title": yt_title,
                "yt_uploader": yt_uploader,
                "yt_duration_sec": hit.get("duration"),
                "title_overlap": round(overlap, 2),
                "speaker_in_uploader_or_title": bool(speaker_match),
                "confident": confident,
            })
            if confident:
                found += 1
            else:
                miss += 1
        else:
            result["yt_url"] = None
            miss += 1
        with OUT_PATH.open("a") as f:
            f.write(json.dumps(result, ensure_ascii=False) + "\n")
        if (i + 1) % 10 == 0:
            print(f"# {i+1}/{len(candidates)} · confident={found} miss={miss} skipped={skipped}", file=sys.stderr)
        time.sleep(args.sleep)

    print(f"DONE · confident={found} miss={miss} skipped={skipped}", file=sys.stderr)


if __name__ == "__main__":
    main()
