#!/usr/bin/env python3
"""
Aria Knowledge Pipeline (AKP) — Instagram Single-URL-Fetch (KAR-84)

Manual-Link-Drop Pattern: Aria bekommt URLs via Telegram oder discover/-Folder,
pullt via yt-dlp (braucht Instagram-Cookies in /root/aria/instagram-cookies.txt).
Schreibt JSON nach brain/akp/raw/instagram/<post-id>.json.

Usage:
  python3 aria-akp-instagram-fetch.py <url>
  python3 aria-akp-instagram-fetch.py --discover  # batch alle .url-Files aus discover/

Cheap-Filter (Value-über-Reichweite, Kais Spec 12.05.2026):
- caption-length min 200 chars
- skip Werbungs-Pattern: 'Link in bio', 'DM me', 'Swipe up', 'Tap to shop'
- skip Hashtag-Stacking >10
"""
from __future__ import annotations
import sys as _sys
_sys.path.insert(0, "/root/aria/lib")
from aria_logging import get_logger as _get_logger
_log = _get_logger("aria-akp-instagram-fetch")

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

CONFIG_PATH = Path("/root/aria/brain/youtube/.config.yaml")
INSTAGRAM_RAW_DIR = Path("/root/aria/brain/akp/raw/instagram")
INSTAGRAM_COOKIES = Path("/root/aria/instagram-cookies.txt")
DISCOVER_DIR = Path("/root/aria/brain/akp/discover")

AD_PATTERNS = [
    r"\blink in bio\b",
    r"\bDM me\b",
    r"\bswipe up\b",
    r"\btap to shop\b",
    r"\bfollow me\b",
    r"\bclick the link\b",
]


def load_config() -> dict:
    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 already_ingested(con, pid: str) -> bool:
    cur = con.execute("SELECT 1 FROM ingested WHERE video_id=?", (pid,))
    return cur.fetchone() is not None


def url_to_id(url: str) -> str | None:
    """Extract Instagram post ID from URL."""
    m = re.search(r"instagram\.com/(?:reel|p|reels)/([\w-]+)", url)
    return m.group(1) if m else None


def fetch_via_yt_dlp(url: str, cookies_path: Path | None, proxy: str | None) -> dict | None:
    """Pull caption + metadata via yt-dlp. Returns dict or None on failure."""
    cmd = ["yt-dlp", "--skip-download", "--dump-json", "--no-warnings",
           "--socket-timeout", "60"]
    if cookies_path and cookies_path.exists():
        cmd += ["--cookies", str(cookies_path)]
    if proxy:
        cmd += ["--proxy", proxy]
    cmd.append(url)
    try:
        r = subprocess.run(cmd, capture_output=True, text=True, timeout=90)
    except subprocess.TimeoutExpired:
        return None
    if not r.stdout.strip():
        return None
    try:
        return json.loads(r.stdout)
    except json.JSONDecodeError:
        return None


def cheap_filter(meta: dict, caption: str) -> str | None:
    """Werbung-Filter + Caption-Length."""
    if len(caption.strip()) < 200:
        return "caption_too_short"
    text = caption.lower()
    for pat in AD_PATTERNS:
        if re.search(pat, text):
            return f"ad_pattern_{pat}"
    hashtag_count = len(re.findall(r"#\w+", caption))
    if hashtag_count > 10:
        return f"hashtag_stacking_{hashtag_count}"
    return None


def write_raw(pid: str, meta: dict, caption: str, url: str) -> str:
    INSTAGRAM_RAW_DIR.mkdir(parents=True, exist_ok=True)
    out_path = INSTAGRAM_RAW_DIR / f"{pid}.json"
    upload_date = meta.get("upload_date", "")
    payload = {
        "video_id": f"ig_{pid}",
        "channel_slug": "instagram-manual",
        "channel_handle": meta.get("uploader", "instagram"),
        "title": (caption.splitlines()[0] if caption else "Instagram Post")[:120],
        "url": url,
        "upload_date": upload_date,
        "transcript": caption,
        "transcript_chars": len(caption),
        "ingested_at": datetime.now(timezone.utc).isoformat(),
        "source": "instagram",
        "yt_meta": {
            "uploader": meta.get("uploader"),
            "view_count": meta.get("view_count"),
            "like_count": meta.get("like_count"),
            "comment_count": meta.get("comment_count"),
            "duration": meta.get("duration"),
        },
    }
    out_path.write_text(json.dumps(payload, indent=2, ensure_ascii=False))
    return str(out_path)


def store_ingested(con, pid, title, url, raw_path, content_chars):
    con.execute(
        "INSERT OR REPLACE INTO ingested (video_id, channel, channel_handle, title, "
        "duration_seconds, published_at, url, transcript_chars, raw_path, source) "
        "VALUES (?,?,?,?,?,?,?,?,?,'instagram')",
        (f"ig_{pid}", "instagram-manual", "instagram", title[:300],
         None, "", url, content_chars, raw_path),
    )
    con.commit()


def store_skipped(con, pid, title, url, reason):
    con.execute(
        "INSERT OR IGNORE INTO ingested (video_id, channel, title, url, skipped_reason, source) VALUES (?,?,?,?,?,'instagram')",
        (f"ig_{pid}", "instagram-manual", title[:200], url, reason),
    )
    con.commit()


def process_url(url: str, con, cookies_path: Path | None, proxy: str | None) -> str:
    """Process one URL. Returns status code: 'ok' | 'skipped' | 'failed' | 'dup'."""
    pid = url_to_id(url)
    if not pid:
        print(f"  [{url}] invalid URL pattern", file=sys.stderr)
        return "failed"
    if already_ingested(con, f"ig_{pid}"):
        print(f"  [{pid}] already ingested")
        return "dup"
    meta = fetch_via_yt_dlp(url, cookies_path, proxy)
    if not meta:
        print(f"  [{pid}] fetch failed (cookies missing/expired?)", file=sys.stderr)
        return "failed"
    caption = meta.get("description") or meta.get("title") or ""
    title_short = caption.splitlines()[0][:120] if caption else "ig_post"
    reason = cheap_filter(meta, caption)
    if reason:
        store_skipped(con, pid, title_short, url, reason)
        print(f"  [{pid}] skipped: {reason}")
        return "skipped"
    raw_path = write_raw(pid, meta, caption, url)
    store_ingested(con, pid, title_short, url, raw_path, len(caption))
    print(f"  [{pid}] ingested: {title_short[:50]}")
    return "ok"


def main(argv):
    cfg = load_config()
    con = db_conn(cfg["paths"]["state_db"])
    proxy = os.environ.get("YT_PROXY") or None
    cookies = INSTAGRAM_COOKIES if INSTAGRAM_COOKIES.exists() else None

    if not cookies:
        print(f"[akp-instagram] WARNING: {INSTAGRAM_COOKIES} not found. "
              f"yt-dlp will likely fail with 'login required' error.", file=sys.stderr)

    urls: list[str] = []
    if "--discover" in argv:
        for f in DISCOVER_DIR.glob("instagram-*.url"):
            text = f.read_text(errors="replace")
            for m in re.finditer(r"https?://[^\s'\"<>]+instagram\.com/(?:reel|p|reels)/[\w-]+", text):
                urls.append(m.group(0))
            try:
                f.unlink()  # consume drop-file
            except OSError:
                pass
    else:
        for arg in argv:
            if arg.startswith("http"):
                urls.append(arg)

    if not urls:
        print("[akp-instagram] no URLs. Usage: <url> | --discover")
        return 1

    print(f"[akp-instagram] processing {len(urls)} URL(s)")
    counts = {"ok": 0, "skipped": 0, "failed": 0, "dup": 0}
    for url in urls:
        status = process_url(url, con, cookies, proxy)
        counts[status] += 1
        time.sleep(0.5)

    print(f"[akp-instagram] {counts}")
    con.close()
    return 0


if __name__ == "__main__":
    _log.event("script_start")
    sys.exit(main(sys.argv[1:]))
