from __future__ import annotations

"""Tests for the granule[videocard] extra ensuring video_insight_card works
in both fallback (no API key) and LLM (API key, mocked client) modes without
network access (YouTube transcript + OpenAI call are mocked).
"""

from typing import Any
import types
import json

import pytest

from granule.api_simple import video_insight_card
from granule import config as granule_config
from granule.models.video_insight import VideoInsightCardPayload


class DummyStructuredDoc:
    def __init__(self, text: str, title: str):
        from granule.models.core import SourceMeta
        self.text = text
        self.source = SourceMeta(type="youtube", url="https://youtu.be/FAKEID12345", title=title)


@pytest.fixture(autouse=True)
def no_network_youtube(monkeypatch):
    """Patch ingest_youtube to avoid real network calls and supply a stable transcript."""
    from granule.ingest import youtube as yt_mod

    def fake_ingest(url_or_id: str, title: str | None = None):
        # Provide short transcript with multiple lines to seed TL;DR section
        transcript = """[00:00] Intro to neutrinos\n[00:05] They rarely interact\n[00:10] Experiments detect oscillations\n[00:15] Importance for cosmology"""
        return DummyStructuredDoc(transcript, title or "Demo Video")

    monkeypatch.setattr(yt_mod, "ingest_youtube", fake_ingest)
    yield


def _make_mock_llm_card(url: str, vid: str, title: str) -> dict[str, Any]:
    return {
        "header": {"title": title, "subtitle": None, "badges": []},
        "video": {"url": url, "id": vid},
        "sections": [
            {"title": "TL;DR & Key Takeaways", "items": ["A", "B"]},
            {"title": "Claims & Evidence", "items": []},
        ],
        "footer": {"persuasion_modes": [], "devices": [], "timeline": []},
    }


def test_videocard_fallback_no_api_key(monkeypatch):
    """Without an API key we should get a minimal fallback card (still valid)."""
    # Ensure API key disabled
    monkeypatch.setattr(granule_config.settings, "openai_api_key", None, raising=False)
    card = video_insight_card("https://www.youtube.com/watch?v=FAKEID12345")
    assert isinstance(card, VideoInsightCardPayload)
    # Minimal path should not invent complex sections beyond TL;DR
    titles = [s.title for s in card.sections]
    assert "TL;DR & Key Takeaways" in titles
    # Claims & Evidence usually absent in fallback; ensure not present to confirm path
    assert all(t != "Claims & Evidence" for t in titles)


def test_videocard_llm_path_mocked(monkeypatch):
    """With an API key and mocked OpenAI client we should exercise LLM path producing Claims & Evidence section."""
    monkeypatch.setattr(granule_config.settings, "openai_api_key", "sk-DUMMY", raising=False)

    # Patch _get_openai_client to return a stub with responses.parse behavior expected by code.
    import granule.llm.video_card as vc

    class ParsedObj:
        def __init__(self, data):
            self.output_parsed = data

    class ResponsesStub:
        def parse(self, model: str, input: list[dict], text_format: Any):  # noqa: D401
            # Extract defaults from user content to fill url/id/title reliably
            user_msg = input[1]["content"]  # system prompt is first
            # Parse DEFAULT tokens naïvely
            import re
            url = (m.group(1) if (m := re.search(r"<DEFAULT_URL>(.*?)</DEFAULT_URL>", user_msg)) else "")
            vid = (m.group(1) if (m := re.search(r"<DEFAULT_ID>(.*?)</DEFAULT_ID>", user_msg)) else "")
            title = (m.group(1) if (m := re.search(r"<DEFAULT_TITLE>(.*?)</DEFAULT_TITLE>", user_msg)) else "Mock Title")
            data = _make_mock_llm_card(url, vid, title)
            return ParsedObj(data)

    class ClientStub:
        def __init__(self):
            self.responses = ResponsesStub()

    monkeypatch.setattr(vc, "_get_openai_client", lambda api_key: ClientStub())

    card = video_insight_card("https://www.youtube.com/watch?v=FAKEID12345")
    assert isinstance(card, VideoInsightCardPayload)
    titles = [s.title for s in card.sections]
    assert "Claims & Evidence" in titles, "Expected LLM path to yield Claims & Evidence section"
    # Ensure schema integrity (round-trip validate)
    revalidated = VideoInsightCardPayload.model_validate_json(card.model_dump_json())
    assert revalidated.header.title == card.header.title
