#!/usr/bin/env python3
"""
PKA Podcast RSS Generator
Downloads YouTube videos as audio and maintains a podcast RSS feed.

Usage:
  python3 add_episode.py <youtube_url> [title]

When you forward a Patreon email with a YouTube link, just pass the URL.
The script will:
  1. Download the audio from the YouTube video via yt-dlp
  2. Extract metadata (title, duration, upload date, thumbnail)
  3. Add it to the RSS feed XML
"""

import sys
import os
import re
import json
import subprocess
import hashlib
import html
from datetime import datetime, timezone
from pathlib import Path
from urllib.parse import quote
import xml.etree.ElementTree as ET
from email.utils import formatdate

# --- Configuration ---
BASE_DIR = Path("/mnt/storage/nzbdav/pka-podcast")
EPISODES_DIR = BASE_DIR / "episodes"
FEED_FILE = BASE_DIR / "pka_feed.xml"
STATE_FILE = BASE_DIR / "state.json"

# The server URL base - this is what your podcast app will use
# Using Tailscale IP for access from your devices
SERVER_HOST = "100.125.84.21"
SERVER_PORT = 8484
BASE_URL = f"http://{SERVER_HOST}:{SERVER_PORT}"

PODCAST_TITLE = "Painkiller Already (Patreon)"
PODCAST_DESC = "PKA podcast episodes from Patreon YouTube links."
PODCAST_AUTHOR = "Painkiller Already"
PODCAST_IMAGE = ""  # Could set a local thumbnail


def ensure_dirs():
    EPISODES_DIR.mkdir(parents=True, exist_ok=True)
    BASE_DIR.mkdir(parents=True, exist_ok=True)


def extract_video_id(url):
    """Extract YouTube video ID from various URL formats."""
    patterns = [
        r'youtube\.com/watch\?v=([A-Za-z0-9_-]{11})',
        r'youtu\.be/([A-Za-z0-9_-]{11})',
        r'youtube\.com/embed/([A-Za-z0-9_-]{11})',
        r'youtube\.com/shorts/([A-Za-z0-9_-]{11})',
    ]
    for p in patterns:
        m = re.search(p, url)
        if m:
            return m.group(1)
    return None


def get_video_info(url):
    """Use yt-dlp to get video metadata without downloading.
    Tries web client first (with JS challenge solving), falls back to android_vr."""
    env = {**os.environ, "PATH": os.environ.get("PATH", "") + ":/home/pkmx/.deno/bin"}
    # web client gives better metadata but needs deno + remote components
    cmd = [
        "yt-dlp",
        "--extractor-args", "youtube:player_client=web",
        "--remote-components", "ejs:github",
        "--dump-json",
        "--no-playlist",
        "--no-warnings",
        url
    ]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=120, env=env)
    if result.returncode != 0:
        print(f"Web client failed, trying android_vr...", file=sys.stderr)
        cmd = [
            "yt-dlp",
            "--extractor-args", "youtube:player_client=android_vr",
            "--dump-json",
            "--no-playlist",
            "--no-warnings",
            url
        ]
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=120, env=env)
        if result.returncode != 0:
            print(f"ERROR getting video info: {result.stderr}", file=sys.stderr)
            return None
    return json.loads(result.stdout)


def download_audio(url, video_id, title):
    """Download audio from YouTube video as MP3."""
    safe_title = re.sub(r'[^\w\s-]', '', title or video_id).strip()[:80]
    safe_title = re.sub(r'\s+', '_', safe_title)
    filename = f"{safe_title}.mp4"
    filepath = EPISODES_DIR / filename

    if filepath.exists():
        print(f"Episode already downloaded: {filepath}")
        return filepath, filename

    # Download as video (mp4). Try clients in order: mweb, web, android_vr
    # mweb gets format 18 for unlisted videos and doesn't 403 from this IP
    env = {**os.environ, "PATH": os.environ.get("PATH", "") + ":/home/pkmx/.deno/bin"}
    clients = [
        ("mweb", False),   # best for unlisted, no JS challenge needed
        ("web", True),     # needs deno + remote components
        ("android_vr", False),
    ]
    result = None
    for client, need_remote in clients:
        cmd = [
            "yt-dlp",
            "--extractor-args", f"youtube:player_client={client}",
        ]
        if need_remote:
            cmd += ["--remote-components", "ejs:github"]
        cmd += [
            "-f", "18/best[ext=mp4]/best",
            "--embed-thumbnail",
            "--add-metadata",
            "-o", str(filepath),
            "--no-playlist",
            "--no-warnings",
            url
        ]
        print(f"Downloading via {client}: {title}")
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=600, env=env)
        if result.returncode == 0:
            break
        print(f"{client} failed: {result.stderr[:200]}", file=sys.stderr)
    
    if result is None or result.returncode != 0:
        print(f"ERROR: all clients failed", file=sys.stderr)
        return None, None
    print(f"Downloaded to: {filepath}")
    return filepath, filename


def get_file_duration(filepath):
    """Get audio duration in seconds using ffprobe."""
    cmd = [
        "ffprobe",
        "-v", "quiet",
        "-show_entries", "format=duration",
        "-of", "default=noprint_wrappers=1:nokey=1",
        str(filepath)
    ]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
    if result.returncode == 0:
        try:
            return int(float(result.stdout.strip()))
        except ValueError:
            pass
    return 0


def get_file_size(filepath):
    return os.path.getsize(filepath)


def load_state():
    if STATE_FILE.exists():
        with open(STATE_FILE) as f:
            return json.load(f)
    return {"episodes": []}


def save_state(state):
    with open(STATE_FILE, 'w') as f:
        json.dump(state, f, indent=2)


def is_duplicate(state, video_id):
    for ep in state["episodes"]:
        if ep.get("video_id") == video_id:
            return True
    return False


def generate_rss(state):
    """Generate podcast RSS XML from state."""
    rss = ET.Element("rss", version="2.0")
    rss.set("xmlns:itunes", "http://www.itunes.com/dtds/podcast-1.0.dtd")
    rss.set("xmlns:atom", "http://www.w3.org/2005/Atom")
    rss.set("xmlns:media", "http://search.yahoo.com/mrss/")

    channel = ET.SubElement(rss, "channel")
    ET.SubElement(channel, "title").text = PODCAST_TITLE
    ET.SubElement(channel, "link").text = "https://www.patreon.com/PKA"
    ET.SubElement(channel, "description").text = PODCAST_DESC
    ET.SubElement(channel, "language").text = "en"
    ET.SubElement(channel, "itunes:author").text = PODCAST_AUTHOR
    ET.SubElement(channel, "itunes:explicit").text = "yes"
    ET.SubElement(channel, "itunes:category", text="Comedy")

    # Build episodes sorted newest first
    episodes = sorted(state["episodes"], key=lambda e: e.get("pub_date", ""), reverse=True)
    for ep in episodes:
        item = ET.SubElement(channel, "item")
        ET.SubElement(item, "title").text = ep["title"]
        ET.SubElement(item, "description").text = ep.get("description", "")
        ET.SubElement(item, "pubDate").text = ep["pub_date_rfc"]
        ET.SubElement(item, "guid", isPermaLink="false").text = f"pka-{ep['video_id']}"

        enclosure_url = f"{BASE_URL}/episodes/{quote(ep['filename'])}"
        ET.SubElement(item, "enclosure",
                       url=enclosure_url,
                       length=str(ep["size"]),
                       type="video/mp4")
        ET.SubElement(item, "itunes:duration").text = str(ep["duration"])
        ET.SubElement(item, "media:content",
                       url=enclosure_url,
                       fileSize=str(ep["size"]),
                       type="video/mp4",
                       duration=str(ep["duration"]))

    # Pretty print
    ET.indent(rss, space="  ")
    xml_str = '<?xml version="1.0" encoding="UTF-8"?>\n' + ET.tostring(rss, encoding="unicode")
    with open(FEED_FILE, 'w') as f:
        f.write(xml_str)
    print(f"RSS feed updated: {FEED_FILE}")
    print(f"Feed URL: {BASE_URL}/pka_feed.xml")
    return FEED_FILE


def add_episode(youtube_url, custom_title=None):
    """Main: download a YouTube video and add it to the RSS feed."""
    ensure_dirs()
    state = load_state()

    video_id = extract_video_id(youtube_url)
    if not video_id:
        print(f"ERROR: Could not extract YouTube video ID from: {youtube_url}", file=sys.stderr)
        return False

    if is_duplicate(state, video_id):
        print(f"Episode already in feed (video_id={video_id}), skipping.")
        return True

    # Get metadata
    info = get_video_info(youtube_url)
    if not info:
        print("ERROR: Could not get video metadata", file=sys.stderr)
        return False

    title = custom_title or info.get("title", video_id)
    upload_date = info.get("upload_date", datetime.now().strftime("%Y%m%d"))
    # Parse upload_date (YYYYMMDD) to datetime
    try:
        dt = datetime.strptime(upload_date, "%Y%m%d").replace(tzinfo=timezone.utc)
    except ValueError:
        dt = datetime.now(timezone.utc)

    # Download audio
    filepath, filename = download_audio(youtube_url, video_id, title)
    if not filepath:
        return False

    # Get file info
    duration = get_file_duration(filepath)
    size = get_file_size(filepath)
    description = info.get("description", "")[:500]

    # Add to state
    episode = {
        "video_id": video_id,
        "title": title,
        "filename": filename,
        "duration": duration,
        "size": size,
        "description": description,
        "pub_date": dt.isoformat(),
        "pub_date_rfc": formatdate(dt.timestamp(), usegmt=True),
        "added_at": datetime.now(timezone.utc).isoformat(),
    }
    state["episodes"].append(episode)
    save_state(state)

    # Regenerate RSS
    generate_rss(state)

    print(f"\nDone! Episode added:")
    print(f"  Title: {title}")
    print(f"  Duration: {duration}s ({duration//60}:{duration%60:02d})")
    print(f"  Size: {size // 1024 // 1024} MB")
    print(f"\nFeed URL: {BASE_URL}/pka_feed.xml")
    return True


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python3 add_episode.py <youtube_url> [title]")
        print("Example: python3 add_episode.py https://www.youtube.com/watch?v=BMyaTIkKtXM 'PKN #626'")
        sys.exit(1)
    url = sys.argv[1]
    title = sys.argv[2] if len(sys.argv) > 2 else None
    success = add_episode(url, title)
    sys.exit(0 if success else 1)
