"""Tests for the describer module and analyze_presentation tool."""

from __future__ import annotations

import json
import tempfile
from pathlib import Path
from unittest.mock import patch

import pytest

from pptx_mcp.brand.defaults import executive_default
from pptx_mcp.engine.describer import describe_presentation, describe_slide
from pptx_mcp.engine.generator import DeckGenerator
from pptx_mcp.models.slides import (
    AgendaItem,
    CardDefinition,
    ChartElement,
    ColumnContent,
    FunnelStage,
    IconItem,
    KpiCard,
    PresentationDefinition,
    PresentationMetadata,
    ProcessStep,
    SlideDefinition,
    SlideType,
    SwotData,
    TableCell,
    TableElement,
    TeamMember,
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _make_definition(slides: list[SlideDefinition]) -> PresentationDefinition:
    return PresentationDefinition(
        metadata=PresentationMetadata(title="Describer Test"),
        slides=slides,
    )


def _generate_pptx(defn: PresentationDefinition) -> str:
    gen = DeckGenerator(brand=executive_default())
    tmp = tempfile.NamedTemporaryFile(suffix=".pptx", delete=False)
    gen.generate(defn, tmp.name)
    return tmp.name


# ---------------------------------------------------------------------------
# describe_slide
# ---------------------------------------------------------------------------


class TestDescribeSlide:
    def test_title_slide(self) -> None:
        slide = SlideDefinition(slide_type=SlideType.TITLE, title="Welcome")
        desc = describe_slide(slide, 0)
        assert "Slide 1" in desc
        assert "Title slide" in desc
        assert "Welcome" in desc

    def test_content_slide_with_bullets(self) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.CONTENT,
            title="Overview",
            bullets=["Point A", "Point B", "Point C"],
        )
        desc = describe_slide(slide, 2)
        assert "Slide 3" in desc
        assert "3 bullet points" in desc

    def test_dashboard_slide(self) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.DASHBOARD,
            title="KPI Dashboard",
            kpis=[
                KpiCard(value="95%", label="Uptime"),
                KpiCard(value="4.5", label="NPS"),
            ],
        )
        desc = describe_slide(slide, 0)
        assert "KPI dashboard" in desc
        assert "2 KPI cards" in desc
        assert "Uptime" in desc

    def test_card_grid_slide(self) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.CARD_GRID,
            title="Our Services",
            cards=[
                CardDefinition(title="Strategy"),
                CardDefinition(title="Implementation"),
            ],
        )
        desc = describe_slide(slide, 0)
        assert "Card grid" in desc
        assert "2 cards" in desc
        assert "Strategy" in desc

    def test_process_slide(self) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.PROCESS,
            title="Our Approach",
            steps=[
                ProcessStep(title="Discover"),
                ProcessStep(title="Design"),
                ProcessStep(title="Deliver", status="active"),
            ],
        )
        desc = describe_slide(slide, 0)
        assert "Process flow" in desc
        assert "3 steps" in desc

    def test_table_slide(self) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.TABLE,
            title="Comparison",
            elements=[
                TableElement(rows=[
                    [TableCell(text="Header A"), TableCell(text="Header B")],
                    [TableCell(text="Row 1A"), TableCell(text="Row 1B")],
                    [TableCell(text="Row 2A"), TableCell(text="Row 2B")],
                ]),
            ],
        )
        desc = describe_slide(slide, 0)
        assert "Table slide" in desc
        assert "3x2" in desc

    def test_team_slide(self) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.TEAM,
            title="Leadership",
            team_members=[
                TeamMember(name="Alice", role="CEO"),
                TeamMember(name="Bob", role="CTO"),
            ],
        )
        desc = describe_slide(slide, 0)
        assert "Team slide" in desc
        assert "Alice" in desc

    def test_swot_slide(self) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.SWOT,
            title="SWOT Analysis",
            swot=SwotData(
                strengths=["Strong brand"],
                weaknesses=["Small team"],
                opportunities=["New market"],
                threats=["Competition"],
            ),
        )
        desc = describe_slide(slide, 0)
        assert "SWOT" in desc
        assert "4 points" in desc

    def test_funnel_slide(self) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.FUNNEL,
            title="Sales Funnel",
            funnel_stages=[
                FunnelStage(label="Awareness"),
                FunnelStage(label="Interest"),
                FunnelStage(label="Decision"),
            ],
        )
        desc = describe_slide(slide, 0)
        assert "Funnel" in desc
        assert "3 stages" in desc

    def test_agenda_slide(self) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.AGENDA,
            title="Today's Agenda",
            agenda_items=[
                AgendaItem(title="Intro"),
                AgendaItem(title="Demo"),
            ],
        )
        desc = describe_slide(slide, 0)
        assert "Agenda" in desc
        assert "2 items" in desc

    def test_untitled_slide(self) -> None:
        slide = SlideDefinition(slide_type=SlideType.BLANK)
        desc = describe_slide(slide, 0)
        assert "(untitled)" in desc

    def test_quote_slide(self) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.QUOTE,
            title="Inspiration",
            quote_attribution="Einstein",
        )
        desc = describe_slide(slide, 0)
        assert "Quote" in desc
        assert "Einstein" in desc

    def test_icon_grid_slide(self) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.ICON_GRID,
            title="Our Values",
            icons=[
                IconItem(icon="\u2605", title="Quality"),
                IconItem(icon="\u2713", title="Speed"),
                IconItem(icon="\u25CF", title="Trust"),
            ],
        )
        desc = describe_slide(slide, 0)
        assert "Icon grid" in desc
        assert "3 icons" in desc


# ---------------------------------------------------------------------------
# describe_presentation
# ---------------------------------------------------------------------------


class TestDescribePresentation:
    def _standard_slides(self) -> list[SlideDefinition]:
        return [
            SlideDefinition(slide_type=SlideType.TITLE, title="Welcome"),
            SlideDefinition(slide_type=SlideType.CONTENT, title="Overview", bullets=["Point A"]),
            SlideDefinition(slide_type=SlideType.DASHBOARD, title="KPIs", kpis=[
                KpiCard(value="100", label="Users"),
            ]),
            SlideDefinition(slide_type=SlideType.CLOSING, title="Thank You"),
        ]

    def test_returns_all_keys(self) -> None:
        result = describe_presentation(self._standard_slides())
        assert "slide_descriptions" in result
        assert "content_inventory" in result
        assert "type_distribution" in result
        assert "narrative_assessment" in result
        assert "recommendations" in result

    def test_slide_descriptions_count(self) -> None:
        slides = self._standard_slides()
        result = describe_presentation(slides)
        assert len(result["slide_descriptions"]) == len(slides)

    def test_content_inventory_structure(self) -> None:
        result = describe_presentation(self._standard_slides())
        inv = result["content_inventory"]
        assert "total_words" in inv
        assert "words_per_slide" in inv
        assert "visual_slide_count" in inv
        assert "text_slide_count" in inv
        assert inv["total_words"] >= 0
        assert len(inv["words_per_slide"]) == 4

    def test_type_distribution_accurate(self) -> None:
        result = describe_presentation(self._standard_slides())
        dist = result["type_distribution"]
        assert dist["title"] == 1
        assert dist["content"] == 1
        assert dist["dashboard"] == 1
        assert dist["closing"] == 1

    def test_narrative_opens_with_title(self) -> None:
        result = describe_presentation(self._standard_slides())
        assert result["narrative_assessment"]["opens_with_title"] is True

    def test_narrative_closes_properly(self) -> None:
        result = describe_presentation(self._standard_slides())
        assert result["narrative_assessment"]["closes_properly"] is True

    def test_no_title_opening_flagged(self) -> None:
        slides = [
            SlideDefinition(slide_type=SlideType.CONTENT, title="Jump In"),
        ]
        result = describe_presentation(slides)
        assert result["narrative_assessment"]["opens_with_title"] is False
        assert any("title slide" in r.lower() for r in result["recommendations"])

    def test_variety_score_range(self) -> None:
        result = describe_presentation(self._standard_slides())
        score = result["narrative_assessment"]["variety_score"]
        assert 0.0 <= score <= 1.0

    def test_empty_presentation(self) -> None:
        result = describe_presentation([])
        assert result["slide_descriptions"] == []
        assert result["content_inventory"]["total_words"] == 0

    def test_section_breaks_detected(self) -> None:
        slides = [
            SlideDefinition(slide_type=SlideType.TITLE, title="Deck"),
            SlideDefinition(slide_type=SlideType.SECTION, title="Part 1"),
            SlideDefinition(slide_type=SlideType.CONTENT, title="Details"),
        ]
        result = describe_presentation(slides)
        assert result["narrative_assessment"]["has_section_breaks"] is True


# ---------------------------------------------------------------------------
# analyze_presentation tool (server)
# ---------------------------------------------------------------------------


class TestAnalyzePresentationTool:
    def _patch_and_import_server(self):
        """Import server with patched FastMCP for kwarg compatibility."""
        from mcp.server.fastmcp import FastMCP
        import inspect

        _original_init = FastMCP.__init__

        def _flexible_init(self, *args, **kwargs):
            sig = inspect.signature(_original_init)
            valid_params = set(sig.parameters.keys())
            filtered = {k: v for k, v in kwargs.items() if k in valid_params}
            _original_init(self, *args, **filtered)

        with patch.object(FastMCP, "__init__", _flexible_init):
            import importlib
            import pptx_mcp.server
            importlib.reload(pptx_mcp.server)
            return pptx_mcp.server

    def test_analyze_returns_valid_json(self) -> None:
        defn = _make_definition([
            SlideDefinition(slide_type=SlideType.TITLE, title="Test Deck"),
            SlideDefinition(
                slide_type=SlideType.CONTENT,
                title="Overview",
                bullets=["Bullet A", "Bullet B"],
            ),
        ])
        path = _generate_pptx(defn)
        server_mod = self._patch_and_import_server()

        result = server_mod.analyze_presentation(file_path=path)
        data = json.loads(result)
        assert "title" in data
        assert "slide_count" in data
        assert data["slide_count"] == 2
        assert "overall_score" in data
        assert "slide_descriptions" in data
        assert "content_inventory" in data
        assert "type_distribution" in data
        assert "narrative_assessment" in data
        assert "recommendations" in data

    def test_analyze_with_brand_preset(self) -> None:
        defn = _make_definition([
            SlideDefinition(slide_type=SlideType.TITLE, title="Brand Test"),
        ])
        path = _generate_pptx(defn)
        server_mod = self._patch_and_import_server()

        result = server_mod.analyze_presentation(
            file_path=path, brand_preset="executive",
        )
        data = json.loads(result)
        assert data["slide_count"] == 1
