"""YouTube provider — list Max Amini's recent uploads, fetch transcripts.

Two pieces, deliberately different in auth:
  1. Listing recent videos uses the YouTube Data API v3 `search.list` endpoint
     with the user-supplied `YOUTUBE_API_KEY` (just an API key, no OAuth).
     Plain httpx — we don't pull in the heavy `google-api-python-client` since
     we only need this one GET.
  2. Transcript extraction uses the third-party `youtube-transcript-api`
     library. It hits the public `timedtext` endpoint — no OAuth, no API key
     needed. (The official Data API `captions.download` endpoint requires
     OAuth + ownership of the video, which we do not have for Max Amini's
     channel. This is the standard approach used across the ecosystem.)

The library is synchronous, so we run its calls via `asyncio.to_thread`.
Per-video transcript failures are non-fatal: we log + skip that video so the
stage can still surface the videos that DID have transcripts. The stage only
fails if the initial Data API listing itself fails (network/retryable).
"""

from __future__ import annotations

import asyncio
import logging
from typing import Any

import httpx
from youtube_transcript_api import YouTubeTranscriptApi

from app.config import settings
from app.providers._common import (
    JsonExtractionError,
    NonRetryableProviderError,
    RetryableProviderError,
    _build_semaphore,
)

logger = logging.getLogger(__name__)

_DATA_API_BASE = "https://www.googleapis.com/youtube/v3/search"


class YouTubeClient:
    def __init__(self) -> None:
        self._http = httpx.AsyncClient(timeout=settings.YOUTUBE_TIMEOUT)
        self._sem = _build_semaphore(settings.YOUTUBE_MAX_CONCURRENCY)
        # The transcript API is sync + stateful internally; one shared instance
        # is fine because we always run its calls inside a thread.
        self._tta = YouTubeTranscriptApi()

    async def close(self) -> None:
        await self._http.aclose()

    async def list_recent_videos(self, max_results: int | None = None) -> list[dict[str, Any]]:
        """List the channel's most recent uploads via Data API v3 `search.list`."""
        params = {
            "part": "snippet",
            "channelId": settings.YOUTUBE_CHANNEL_ID,
            "order": "date",
            "type": "video",
            "maxResults": min(max_results or settings.YOUTUBE_MAX_VIDEOS, 50),
            "key": settings.YOUTUBE_API_KEY,
        }
        async with self._sem:
            try:
                response = await self._http.get(_DATA_API_BASE, params=params)
            except (httpx.TimeoutException, httpx.NetworkError) as exc:
                raise RetryableProviderError("youtube", 0, f"network: {exc}")

        status = response.status_code
        if status == 429 or status >= 500:
            raise RetryableProviderError("youtube", status, response.text)
        if status >= 400:
            raise NonRetryableProviderError("youtube", status, response.text)
        try:
            data = response.json()
        except ValueError as exc:
            raise JsonExtractionError(f"youtube: non-JSON response (HTTP {status})") from exc

        videos: list[dict[str, Any]] = []
        for item in data.get("items", []):
            video_id = (item.get("id") or {}).get("videoId")
            if not video_id:
                continue
            snip = item.get("snippet") or {}
            videos.append({
                "video_id": video_id,
                "title": snip.get("title") or "",
                "description": (snip.get("description") or "")[:500],
                "published_at": snip.get("publishedAt"),
                "thumbnail": (snip.get("thumbnails") or {}).get("medium", {}).get("url"),
                "url": f"https://www.youtube.com/watch?v={video_id}",
            })
        logger.info("youtube: listed %d recent videos", len(videos))
        return videos

    async def fetch_transcript(self, video_id: str) -> str:
        """Fetch the transcript text. Returns '' if unavailable (transcripts
        are user-supplied or auto-generated and not all videos have them).

        The `youtube-transcript-api` library is synchronous; wrap in
        `asyncio.to_thread`. All errors are caught and treated as
        "no transcript for this video" so one un-transcripted clip never
        fails the whole stage.
        """
        try:
            if hasattr(self._tta, "fetch"):
                # youtube-transcript-api >= 1.x returns snippet objects from
                # `fetch`; older releases expose get_transcript instead.
                fetched = await asyncio.to_thread(
                    self._tta.fetch, video_id, languages=["en"]
                )
                chunks = list(fetched)
            else:
                chunks = await asyncio.to_thread(
                    self._tta.get_transcript, video_id, languages=["en"]
                )
        except Exception as exc:
            # Library raises a variety of custom exceptions whose names differ
            # across versions; treat any failure as "no transcript available".
            logger.debug("youtube: no transcript for %s (%s)", video_id, type(exc).__name__)
            return ""
        if not chunks:
            return ""
        texts: list[str] = []
        for chunk in chunks:
            if isinstance(chunk, dict):
                text = chunk.get("text", "")
            else:
                text = getattr(chunk, "text", "")
            if text:
                texts.append(str(text))
        return " ".join(texts)

    async def list_recent_with_transcripts(self) -> list[dict[str, Any]]:
        """Convenience: list + transcribe each. Skips videos with no transcript.

        Returns list of dicts: {video_id, title, published_at, url, transcript, thumbnail}.
        """
        videos = await self.list_recent_videos()
        out: list[dict[str, Any]] = []
        for v in videos:
            transcript = await self.fetch_transcript(v["video_id"])
            if not transcript:
                continue
            v["transcript"] = transcript
            out.append(v)
        logger.info("youtube: %d/%d videos had transcripts", len(out), len(videos))
        return out
