#!/usr/bin/env python3
"""Generate a podcast RSS feed for the Cross-Country Study Library."""
import os, subprocess, html, datetime, urllib.parse, re

HOST = "https://bobs-mac-studio-2-1.tail24a318.ts.net"
SLUG = "cross-country-pilot-prep"            # this show's folder/path
BASE_URL = f"{HOST}/{SLUG}"                   # feed/media/cover all live under here
ROOT = os.path.expanduser(f"~/Podcasts/{SLUG}")
MEDIA = os.path.join(ROOT, "media")
OUT = os.path.join(ROOT, "feed.xml")

AUTHOR = "Bob Francis"
OWNER_EMAIL = "bob.francis@steeldynamics.com"
PODCAST_TITLE = "Cross-Country Study Library — Private Pilot Prep"
PODCAST_DESC = ("A ten-volume, FAA-grounded audio course for student pilots preparing for the "
                "Private Pilot checkride, with an emphasis on cross-country flight planning and "
                "real-world decision making. Generated from a custom study library.")

# Ordered episodes: (media filename, title, description)
EPISODES = [
 ("Planning_your_first_VFR_cross_country.m4a",
  "Ep 1 — Planning Your First VFR Cross-Country",
  "The full planning workflow from scratch: route selection, checkpoints, the nav log, winds and groundspeed, fuel and reserves, alternates, flight following, diversions, and lost procedures."),
 ("Aviation_Weather_Physics_and_Safety_Decisions.m4a",
  "Ep 2 — Aviation Weather: Physics and Safety Decisions",
  "Why the atmosphere behaves the way it does, plus METARs, TAFs, PIREPs, AIRMETs, SIGMETs and winds aloft — and how a student turns all of it into a sound go/no-go decision."),
 ("Navigating_Airspace_with_VFR_Sectional_Charts.m4a",
  "Ep 3 — Navigating Airspace with VFR Sectional Charts",
  "Reading a sectional, terrain and obstacles, the airspace classes and what each requires, VFR weather minimums, TFRs and special use airspace, and communication requirements."),
 ("You_Cannot_Outfly_Bad_Physics.m4a",
  "Ep 4 — You Cannot Outfly Bad Physics",
  "Aircraft performance, weight and balance, and fuel planning: density altitude, reading performance charts, arms and moments, and why book numbers are ideal-condition numbers."),
 ("Breaking_the_aviation_accident_chain.m4a",
  "Ep 5 — Breaking the Aviation Accident Chain",
  "Aeronautical decision making and risk management: PAVE, IMSAFE, the 3P model, personal minimums, the five hazardous attitudes, and how small risks stack into an accident chain."),
 ("Smarter_pilot_decisions_in_six_flight_scenarios.m4a",
  "Ep 6 — Smarter Pilot Decisions in Six Flight Scenarios",
  "Six narrated cross-country flights — ideal weather, strong crosswind, a weather diversion, a fuel squeeze, getting temporarily lost, and an airspace challenge — debriefed at every decision point."),
 ("Private_Pilot_Oral_Exam_Simulation.m4a",
  "Ep 7 — Private Pilot Oral Exam: Weather, Airspace & Navigation",
  "A mock oral exam in examiner-and-applicant format covering weather, airspace, and navigation, with reasoning, common wrong answers, and likely follow-up questions."),
 ("What_Your_Pilot_Examiner_Really_Wants.m4a",
  "Ep 8 — What Your Pilot Examiner Really Wants",
  "The oral continues on regulations, performance, risk management, and cross-country planning — drilling the high-frequency questions and the maturity markers of a strong applicant."),
 ("Aviation_Math_for_Cross_Country_Pilots.m4a",
  "Ep 9 — Aviation Math for Cross-Country Pilots",
  "Worked calculations done out loud: wind correction angle, groundspeed, ETA, fuel burn, weight and balance, density altitude, and diversion estimates — with the common setup errors flagged."),
 ("Why_smart_pilots_make_mental_mistakes.m4a",
  "Ep 10 — Why Smart Pilots Make Mental Mistakes",
  "An honest look at the mistakes student pilots commonly make in planning, weather, fuel, navigation, airspace, and decision making — and the concrete habits that fix each one."),
 ("Authoritative_Sources_for_Your_Pilot_Checkride.m4a",
  "Ep 11 — Authoritative Sources for Your Pilot Checkride",
  "How to navigate the FAA source documents — PHAK, AFH, Aviation Weather Handbook, Risk Management Handbook, AIM, the ACS, and 14 CFR Parts 61 and 91 — and verify specifics against the current edition."),
]

def duration_seconds(path):
    try:
        out = subprocess.run(["afinfo", path], capture_output=True, text=True, timeout=30).stdout
        m = re.search(r"estimated duration:\s*([0-9.]+)\s*sec", out)
        if m: return int(float(m.group(1)))
    except Exception:
        pass
    return 0

def hms(sec):
    h = sec // 3600; m = (sec % 3600) // 60; s = sec % 60
    return f"{h:d}:{m:02d}:{s:02d}" if h else f"{m:d}:{s:02d}"

def rfc2822(dt):
    return dt.strftime("%a, %d %b %Y %H:%M:%S +0000")

items = []
# Default podcast view is newest-first, so date Episode 1 as the MOST recent
# (and Episode 11 oldest). Combined with itunes:episode numbers + serial type,
# this makes the list read 1 -> 11 in order across both episodic and serial apps.
base = datetime.datetime(2026, 6, 14, 12, 0, 0)  # Episode 11 = newest (today)
N = len(EPISODES)
for i, (fn, title, desc) in enumerate(EPISODES):
    path = os.path.join(MEDIA, fn)
    size = os.path.getsize(path)
    dur = duration_seconds(path)
    pub = base - datetime.timedelta(days=(N - 1 - i))  # Ep1 oldest -> Ep11 newest, one day apart
    url = BASE_URL + "/media/" + urllib.parse.quote(fn)
    guid = url
    items.append(f"""    <item>
      <title>{html.escape(title)}</title>
      <description>{html.escape(desc)}</description>
      <itunes:summary>{html.escape(desc)}</itunes:summary>
      <itunes:episode>{i+1}</itunes:episode>
      <itunes:episodeType>full</itunes:episodeType>
      <enclosure url="{html.escape(url)}" length="{size}" type="audio/x-m4a"/>
      <guid isPermaLink="false">{html.escape(guid)}</guid>
      <pubDate>{rfc2822(pub)}</pubDate>
      <itunes:duration>{hms(dur)}</itunes:duration>
      <itunes:author>{html.escape(AUTHOR)}</itunes:author>
      <itunes:explicit>false</itunes:explicit>
    </item>""")

now = rfc2822(datetime.datetime.utcnow())
feed = f"""<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>{html.escape(PODCAST_TITLE)}</title>
    <link>{BASE_URL}/</link>
    <language>en-us</language>
    <description>{html.escape(PODCAST_DESC)}</description>
    <itunes:author>{html.escape(AUTHOR)}</itunes:author>
    <itunes:summary>{html.escape(PODCAST_DESC)}</itunes:summary>
    <itunes:type>serial</itunes:type>
    <itunes:explicit>false</itunes:explicit>
    <itunes:image href="{BASE_URL}/cover.jpg"/>
    <image><url>{BASE_URL}/cover.jpg</url><title>{html.escape(PODCAST_TITLE)}</title><link>{BASE_URL}/</link></image>
    <itunes:owner><itunes:name>{html.escape(AUTHOR)}</itunes:name><itunes:email>{html.escape(OWNER_EMAIL)}</itunes:email></itunes:owner>
    <itunes:category text="Education"/>
    <lastBuildDate>{now}</lastBuildDate>
{chr(10).join(items)}
  </channel>
</rss>
"""
with open(OUT, "w") as f:
    f.write(feed)
print("WROTE", OUT, len(feed), "bytes,", len(EPISODES), "episodes")
