#!/usr/bin/env python3
"""
SWAPA Podcast Analytics — Data Fetcher
Pulls episode download stats from Buzzsprout and Podbean APIs.
Outputs: data/podcast_stats.json  ← read by swapa_podcast_dashboard.html

Normal usage (fully automated — runs via launchd every Monday at 9am):
  python3 fetch_stats.py

Manual CSV override (fallback if Podbean API is unavailable):
  python3 fetch_stats.py --podbean ~/Downloads/podbean_export.csv
"""

import json
import csv
import io
import argparse
import urllib.request
import urllib.parse
from datetime import datetime, timezone
from pathlib import Path

# ── CONFIG ────────────────────────────────────────────────────────────────────
BUZZSPROUT_TOKEN     = "12da8e465de1fba0dedbf981132472e8"
BUZZSPROUT_PODCAST   = "469237"

PODBEAN_CLIENT_ID    = "baf2841c7e9e83f77b2e4"
PODBEAN_CLIENT_SECRET= "97fc818f4a5c1b30884f7"
PODBEAN_PODCAST_ID   = "EmAEvYtOwC6J"

# Episodes on/after this date: Buzzsprout = app only, Podbean = external only
# Episodes before this date:   Buzzsprout = combined (no split available)
PLATFORM_SWITCH_DATE = "2026-03-23"

OUTPUT_DIR = Path(__file__).parent / "data"

# ── HELPERS ───────────────────────────────────────────────────────────────────
def http_get(url, headers=None):
    req = urllib.request.Request(url)
    req.add_header("User-Agent", "SWAPA-Analytics/1.0")
    for k, v in (headers or {}).items():
        req.add_header(k, v)
    with urllib.request.urlopen(req, timeout=30) as r:
        return r.read()

def http_post(url, data):
    encoded = urllib.parse.urlencode(data).encode()
    req = urllib.request.Request(url, data=encoded)
    req.add_header("User-Agent", "SWAPA-Analytics/1.0")
    req.add_header("Content-Type", "application/x-www-form-urlencoded")
    with urllib.request.urlopen(req, timeout=30) as r:
        return json.loads(r.read())

def parse_date(s):
    """Return YYYY-MM-DD string from ISO 8601 or date-only input."""
    if not s:
        return ""
    s = str(s).strip()
    # Try common ISO formats explicitly — avoid slicing format strings
    for fmt in (
        "%Y-%m-%dT%H:%M:%S.%f+00:00",
        "%Y-%m-%dT%H:%M:%S+00:00",
        "%Y-%m-%dT%H:%M:%SZ",
        "%Y-%m-%dT%H:%M:%S",
        "%Y-%m-%d",
    ):
        try:
            return datetime.strptime(s[:len(fmt)], fmt).strftime("%Y-%m-%d")
        except ValueError:
            continue
    # Last resort: take first 10 chars if they look like a date
    return s[:10] if len(s) >= 10 else s

# ── BUZZSPROUT ────────────────────────────────────────────────────────────────
def fetch_buzzsprout():
    """Fetch all episodes and their total play counts from Buzzsprout API."""
    print("Fetching Buzzsprout episodes...")
    url = f"https://www.buzzsprout.com/api/{BUZZSPROUT_PODCAST}/episodes.json"
    raw = http_get(url, {"Authorization": f"Token token={BUZZSPROUT_TOKEN}"})
    episodes = json.loads(raw)
    print(f"  → {len(episodes)} total episodes")

    result = {}
    for ep in episodes:
        title = ep["title"].strip()
        result[title] = {
            "title":            title,
            "date":             parse_date(ep["published_at"]),
            "duration_sec":     ep["duration"],
            "buzzsprout_id":    ep["id"],
            "buzzsprout_plays": ep["total_plays"],
        }
    return result

# ── PODBEAN API ───────────────────────────────────────────────────────────────
def get_podbean_token():
    """Request a fresh OAuth token. Tokens expire in 7 days; always re-fetch."""
    print("Authenticating with Podbean...")
    resp = http_post("https://api.podbean.com/v1/oauth/token", {
        "grant_type":    "client_credentials",
        "client_id":     PODBEAN_CLIENT_ID,
        "client_secret": PODBEAN_CLIENT_SECRET,
    })
    token = resp.get("access_token")
    if not token:
        raise RuntimeError(f"Podbean auth failed: {resp}")
    print("  → Token obtained")
    return token

def fetch_podbean_episodes(token):
    """List all episodes on the Podbean podcast (paginated)."""
    episodes = []
    offset = 0
    limit = 50
    while True:
        params = urllib.parse.urlencode({
            "access_token": token,
            "podcast_id":   PODBEAN_PODCAST_ID,
            "offset":       offset,
            "limit":        limit,
        })
        raw = http_get(f"https://api.podbean.com/v1/episodes?{params}")
        resp = json.loads(raw)
        batch = resp.get("episodes", [])
        episodes.extend(batch)
        if not resp.get("has_more") or not batch:
            break
        offset += limit
    return episodes

def fetch_podbean_downloads(token):
    """
    Pull per-episode download counts from Podbean.

    Podbean has no bulk "download report" endpoint — /v1/analytics/podcastReach
    (originally assumed) doesn't exist and 404s. The real endpoint is
    /v1/podcastStats/stats, which returns a per-month download breakdown for
    one episode_id at a time. Only post-switch episodes need Podbean data
    (external downloads), so we limit the per-episode calls to those.

    Returns dict keyed by episode title → {podbean_downloads, podbean_date}.
    """
    print("Fetching Podbean episode list...")
    episodes = fetch_podbean_episodes(token)
    print(f"  → {len(episodes)} Podbean episodes found")

    switch_dt = datetime.strptime(PLATFORM_SWITCH_DATE, "%Y-%m-%d").replace(tzinfo=timezone.utc)
    post_switch_eps = [
        ep for ep in episodes
        if datetime.fromtimestamp(ep["publish_time"], tz=timezone.utc) >= switch_dt
    ]
    print(f"  → {len(post_switch_eps)} post-switch episodes; fetching per-episode stats...")

    today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
    result = {}
    for ep in post_switch_eps:
        title    = ep["title"].strip()
        pub_date = datetime.fromtimestamp(ep["publish_time"], tz=timezone.utc).strftime("%Y-%m-%d")
        params = urllib.parse.urlencode({
            "access_token": token,
            "episode_id":   ep["id"],
            "start":        pub_date,
            "end":          today,
            "period":       "m",
        })
        try:
            raw = http_get(f"https://api.podbean.com/v1/podcastStats/stats?{params}")
            monthly = json.loads(raw)
            total = sum(v for v in monthly.values() if isinstance(v, (int, float)))
        except Exception as e:
            print(f"  ⚠ Stats fetch failed for '{title}': {e}")
            total = None
        result[title] = {
            "podbean_downloads": total,
            "podbean_date":      pub_date,
        }

    print(f"  → {len(result)} Podbean episodes with stats parsed")
    return result

def parse_podbean_csv_text(csv_text):
    """
    Parse Podbean CSV content (as string) into a title → downloads dict.
    Handles flexible column naming across different Podbean report formats.
    """
    result = {}
    reader = csv.DictReader(io.StringIO(csv_text))
    cols = reader.fieldnames or []
    print(f"  → Podbean CSV columns: {cols}")

    title_col = next((c for c in cols if "title" in c.lower()), None)
    dl_col    = next((c for c in cols if "download" in c.lower() or "play" in c.lower()), None)
    date_col  = next((c for c in cols if "date" in c.lower() or "publish" in c.lower()), None)

    if not title_col or not dl_col:
        print(f"  ⚠ Cannot identify required columns in Podbean CSV.")
        print(f"     Expected columns with 'title' and 'download' or 'play'. Found: {cols}")
        return result

    for row in reader:
        title = row[title_col].strip()
        try:
            downloads = int(str(row[dl_col]).replace(",", "").strip())
        except (ValueError, TypeError):
            downloads = 0
        result[title] = {
            "podbean_downloads": downloads,
            "podbean_date": parse_date(row.get(date_col, "")) if date_col else "",
        }

    print(f"  → {len(result)} Podbean episodes parsed")
    return result

def load_podbean_csv_file(filepath):
    """Load Podbean data from a local CSV file (manual override / fallback)."""
    print(f"Loading Podbean CSV from file: {filepath}")
    with open(filepath, newline="", encoding="utf-8-sig") as f:
        return parse_podbean_csv_text(f.read())

# ── MERGE ─────────────────────────────────────────────────────────────────────
def merge(buzzsprout_data, podbean_data):
    """
    Combine Buzzsprout and Podbean data per episode.

    Pre-switch (before 2026-03-23):
      - buzzsprout_plays = external + app combined
      - app_dl and external_dl are None (split not available from API)

    Post-switch (2026-03-23 and after):
      - buzzsprout_plays = app-only
      - podbean_downloads = external-only (Apple Podcasts, Spotify, etc.)
      - total_plays = app + external (when both available)
    """
    all_titles = set(buzzsprout_data.keys()) | set(podbean_data.keys())
    merged = []

    for title in all_titles:
        bz = buzzsprout_data.get(title, {})
        pb = podbean_data.get(title, {})

        date           = bz.get("date") or pb.get("podbean_date") or ""
        is_post_switch = date >= PLATFORM_SWITCH_DATE if date else False
        bz_plays       = bz.get("buzzsprout_plays", 0)
        pb_downloads   = pb.get("podbean_downloads") or None

        if is_post_switch:
            app_dl      = bz_plays
            external_dl = pb_downloads
            # Only sum when both values are available
            total = (app_dl + external_dl) if external_dl is not None else None
        else:
            app_dl      = None
            external_dl = None
            total       = bz_plays  # combined, no split

        merged.append({
            "title":              title,
            "date":               date,
            "duration_sec":       bz.get("duration_sec"),
            "buzzsprout_id":      bz.get("buzzsprout_id"),
            "is_post_switch":     is_post_switch,
            "buzzsprout_plays":   bz_plays,   # app-only post-switch, combined pre-switch
            "app_dl":             app_dl,     # post-switch only
            "external_dl":        external_dl,# post-switch only (from Podbean)
            "total_plays":        total,      # combined when available, combined pre-switch
            "podbean_downloads":  pb_downloads,
        })

    merged.sort(key=lambda x: x["date"] or "", reverse=True)
    return merged

# ── OUTPUT ────────────────────────────────────────────────────────────────────
def write_output(episodes):
    OUTPUT_DIR.mkdir(exist_ok=True)
    out_path = OUTPUT_DIR / "podcast_stats.json"

    eps_2026    = [e for e in episodes if (e["date"] or "").startswith("2026")]
    post_switch = [e for e in eps_2026 if e["is_post_switch"]]
    pre_switch  = [e for e in eps_2026 if not e["is_post_switch"]]
    has_podbean = any(e["podbean_downloads"] for e in post_switch)

    payload = {
        "generated_at":         datetime.now(timezone.utc).isoformat(),
        "platform_switch_date": PLATFORM_SWITCH_DATE,
        "podbean_connected":    has_podbean,
        "summary": {
            "total_episodes":       len(episodes),
            "episodes_2026":        len(eps_2026),
            "buzzsprout_plays_2026": sum(e["buzzsprout_plays"] for e in eps_2026),
        },
        "episodes": episodes,
    }

    with open(out_path, "w") as f:
        json.dump(payload, f, indent=2)

    print(f"\n✓ Written to {out_path}")
    print(f"  Total episodes:        {len(episodes)}")
    print(f"  2026 episodes:         {len(eps_2026)}")
    print(f"  Pre-switch:            {len(pre_switch)}")
    print(f"  Post-switch:           {len(post_switch)}")
    print(f"  Podbean data present:  {'yes' if has_podbean else 'no — external_dl will show pending'}")
    print(f"  Buzzsprout 2026 plays: {payload['summary']['buzzsprout_plays_2026']:,}")

# ── MAIN ──────────────────────────────────────────────────────────────────────
def main():
    parser = argparse.ArgumentParser(description="Fetch SWAPA podcast analytics")
    parser.add_argument(
        "--podbean", metavar="FILE",
        help="Path to a local Podbean CSV export (fallback if API unavailable)"
    )
    args = parser.parse_args()

    # Step 1: Always fetch Buzzsprout
    buzzsprout_data = fetch_buzzsprout()

    # Step 2: Fetch Podbean data
    if args.podbean:
        # Manual override: use local CSV file
        podbean_data = load_podbean_csv_file(args.podbean)
    else:
        # Normal path: authenticate and pull via API
        try:
            token        = get_podbean_token()
            podbean_data = fetch_podbean_downloads(token)
        except Exception as e:
            print(f"  ⚠ Podbean API error: {e}")
            print("    External downloads will be null this run.")
            podbean_data = {}

    if not podbean_data:
        print("  (No Podbean data — post-switch external downloads will show as pending)")

    # Step 3: Merge and write
    merged = merge(buzzsprout_data, podbean_data)
    write_output(merged)

if __name__ == "__main__":
    main()
