#!/usr/bin/env python3
"""News fetcher for the News Reader app.

Uses the Perplexity SDK (pplx_sdk) to:
  - list the top headlines currently shown on a source's homepage (cached page extraction)
  - resolve a headline to its article URL (search.web, domain-restricted)
  - extract the clean article body for reader mode / TTS (cached page extraction)

Auth: loads SDK env vars from .pplx_env next to this file so it works inside the
deployed Node server regardless of how that process was launched.

CLI:
  python3 news_fetch.py headlines <source>
  python3 news_fetch.py article   <source> <headline>
  python3 news_fetch.py all
"""
import json
import os
import sys
import re
import pathlib

HERE = pathlib.Path(__file__).resolve().parent

# --- load SDK env from file (if present) ---
env_file = HERE / ".pplx_env"
if env_file.exists():
    for line in env_file.read_text().splitlines():
        line = line.strip()
        if not line or line.startswith("#") or "=" not in line:
            continue
        k, _, v = line.partition("=")
        os.environ.setdefault(k, v)

import pplx_sdk  # noqa: E402

SOURCES = {
    "reuters": {
        "label": "Reuters",
        "home": "https://www.reuters.com/world/",
        "domains": ["reuters.com"],
        "alt_homes": ["https://www.reuters.com/"],
    },
    "cnn": {
        "label": "CNN",
        "home": "https://www.cnn.com/",
        "domains": ["cnn.com", "edition.cnn.com"],
        "alt_homes": ["https://www.cnn.com/world"],
    },
    "pbs": {
        "label": "PBS NewsHour",
        "home": "https://www.pbs.org/newshour/",
        "domains": ["pbs.org"],
        "alt_homes": [],
    },
}

HEADLINE_PROMPT = (
    "List the top news headlines currently shown on this page. "
    "Return ONLY a JSON array (no prose, no markdown code fences) of objects, each with: "
    '"headline" (string), "summary" (string, 1-2 sentence excerpt). '
    "Include up to 8 items. Omit navigation, section labels, ads, newsletters, and non-story links."
)

ARTICLE_PROMPT = (
    "Extract the news article from this page. Return ONLY a JSON object (no prose, no code fences) with: "
    '"headline" (string), "author" (string or ""), "date" (string or ""), '
    '"body" (string, the full article text as paragraphs separated by \\n). '
    "Omit ads, related links, newsletters, and navigation. If this is not an article, return {\"body\": \"\"}."
)


def _clean_json(text):
    if not text:
        return None
    t = text.strip()
    # strip markdown fences
    if t.startswith("```"):
        t = t.split("```", 2)
        t = t[1] if len(t) > 1 else t[0]
        if t.lstrip().startswith("json"):
            t = t.lstrip()[4:]
    t = t.strip()
    # sometimes a leading list wrapper / stray text; find first { or [
    for i, ch in enumerate(t):
        if ch in "[{":
            t = t[i:]
            break
    return t


def _is_article_url(url, domains):
    if not url:
        return False
    dom = next((d for d in domains if d in url), None)
    if not dom:
        return False
    after = url.split(dom, 1)[1] if dom in url else ""
    # bare landing/section pages -> skip
    if after in ("", "/", "/world/", "/us/", "/business/", "/politics/", "/health/", "/world"):
        return False
    # sitemaps / live hub pages -> skip
    if "sitemap" in after or after.rstrip("/").endswith("/live") or "/live/" in after:
        return False
    return True


def fetch_headlines(source_key):
    cfg = SOURCES[source_key]
    homes = [cfg["home"]] + cfg.get("alt_homes", [])
    items = []
    last_err = None
    for home in homes:
        try:
            pr = pplx_sdk.content.fetch([home], prompt=HEADLINE_PROMPT)
            p = pr[0]
            if p.get("error"):
                last_err = p["error"]
                continue
            c = _clean_json(p.get("content", "") or "")
            if not c:
                continue
            try:
                arr = json.loads(c)
            except json.JSONDecodeError:
                # try to salvage a JSON array via regex
                m = re.search(r"\[.*\]", c, re.S)
                arr = json.loads(m.group(0)) if m else []
            for a in arr:
                hl = (a.get("headline") or "").strip()
                sm = (a.get("summary") or "").strip()
                if hl and len(hl) > 8:
                    items.append({"headline": hl, "summary": sm, "source": cfg["label"]})
            if items:
                break
        except Exception as e:
            last_err = f"{type(e).__name__}: {e}"
            continue
    # de-dup by headline, cap at 8
    seen, out = set(), []
    for it in items:
        key = it["headline"].lower()
        if key in seen:
            continue
        seen.add(key)
        out.append(it)
    if not out:
        return {"source": cfg["label"], "error": last_err or "no headlines found", "items": []}
    return {"source": cfg["label"], "updated": _now(), "items": out[:8]}


def resolve_article_url(source_key, headline):
    cfg = SOURCES[source_key]
    try:
        hits = pplx_sdk.search.web(
            headline, domains=cfg["domains"],
            reformulations=[headline + " news", "latest " + headline],
        )
    except Exception:
        hits = []
    for h in hits:
        d = h.to_dict() if hasattr(h, "to_dict") else dict(h)
        u = d.get("url", "")
        if _is_article_url(u, cfg["domains"]):
            return u
    # fallback: first hit with a date-ish path
    for h in hits:
        d = h.to_dict() if hasattr(h, "to_dict") else dict(h)
        u = d.get("url", "")
        if re.search(r"/20\d{2}/", u) and any(dom in u for dom in cfg["domains"]):
            return u
    return None


def fetch_article(source_key, headline):
    cfg = SOURCES[source_key]
    url = resolve_article_url(source_key, headline)
    body, author, date, title = "", "", "", headline
    err = None
    if url:
        try:
            pr = pplx_sdk.content.fetch([url], prompt=ARTICLE_PROMPT)
            p = pr[0]
            if p.get("error"):
                err = p["error"]
            c = _clean_json(p.get("content", "") or "")
            if c:
                try:
                    obj = json.loads(c)
                    body = (obj.get("body") or "").strip()
                    title = (obj.get("headline") or headline).strip()
                    author = (obj.get("author") or "").strip()
                    date = (obj.get("date") or "").strip()
                except json.JSONDecodeError:
                    body = c
        except Exception as e:
            err = f"{type(e).__name__}: {e}"
    # fall back to summary-length content if body too short
    if len(body) < 120:
        body = body or ""
    return {
        "source": cfg["label"],
        "headline": title or headline,
        "url": url or "",
        "author": author,
        "date": date,
        "body": body,
        "error": err if not body else None,
    }


def _now():
    from datetime import datetime, timezone
    return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")


def main():
    if len(sys.argv) < 3:
        print(json.dumps({"error": "usage: news_fetch.py headlines|article|all <source> [headline]"}))
        sys.exit(1)
    cmd = sys.argv[1]
    if cmd == "headlines":
        key = sys.argv[2]
        if key == "all":
            out = {}
            for k in SOURCES:
                try:
                    out[k] = fetch_headlines(k)
                except Exception as e:
                    out[k] = {"source": SOURCES[k]["label"], "error": f"{type(e).__name__}: {e}", "items": []}
            print(json.dumps(out))
        elif key in SOURCES:
            print(json.dumps(fetch_headlines(key)))
        else:
            print(json.dumps({"error": f"unknown source {key}"}))
            sys.exit(1)
    elif cmd == "article":
        key = sys.argv[2]
        headline = " ".join(sys.argv[3:]).strip()
        if key not in SOURCES or not headline:
            print(json.dumps({"error": "usage: news_fetch.py article <source> <headline>"}))
            sys.exit(1)
        print(json.dumps(fetch_article(key, headline)))
    elif cmd == "all":
        out = {}
        for k in SOURCES:
            try:
                out[k] = fetch_headlines(k)
            except Exception as e:
                out[k] = {"source": SOURCES[k]["label"], "error": f"{type(e).__name__}: {e}", "items": []}
        print(json.dumps(out))
    else:
        print(json.dumps({"error": f"unknown command {cmd}"}))
        sys.exit(1)


if __name__ == "__main__":
    main()
