#!/usr/bin/env python3
"""KAR-564 — AKP Stage 1.5: Fill empty YouTube transcripts via Whisper/Transkriptor router.

Scannt `ingested` Tabelle nach YouTube-Videos mit leeren Transcripts und
ruellt sie nach via:
1. yt-dlp Audio-Extract (low-bitrate MP3, <25 MB cap)
2. `aria-transcribe-route.py` (Default Groq, env-override Transkriptor)
3. UPDATE `ingested.transcript_chars` + `raw_path` (mit Transcript befuellt)
4. DELETE aus `triaged` mit `verdict='skip', reason='empty_transcript'`
   damit naechster Triage-Lauf das Video aufnimmt

Default-Strategy: Groq (Cost-Optimierung, $0.04/h vs Transkriptor-Quota).
Env-Override:
  TRANSCRIBE_FORCE=transkriptor  — nutze Pro-Account
  FILL_MAX=N                     — max Videos pro Run (Default 50)
  FILL_DRY_RUN=1                 — kein API-Call, nur SQL-Preview

Gated auf yt-dlp + ffmpeg verfuegbar.
"""
from __future__ import annotations

import argparse
import json
import os
import re
import subprocess
import sqlite3
import sys
import tempfile
import time
from datetime import datetime, timezone
from pathlib import Path

ROUTER = "/root/aria/scripts/aria-transcribe-route.py"
CONFIG_PATH = Path("/root/aria/brain/youtube/.config.yaml")
RAW_DIR = Path("/root/aria/brain/youtube/raw")


def load_config() -> dict:
    import yaml
    with CONFIG_PATH.open() as f:
        return yaml.safe_load(f)


def db_conn(db_path: str) -> sqlite3.Connection:
    con = sqlite3.connect(db_path)
    con.row_factory = sqlite3.Row
    return con


def get_pending(con: sqlite3.Connection, limit: int) -> list[sqlite3.Row]:
    return list(con.execute(
        """SELECT video_id, channel, channel_handle, title, url, raw_path, duration_seconds
            FROM ingested
            WHERE source = 'youtube'
              AND (transcript_chars IS NULL OR transcript_chars = 0)
            ORDER BY ingested_at DESC
            LIMIT ?""",
        (limit,),
    ))


def slugify(s: str, maxlen: int = 40) -> str:
    s = re.sub(r"[^\w\-]", "-", (s or "").lower())
    s = re.sub(r"-+", "-", s).strip("-")
    return s[:maxlen]


def extract_audio(url: str, cookies: str | None, proxy: str | None, dst_dir: Path) -> Path | None:
    """KAR-570: 2-step extract — yt-dlp lädt Best-Audio, dann ffmpeg manual convert auf Opus 24k mono 16 kHz.
    Garantiert <24 MB für Videos bis ~3h Länge (24 kbps CBR mono).
    """
    # Step 1: yt-dlp pull best-audio in nativem Format
    raw_path = dst_dir / "raw_audio"
    args = [
        "yt-dlp",
        "--no-warnings",
        "--socket-timeout", "60",
        "-f", "ba/best",
        "-o", str(raw_path) + ".%(ext)s",
        "--no-playlist",
        url,
    ]
    if proxy:
        args += ["--proxy", proxy]
    if cookies and Path(cookies).exists():
        args += ["--cookies", cookies]
    try:
        r = subprocess.run(args, capture_output=True, text=True, timeout=300)
    except subprocess.TimeoutExpired:
        print(f"  [audio_timeout] {url}", file=sys.stderr)
        return None
    if r.returncode != 0:
        err = (r.stderr or "").strip().splitlines()[-1:] or [""]
        print(f"  [audio_fail] {url}: {err[0][:160]}", file=sys.stderr)
        return None
    raw_files = [p for p in dst_dir.iterdir() if p.name.startswith("raw_audio.")]
    if not raw_files:
        print(f"  [audio_no_file] {url}", file=sys.stderr)
        return None

    # Step 2: ffmpeg convert -> opus 24kbps mono 16kHz (hard-cap)
    raw = raw_files[0]
    out = dst_dir / "audio.opus"
    ffmpeg_args = [
        "ffmpeg", "-y", "-i", str(raw),
        "-vn",
        "-c:a", "libopus",
        "-b:a", "24k",
        "-ac", "1",
        "-ar", "16000",
        str(out),
    ]
    try:
        rr = subprocess.run(ffmpeg_args, capture_output=True, text=True, timeout=300)
    except subprocess.TimeoutExpired:
        print(f"  [ffmpeg_timeout] {url}", file=sys.stderr)
        return None
    if rr.returncode != 0 or not out.exists():
        print(f"  [ffmpeg_fail] {url}: {rr.stderr.strip()[-160:]}", file=sys.stderr)
        return None

    if out.stat().st_size > 24 * 1024 * 1024:
        print(f"  [audio_too_big] {url}: {out.stat().st_size // 1024 // 1024} MB (even after opus 24k)", file=sys.stderr)
        return None
    return out


def transcribe(audio_path: Path) -> str | None:
    """Call router. Returns transcript text or None on failure."""
    try:
        r = subprocess.run(
            ["python3", ROUTER, str(audio_path)],
            capture_output=True, text=True, timeout=900,
        )
    except subprocess.TimeoutExpired:
        return None
    if r.returncode != 0:
        print(f"  [transcribe_fail] {audio_path.name}: {r.stderr.strip()[-160:]}", file=sys.stderr)
        return None
    return r.stdout.strip() or None


TRANSKRIPTOR = "/root/aria/scripts/transkriptor-transcribe.py"


def transcribe_via_transkriptor_url(yt_url: str) -> str | None:
    """KAR-568 Plan A: Fallback fuer yt-dlp Bot-Detect.
    Schickt YouTube-URL direkt an Transkriptor (kein Audio-Download noetig).
    Verbraucht Transkriptor-Quota.
    """
    try:
        r = subprocess.run(
            ["python3", TRANSKRIPTOR, "url", yt_url, "--wait"],
            capture_output=True, text=True, timeout=1800,
        )
    except subprocess.TimeoutExpired:
        return None
    if r.returncode != 0:
        print(f"  [transkriptor_url_fail] {yt_url}: {r.stderr.strip()[-160:]}", file=sys.stderr)
        return None
    txt = r.stdout.strip()
    return txt if txt else None


def update_ingested(con: sqlite3.Connection, video_id: str, transcript: str, raw_path: Path, source: str = "router-default") -> None:
    # write raw JSON with transcript filled (create dir if needed)
    raw_path.parent.mkdir(parents=True, exist_ok=True)
    if raw_path.exists():
        try:
            data = json.loads(raw_path.read_text())
        except json.JSONDecodeError:
            data = {"video_id": video_id}
    else:
        data = {"video_id": video_id}
    data["transcript"] = transcript
    data["transcript_chars"] = len(transcript)
    data["transcript_filled_at"] = datetime.now(timezone.utc).isoformat()
    data["transcript_filled_by"] = source
    raw_path.write_text(json.dumps(data, indent=2, ensure_ascii=False))
    con.execute(
        "UPDATE ingested SET transcript_chars = ?, raw_path = ? WHERE video_id = ?",
        (len(transcript), str(raw_path), video_id),
    )
    # Triage-Reset: delete the empty_transcript skip
    con.execute(
        "DELETE FROM triaged WHERE video_id = ? AND verdict = 'skip' AND one_line_reason LIKE '%empty_transcript%'",
        (video_id,),
    )
    con.commit()


def log_run(con: sqlite3.Connection, processed: int, skipped: int, failed: int, notes: str) -> None:
    con.execute(
        "INSERT INTO run_log (stage, finished_at, status, items_processed, items_skipped, items_failed, notes) "
        "VALUES ('fill', datetime('now'), 'completed', ?, ?, ?, ?)",
        (processed, skipped, failed, notes),
    )
    con.commit()


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--max", type=int, default=int(os.environ.get("FILL_MAX", "50")))
    ap.add_argument("--dry-run", action="store_true", default=bool(int(os.environ.get("FILL_DRY_RUN", "0"))))
    ap.add_argument("--video-id", help="Run for one specific video_id (debug)")
    ap.add_argument("--transkriptor-fallback-cap", type=int,
                    default=int(os.environ.get("TRANSKRIPTOR_FALLBACK_CAP", "20")),
                    help="Max Transkriptor-URL-Mode-Calls pro Run (Quota-Schutz)")
    args = ap.parse_args()

    cfg = load_config()
    con = db_conn(cfg["paths"]["state_db"])
    cookies = cfg["ingest"].get("yt_dlp_cookies")
    proxy = os.environ.get("YT_PROXY") or cfg["ingest"].get("yt_dlp_proxy") or None

    if args.video_id:
        rows = list(con.execute(
            "SELECT video_id, channel, channel_handle, title, url, raw_path, duration_seconds "
            "FROM ingested WHERE video_id = ?",
            (args.video_id,),
        ))
    else:
        rows = get_pending(con, args.max)
    print(f"[akp-fill] candidates: {len(rows)} (force={os.environ.get('TRANSCRIBE_FORCE','router-default')})")

    if args.dry_run:
        for r in rows[:10]:
            print(f"  DRY  {r['video_id'][:11]}  {(r['channel'] or '')[:20]:20s}  {(r['title'] or '')[:60]}")
        log_run(con, 0, 0, 0, f"dry-run candidates={len(rows)}")
        return 0

    processed = skipped = failed = via_transkriptor = 0
    transkriptor_cap = args.transkriptor_fallback_cap
    for i, row in enumerate(rows, 1):
        vid = row["video_id"]
        url = row["url"]
        with tempfile.TemporaryDirectory(prefix="akp-fill-") as tmp:
            audio = extract_audio(url, cookies, proxy, Path(tmp))
            txt = None
            source = "groq"
            if audio:
                txt = transcribe(audio)
                if not txt:
                    failed += 1
                    continue
            else:
                # KAR-568 Plan A: yt-dlp blocked, fallback to Transkriptor URL-Mode
                if via_transkriptor >= transkriptor_cap:
                    print(f"  [{i}/{len(rows)}] SKIP {vid[:11]} {(row['channel'] or '')[:18]:18s} (yt-dlp blocked + transkriptor cap reached)")
                    skipped += 1
                    continue
                print(f"  [{i}/{len(rows)}] yt-dlp blocked, trying Transkriptor URL-Mode for {vid[:11]}...", flush=True)
                txt = transcribe_via_transkriptor_url(url)
                if not txt:
                    skipped += 1
                    continue
                via_transkriptor += 1
                source = "transkriptor"
            raw_path = Path(row["raw_path"]) if row["raw_path"] else (
                RAW_DIR / slugify(row["channel"] or "akp", 20) / f"{vid}.json"
            )
            update_ingested(con, vid, txt, raw_path, source=source)
            processed += 1
            print(f"  [{i}/{len(rows)}] OK {vid[:11]} {(row['channel'] or '')[:18]:18s} chars={len(txt)} via={source}")
            time.sleep(0.3)

    print(f"[akp-fill] processed={processed} skipped={skipped} failed={failed} via_transkriptor={via_transkriptor}/{transkriptor_cap}")
    log_run(con, processed, skipped, failed, f"force={os.environ.get('TRANSCRIBE_FORCE','router-default')} transkriptor_fallback={via_transkriptor}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
