# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
iTunes Search + RSS podcast client.

iTunes Search API: free, no auth, 20 calls/min limit.
RSS parsing uses stdlib xml.etree.ElementTree.
"""

from __future__ import annotations

import logging
import xml.etree.ElementTree as ET

import httpx

from familiar.core.api_cache import cached
from familiar.skills._api_utils import APIError, RateLimiter, api_get

logger = logging.getLogger(__name__)

ITUNES_SEARCH_URL = "https://itunes.apple.com/search"

# 18 calls/min (2 call buffer from 20/min limit)
_itunes_limiter = RateLimiter(calls=18, period=60)

_ITUNES_NS = {"itunes": "http://www.itunes.com/dtds/podcast-1.0.dtd"}


def _format_podcast(result: dict) -> dict:
    """Normalize an iTunes Search result to a podcast dict."""
    return {
        "name": result.get("collectionName") or result.get("trackName", ""),
        "artist": result.get("artistName", ""),
        "feed_url": result.get("feedUrl", ""),
        "artwork": result.get("artworkUrl100", ""),
        "artwork_large": result.get("artworkUrl600") or result.get("artworkUrl100", ""),
        "genres": result.get("genres", []),
        "primary_genre": result.get("primaryGenreName", ""),
    }


def _parse_duration(raw: str) -> str:
    """Parse duration from seconds-only or HH:MM:SS format."""
    if not raw:
        return ""
    raw = raw.strip()
    # Already in HH:MM:SS or MM:SS format
    if ":" in raw:
        return raw
    # Seconds-only
    try:
        total = int(raw)
        hours, remainder = divmod(total, 3600)
        minutes, seconds = divmod(remainder, 60)
        if hours:
            return f"{hours}:{minutes:02d}:{seconds:02d}"
        return f"{minutes}:{seconds:02d}"
    except (ValueError, TypeError):
        return raw


@cached(prefix="podcast_search", ttl=300)
def search_podcasts(term: str, limit: int = 25) -> list[dict]:
    """Search iTunes for podcasts."""
    if not _itunes_limiter.acquire():
        logger.warning("iTunes rate limit reached")
        return []

    try:
        data = api_get(
            ITUNES_SEARCH_URL,
            params={
                "term": term,
                "media": "podcast",
                "limit": min(limit, 50),
            },
            service_name="iTunes Search",
        )
        results = data.get("results", [])
        return [_format_podcast(r) for r in results if r.get("feedUrl")]
    except APIError as e:
        logger.warning("Podcast search failed: %s", e)
        return []


def get_episodes(feed_url: str, limit: int = 20) -> list[dict]:
    """Fetch and parse episodes from an RSS feed URL."""
    if not feed_url:
        return []

    try:
        resp = httpx.get(feed_url, timeout=15, follow_redirects=True)
        if resp.status_code != 200:
            logger.warning("RSS fetch failed: %s %s", resp.status_code, feed_url)
            return []

        root = ET.fromstring(resp.text)
    except (httpx.HTTPError, ET.ParseError) as e:
        logger.warning("RSS parse error for %s: %s", feed_url, e)
        return []

    channel = root.find("channel")
    if channel is None:
        return []

    episodes = []
    for item in channel.findall("item")[:limit]:
        # Get audio URL from enclosure
        audio_url = ""
        enclosure = item.find("enclosure")
        if enclosure is not None:
            enc_type = enclosure.get("type", "")
            if "audio" in enc_type or enc_type == "":
                audio_url = enclosure.get("url", "")

        if not audio_url:
            continue

        title = item.findtext("title", "").strip()
        description = item.findtext("description", "").strip()
        if len(description) > 300:
            description = description[:297] + "..."

        # Duration from itunes:duration
        duration_el = item.find("itunes:duration", _ITUNES_NS)
        duration = _parse_duration(duration_el.text if duration_el is not None else "")

        published = item.findtext("pubDate", "").strip()

        # Image from itunes:image
        image = ""
        image_el = item.find("itunes:image", _ITUNES_NS)
        if image_el is not None:
            image = image_el.get("href", "")

        episodes.append({
            "title": title,
            "description": description,
            "audio_url": audio_url,
            "duration": duration,
            "published": published,
            "image": image,
        })

    return episodes
