"""Tests for planner workflow fixes: explicit slides, expanded update_plan_slide, no placeholders.

Covers:
- Explicit slide type specs via SlideSpec / slides param
- Expanded update_plan_slide (slide_type change, replace_slide)
- No placeholder titles ("Detail Slide N", "[Section Break]", "Additional Detail N")
- Strict slide count enforcement
- Backward compatibility with existing paths
"""

from __future__ import annotations

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

import pytest

from pptx_mcp.agents.planner import PlannerInput, SlideSpec, SlidePlannerAgent
from pptx_mcp.models.slides import (
    PresentationDefinition,
    PresentationMetadata,
    SlideDefinition,
    SlideType,
)


# ---------------------------------------------------------------------------
# Import server with FastMCP compat patch (same pattern as test_server_tools)
# ---------------------------------------------------------------------------

def _patch_and_import_server():
    from mcp.server.fastmcp import FastMCP

    _original_init = FastMCP.__init__

    def _flexible_init(self, *args, **kwargs):
        import inspect
        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):
        mod_name = "pptx_mcp.server"
        cached = sys.modules.pop(mod_name, None)
        try:
            import pptx_mcp.server as server_mod
            return server_mod
        finally:
            pass


server = _patch_and_import_server()
plan_presentation = server.plan_presentation
update_plan_slide = server.update_plan_slide
_plan_cache = server._plan_cache


# ---------------------------------------------------------------------------
# Placeholder patterns to reject
# ---------------------------------------------------------------------------

_PLACEHOLDER_PATTERNS = [
    "Detail Slide",
    "Additional Detail",
    "[Section Break]",
]


def _has_placeholder(title: str) -> bool:
    return any(pat in title for pat in _PLACEHOLDER_PATTERNS)


# =========================================================================
# TestExplicitSlides — Path 0
# =========================================================================


class TestExplicitSlides:
    """Tests for explicit slide type specs via SlideSpec."""

    def test_types_respected(self):
        """Explicit slide types should be used verbatim."""
        specs = [
            SlideSpec(slide_type=SlideType.TITLE),
            SlideSpec(slide_type=SlideType.CHART),
            SlideSpec(slide_type=SlideType.DASHBOARD),
            SlideSpec(slide_type=SlideType.CLOSING),
        ]
        inp = PlannerInput(topic="Revenue", num_slides=4, slides=specs)
        result = SlidePlannerAgent().run(inp)
        types = [s.slide_type for s in result.slides]
        assert types == [SlideType.TITLE, SlideType.CHART, SlideType.DASHBOARD, SlideType.CLOSING]

    def test_title_preserved(self):
        """Caller-specified titles should override auto-generated ones."""
        specs = [
            SlideSpec(slide_type=SlideType.TITLE, title="My Custom Title"),
            SlideSpec(slide_type=SlideType.CHART, title="Revenue Breakdown"),
            SlideSpec(slide_type=SlideType.CLOSING, title="Wrap Up"),
        ]
        inp = PlannerInput(topic="Q4 Results", num_slides=3, slides=specs)
        result = SlidePlannerAgent().run(inp)
        assert result.slides[0].title == "My Custom Title"
        assert result.slides[1].title == "Revenue Breakdown"

    def test_auto_title_generated(self):
        """When no title specified, a meaningful one should be generated."""
        specs = [
            SlideSpec(slide_type=SlideType.DASHBOARD),
        ]
        inp = PlannerInput(topic="Sales Metrics", num_slides=3, slides=specs)
        result = SlidePlannerAgent().run(inp)
        assert result.slides[0].title  # not empty
        assert not _has_placeholder(result.slides[0].title)

    def test_overrides_deck_type(self):
        """When slides is provided, deck_type should be ignored."""
        specs = [
            SlideSpec(slide_type=SlideType.TITLE),
            SlideSpec(slide_type=SlideType.FUNNEL),
            SlideSpec(slide_type=SlideType.CLOSING),
        ]
        inp = PlannerInput(
            topic="Test", num_slides=3, deck_type="pitch_deck", slides=specs,
        )
        result = SlidePlannerAgent().run(inp)
        types = [s.slide_type for s in result.slides]
        assert SlideType.FUNNEL in types

    def test_content_hydrated(self):
        """Slides should have real content, not empty fields."""
        specs = [
            SlideSpec(slide_type=SlideType.DASHBOARD),
            SlideSpec(slide_type=SlideType.CHART),
            SlideSpec(slide_type=SlideType.CLOSING),
        ]
        inp = PlannerInput(
            topic="Revenue", num_slides=3, slides=specs,
            key_points=["Q1 growth 15%", "New markets"],
        )
        result = SlidePlannerAgent().run(inp)
        dashboard = result.slides[0]
        assert dashboard.kpis  # should have KPI cards
        chart = result.slides[1]
        assert chart.elements  # should have chart element

    def test_all_32_types_accepted(self):
        """Every SlideType enum value should be accepted in explicit specs."""
        for stype in SlideType:
            specs = [SlideSpec(slide_type=stype)]
            inp = PlannerInput(topic="Test", num_slides=3, slides=specs)
            result = SlidePlannerAgent().run(inp)
            assert result.slides[0].slide_type == stype

    def test_server_tool_slides_param(self):
        """The plan_presentation tool should accept slides dicts."""
        result_json = plan_presentation(
            topic="Quarterly Review",
            slides=[
                {"slide_type": "title", "title": "Q4 Review"},
                {"slide_type": "chart", "title": "Revenue"},
                {"slide_type": "dashboard"},
                {"slide_type": "closing"},
            ],
        )
        result = json.loads(result_json)
        assert result["slide_count"] == 4
        types = [s["slide_type"] for s in result["slides"]]
        assert types == ["title", "chart", "dashboard", "closing"]
        # Check explicit title preserved
        assert result["slides"][0]["title"] == "Q4 Review"
        assert result["slides"][1]["title"] == "Revenue"


# =========================================================================
# TestUpdatePlanSlideExpanded
# =========================================================================


class TestUpdatePlanSlideExpanded:
    """Tests for expanded update_plan_slide (type change + replace_slide)."""

    def _make_plan(self) -> str:
        """Create a plan and return plan_id."""
        result = json.loads(plan_presentation(
            topic="Update Test", num_slides=5,
        ))
        return result["plan_id"]

    def test_type_change(self):
        """Changing slide_type should re-hydrate the slide."""
        plan_id = self._make_plan()
        result = json.loads(update_plan_slide(
            plan_id=plan_id, slide_index=1, slide_type="dashboard",
        ))
        assert result["status"] == "success"
        assert result["slide_type"] == "dashboard"
        assert result["mode"] == "type_change"

    def test_type_change_with_title(self):
        """Changing slide_type + title should preserve the title."""
        plan_id = self._make_plan()
        result = json.loads(update_plan_slide(
            plan_id=plan_id, slide_index=1,
            slide_type="chart", title="Custom Chart Title",
        ))
        assert result["slide_type"] == "chart"
        assert result["title"] == "Custom Chart Title"

    def test_replace_slide(self):
        """replace_slide should fully replace the slide definition."""
        plan_id = self._make_plan()
        replacement = SlideDefinition(
            slide_type=SlideType.QUOTE,
            title="Inspiration",
            body="The future is now",
            quote_attribution="Unknown",
        )
        result = json.loads(update_plan_slide(
            plan_id=plan_id, slide_index=2,
            replace_slide=replacement.model_dump_json(),
        ))
        assert result["status"] == "success"
        assert result["slide_type"] == "quote"
        assert result["title"] == "Inspiration"
        assert result["mode"] == "replace_slide"

    def test_invalid_type_raises(self):
        """Invalid slide_type string should raise."""
        plan_id = self._make_plan()
        from mcp.server.fastmcp.exceptions import ToolError
        with pytest.raises(ToolError):
            update_plan_slide(
                plan_id=plan_id, slide_index=1, slide_type="nonexistent_type",
            )

    def test_invalid_replace_json_raises(self):
        """Invalid JSON for replace_slide should raise."""
        plan_id = self._make_plan()
        from mcp.server.fastmcp.exceptions import ToolError
        with pytest.raises(ToolError):
            update_plan_slide(
                plan_id=plan_id, slide_index=1, replace_slide="not valid json{{{",
            )

    def test_text_only_backward_compat(self):
        """Text-only updates (no slide_type, no replace_slide) should still work."""
        plan_id = self._make_plan()
        result = json.loads(update_plan_slide(
            plan_id=plan_id, slide_index=1, title="New Title", bullets=["A", "B"],
        ))
        assert result["status"] == "success"
        assert result["title"] == "New Title"
        # Should not have 'mode' key (text-only path doesn't set it)
        assert "mode" not in result


# =========================================================================
# TestNoPlaceholderTitles
# =========================================================================


class TestNoPlaceholderTitles:
    """No slide should have placeholder titles like 'Detail Slide N'."""

    def test_archetype_path_no_placeholders(self):
        """Archetype path should not produce placeholder titles."""
        inp = PlannerInput(
            topic="AI Strategy", num_slides=15, deck_type="pitch_deck",
            key_points=["Market size", "Product fit", "Revenue model"],
        )
        result = SlidePlannerAgent().run(inp)
        for slide in result.slides:
            assert not _has_placeholder(slide.title or ""), (
                f"Placeholder found: '{slide.title}'"
            )

    def test_key_points_path_no_placeholders(self):
        """Key-points path should not produce placeholder titles."""
        inp = PlannerInput(
            topic="Cloud Migration", num_slides=12,
            key_points=["Assessment", "Planning", "Execution", "Optimization"],
        )
        result = SlidePlannerAgent().run(inp)
        for slide in result.slides:
            assert not _has_placeholder(slide.title or ""), (
                f"Placeholder found: '{slide.title}'"
            )

    def test_default_path_no_placeholders(self):
        """Default path should not produce placeholder titles."""
        inp = PlannerInput(topic="Digital Transformation", num_slides=20)
        result = SlidePlannerAgent().run(inp)
        for slide in result.slides:
            assert not _has_placeholder(slide.title or ""), (
                f"Placeholder found: '{slide.title}'"
            )

    def test_enforce_variety_no_section_break_placeholder(self):
        """_enforce_variety should not use '[Section Break]' as title."""
        planner = SlidePlannerAgent()
        # Create 4 consecutive CONTENT slides to trigger variety enforcement
        slides = [
            SlideDefinition(slide_type=SlideType.CONTENT, title=f"Content {i}")
            for i in range(6)
        ]
        inp = PlannerInput(topic="Test Topic", num_slides=10)
        result = planner._enforce_variety(slides, inp)
        for slide in result:
            assert not _has_placeholder(slide.title or ""), (
                f"Placeholder found: '{slide.title}'"
            )


# =========================================================================
# TestStrictSlideCount
# =========================================================================


_COUNTS = [3, 5, 8, 10, 15]


class TestStrictSlideCount:
    """Planner should produce exactly the requested number of slides."""

    @pytest.mark.parametrize("count", _COUNTS)
    def test_archetype_count(self, count: int):
        inp = PlannerInput(
            topic="Count Test", num_slides=count, deck_type="pitch_deck",
        )
        result = SlidePlannerAgent().run(inp)
        assert len(result.slides) <= count, (
            f"Expected <= {count}, got {len(result.slides)}"
        )

    @pytest.mark.parametrize("count", _COUNTS)
    def test_key_points_count(self, count: int):
        inp = PlannerInput(
            topic="Count Test", num_slides=count,
            key_points=["Point A", "Point B", "Point C", "Point D"],
        )
        result = SlidePlannerAgent().run(inp)
        assert len(result.slides) <= count, (
            f"Expected <= {count}, got {len(result.slides)}"
        )

    @pytest.mark.parametrize("count", _COUNTS)
    def test_default_count(self, count: int):
        inp = PlannerInput(topic="Count Test", num_slides=count)
        result = SlidePlannerAgent().run(inp)
        assert len(result.slides) <= count, (
            f"Expected <= {count}, got {len(result.slides)}"
        )


# =========================================================================
# TestBackwardCompatibility
# =========================================================================


class TestBackwardCompatibility:
    """Existing planner behavior should be preserved when slides is not provided."""

    def test_deck_type_still_works(self):
        """deck_type path should still produce valid slides."""
        inp = PlannerInput(
            topic="Pitch", num_slides=8, deck_type="pitch_deck",
        )
        result = SlidePlannerAgent().run(inp)
        assert len(result.slides) >= 3
        assert result.slides[0].slide_type == SlideType.TITLE

    def test_key_points_still_works(self):
        """key_points path should still produce valid slides."""
        inp = PlannerInput(
            topic="Strategy", num_slides=6,
            key_points=["Growth", "Efficiency", "Innovation"],
        )
        result = SlidePlannerAgent().run(inp)
        assert len(result.slides) >= 3
        assert result.slides[0].slide_type == SlideType.TITLE

    def test_default_still_works(self):
        """Default path (no deck_type, no key_points) should produce valid slides."""
        inp = PlannerInput(topic="Overview", num_slides=6)
        result = SlidePlannerAgent().run(inp)
        assert len(result.slides) >= 3
        assert result.slides[0].slide_type == SlideType.TITLE


# =========================================================================
# TestFillerTitleHelper
# =========================================================================


class TestFillerTitleHelper:
    """Tests for the _filler_title() helper."""

    def test_uses_key_points(self):
        inp = PlannerInput(
            topic="Test", key_points=["Alpha", "Beta", "Gamma"],
        )
        planner = SlidePlannerAgent()
        assert planner._filler_title(inp, 0) == "Alpha"
        assert planner._filler_title(inp, 1) == "Beta"
        assert planner._filler_title(inp, 3) == "Alpha"  # wraps

    def test_fallback_to_topic(self):
        inp = PlannerInput(topic="Cloud Migration")
        planner = SlidePlannerAgent()
        title = planner._filler_title(inp, 0)
        assert "Cloud Migration" in title
        assert not _has_placeholder(title)


# =========================================================================
# TestGenerateTitleForType
# =========================================================================


class TestGenerateTitleForType:
    """Tests for the _generate_title_for_type() helper."""

    def test_uses_key_points_for_content(self):
        inp = PlannerInput(
            topic="Test", key_points=["Revenue", "Costs"],
        )
        planner = SlidePlannerAgent()
        title = planner._generate_title_for_type(SlideType.CHART, 0, inp)
        assert title == "Revenue"

    def test_uses_template_for_special_types(self):
        inp = PlannerInput(topic="Strategy")
        planner = SlidePlannerAgent()
        title = planner._generate_title_for_type(SlideType.CLOSING, 0, inp)
        assert title == "Key Takeaways"

    def test_title_type_uses_topic(self):
        inp = PlannerInput(topic="My Topic")
        planner = SlidePlannerAgent()
        title = planner._generate_title_for_type(SlideType.TITLE, 0, inp)
        assert title == "My Topic"
