"""Tests for Stream 2A diagram renderers: org_chart, matrix, pyramid, venn."""

from __future__ import annotations

from pathlib import Path

import pytest
from pptx import Presentation

from pptx_mcp.brand.defaults import PRESETS
from pptx_mcp.engine.generator import DeckGenerator
from pptx_mcp.models.diagrams import (
    MatrixData,
    MatrixQuadrant,
    OrgNode,
    PyramidLayer,
    VennCircle,
    VennData,
)
from pptx_mcp.models.slides import (
    PresentationDefinition,
    PresentationMetadata,
    SlideDefinition,
    SlideType,
)


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


# ---------------------------------------------------------------------------
# Org chart tests
# ---------------------------------------------------------------------------


class TestOrgChart:
    def test_basic_org_chart(self, tmp_path: Path) -> None:
        brand = PRESETS["executive"].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        slide = SlideDefinition(
            slide_type=SlideType.ORG_CHART,
            title="Organization",
            org_chart=OrgNode(
                id="ceo",
                name="Jane Smith",
                role="CEO",
                children=[
                    OrgNode(id="vp1", name="Alice", role="VP Engineering"),
                    OrgNode(id="vp2", name="Bob", role="VP Sales"),
                ],
            ),
        )
        path = gen.generate(_make_defn([slide]), tmp_path / "org.pptx")
        assert path.exists()
        prs = Presentation(str(path))
        assert len(prs.slides) == 1

    def test_deep_org_chart(self, tmp_path: Path) -> None:
        """5-level deep tree."""
        brand = PRESETS["technical"].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        # Build a chain: CEO -> VP -> Dir -> Mgr -> IC
        ic = OrgNode(id="ic", name="IC", role="Engineer")
        mgr = OrgNode(id="mgr", name="Manager", role="Mgr", children=[ic])
        dr = OrgNode(id="dir", name="Director", role="Dir", children=[mgr])
        vp = OrgNode(id="vp", name="VP", role="VP", children=[dr])
        ceo = OrgNode(id="ceo", name="CEO", role="CEO", children=[vp])
        slide = SlideDefinition(
            slide_type=SlideType.ORG_CHART,
            title="Deep Org",
            org_chart=ceo,
        )
        path = gen.generate(_make_defn([slide]), tmp_path / "deep_org.pptx")
        assert path.exists()

    def test_wide_org_chart(self, tmp_path: Path) -> None:
        """Wide tree with many direct reports."""
        brand = PRESETS["minimal"].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        children = [
            OrgNode(id=f"c{i}", name=f"Person {i}", role=f"Role {i}")
            for i in range(6)
        ]
        root = OrgNode(id="ceo", name="CEO", role="Chief Executive", children=children)
        slide = SlideDefinition(
            slide_type=SlideType.ORG_CHART,
            title="Wide Team",
            org_chart=root,
        )
        path = gen.generate(_make_defn([slide]), tmp_path / "wide_org.pptx")
        assert path.exists()

    def test_org_chart_none(self, tmp_path: Path) -> None:
        """Gracefully handle None org_chart data."""
        brand = PRESETS["executive"].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        slide = SlideDefinition(
            slide_type=SlideType.ORG_CHART,
            title="Empty Org",
            org_chart=None,
        )
        path = gen.generate(_make_defn([slide]), tmp_path / "empty_org.pptx")
        assert path.exists()

    def test_org_chart_single_node(self, tmp_path: Path) -> None:
        """Single node (no children)."""
        brand = PRESETS["creative"].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        slide = SlideDefinition(
            slide_type=SlideType.ORG_CHART,
            title="Solo",
            org_chart=OrgNode(id="solo", name="Solo Person", role="Founder"),
        )
        path = gen.generate(_make_defn([slide]), tmp_path / "solo_org.pptx")
        assert path.exists()

    def test_org_chart_custom_colors(self, tmp_path: Path) -> None:
        """Nodes with explicit color overrides."""
        brand = PRESETS["corporate"].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        slide = SlideDefinition(
            slide_type=SlideType.ORG_CHART,
            title="Colored Org",
            org_chart=OrgNode(
                id="root",
                name="Root",
                role="Boss",
                color="#FF0000",
                children=[
                    OrgNode(id="a", name="A", role="Lead", color="#00FF00"),
                ],
            ),
        )
        path = gen.generate(_make_defn([slide]), tmp_path / "colored_org.pptx")
        assert path.exists()


# ---------------------------------------------------------------------------
# Matrix tests
# ---------------------------------------------------------------------------


class TestMatrix:
    def test_basic_matrix(self, tmp_path: Path) -> None:
        brand = PRESETS["consulting"].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        slide = SlideDefinition(
            slide_type=SlideType.MATRIX,
            title="Priority Matrix",
            matrix=MatrixData(
                x_axis_label="Effort",
                y_axis_label="Impact",
                top_left=MatrixQuadrant(label="Quick Wins", items=["Task A", "Task B"]),
                top_right=MatrixQuadrant(label="Major Projects", items=["Task C"]),
                bottom_left=MatrixQuadrant(label="Fill-ins", items=["Task D", "Task E"]),
                bottom_right=MatrixQuadrant(label="Thankless Tasks", items=["Task F"]),
            ),
        )
        path = gen.generate(_make_defn([slide]), tmp_path / "matrix.pptx")
        assert path.exists()
        prs = Presentation(str(path))
        assert len(prs.slides) == 1

    def test_matrix_no_axis_labels(self, tmp_path: Path) -> None:
        """Matrix without axis labels."""
        brand = PRESETS["executive"].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        slide = SlideDefinition(
            slide_type=SlideType.MATRIX,
            title="Simple Matrix",
            matrix=MatrixData(
                top_left=MatrixQuadrant(label="TL"),
                top_right=MatrixQuadrant(label="TR"),
                bottom_left=MatrixQuadrant(label="BL"),
                bottom_right=MatrixQuadrant(label="BR"),
            ),
        )
        path = gen.generate(_make_defn([slide]), tmp_path / "matrix_no_axis.pptx")
        assert path.exists()

    def test_matrix_none(self, tmp_path: Path) -> None:
        """Gracefully handle None matrix data."""
        brand = PRESETS["minimal"].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        slide = SlideDefinition(
            slide_type=SlideType.MATRIX,
            title="Empty Matrix",
            matrix=None,
        )
        path = gen.generate(_make_defn([slide]), tmp_path / "empty_matrix.pptx")
        assert path.exists()

    def test_matrix_with_custom_colors(self, tmp_path: Path) -> None:
        """Quadrants with explicit color overrides."""
        brand = PRESETS["technical"].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        slide = SlideDefinition(
            slide_type=SlideType.MATRIX,
            title="Custom Colors",
            matrix=MatrixData(
                top_left=MatrixQuadrant(label="A", color="#FF5733"),
                top_right=MatrixQuadrant(label="B", color="#33FF57"),
                bottom_left=MatrixQuadrant(label="C", color="#3357FF"),
                bottom_right=MatrixQuadrant(label="D", color="#FF33F5"),
            ),
        )
        path = gen.generate(_make_defn([slide]), tmp_path / "matrix_colors.pptx")
        assert path.exists()


# ---------------------------------------------------------------------------
# Pyramid tests
# ---------------------------------------------------------------------------


class TestPyramid:
    def test_basic_pyramid(self, tmp_path: Path) -> None:
        brand = PRESETS["executive"].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        slide = SlideDefinition(
            slide_type=SlideType.PYRAMID,
            title="Maslow's Hierarchy",
            pyramid_layers=[
                PyramidLayer(label="Self-actualization", description="Achieving full potential"),
                PyramidLayer(label="Esteem", description="Respect and recognition"),
                PyramidLayer(label="Love/Belonging", description="Friendship, intimacy"),
                PyramidLayer(label="Safety", description="Security, employment"),
                PyramidLayer(label="Physiological", description="Food, water, shelter"),
            ],
        )
        path = gen.generate(_make_defn([slide]), tmp_path / "pyramid.pptx")
        assert path.exists()
        prs = Presentation(str(path))
        assert len(prs.slides) == 1

    def test_pyramid_empty(self, tmp_path: Path) -> None:
        """Gracefully handle empty pyramid_layers."""
        brand = PRESETS["minimal"].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        slide = SlideDefinition(
            slide_type=SlideType.PYRAMID,
            title="Empty Pyramid",
            pyramid_layers=[],
        )
        path = gen.generate(_make_defn([slide]), tmp_path / "empty_pyramid.pptx")
        assert path.exists()

    def test_pyramid_two_layers(self, tmp_path: Path) -> None:
        """Minimal 2-layer pyramid."""
        brand = PRESETS["startup"].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        slide = SlideDefinition(
            slide_type=SlideType.PYRAMID,
            title="Two Tier",
            pyramid_layers=[
                PyramidLayer(label="Top"),
                PyramidLayer(label="Bottom"),
            ],
        )
        path = gen.generate(_make_defn([slide]), tmp_path / "pyramid_two.pptx")
        assert path.exists()

    def test_pyramid_custom_colors(self, tmp_path: Path) -> None:
        """Layers with explicit color overrides."""
        brand = PRESETS["corporate"].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        slide = SlideDefinition(
            slide_type=SlideType.PYRAMID,
            title="Colored Pyramid",
            pyramid_layers=[
                PyramidLayer(label="A", color="#E74C3C"),
                PyramidLayer(label="B", color="#3498DB"),
                PyramidLayer(label="C", color="#2ECC71"),
            ],
        )
        path = gen.generate(_make_defn([slide]), tmp_path / "pyramid_colors.pptx")
        assert path.exists()


# ---------------------------------------------------------------------------
# Venn diagram tests
# ---------------------------------------------------------------------------


class TestVenn:
    def test_two_circle_venn(self, tmp_path: Path) -> None:
        brand = PRESETS["executive"].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        slide = SlideDefinition(
            slide_type=SlideType.VENN,
            title="Skills Overlap",
            venn=VennData(
                circles=[
                    VennCircle(label="Design", items=["UI", "UX"]),
                    VennCircle(label="Engineering", items=["Backend", "Frontend"]),
                ],
                intersection_label="Product",
            ),
        )
        path = gen.generate(_make_defn([slide]), tmp_path / "venn2.pptx")
        assert path.exists()
        prs = Presentation(str(path))
        assert len(prs.slides) == 1

    def test_three_circle_venn(self, tmp_path: Path) -> None:
        brand = PRESETS["creative"].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        slide = SlideDefinition(
            slide_type=SlideType.VENN,
            title="Triple Venn",
            venn=VennData(
                circles=[
                    VennCircle(label="A", items=["a1"]),
                    VennCircle(label="B", items=["b1"]),
                    VennCircle(label="C", items=["c1"]),
                ],
                intersection_label="Core",
            ),
        )
        path = gen.generate(_make_defn([slide]), tmp_path / "venn3.pptx")
        assert path.exists()

    def test_venn_none(self, tmp_path: Path) -> None:
        """Gracefully handle None venn data."""
        brand = PRESETS["minimal"].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        slide = SlideDefinition(
            slide_type=SlideType.VENN,
            title="Empty Venn",
            venn=None,
        )
        path = gen.generate(_make_defn([slide]), tmp_path / "empty_venn.pptx")
        assert path.exists()

    def test_venn_no_intersection_label(self, tmp_path: Path) -> None:
        """Venn without intersection label."""
        brand = PRESETS["technical"].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        slide = SlideDefinition(
            slide_type=SlideType.VENN,
            title="No Intersection",
            venn=VennData(
                circles=[
                    VennCircle(label="X"),
                    VennCircle(label="Y"),
                ],
            ),
        )
        path = gen.generate(_make_defn([slide]), tmp_path / "venn_no_int.pptx")
        assert path.exists()

    def test_venn_custom_colors(self, tmp_path: Path) -> None:
        """Circles with explicit color overrides."""
        brand = PRESETS["consulting"].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        slide = SlideDefinition(
            slide_type=SlideType.VENN,
            title="Custom Venn",
            venn=VennData(
                circles=[
                    VennCircle(label="Red", color="#E74C3C"),
                    VennCircle(label="Blue", color="#3498DB"),
                ],
                intersection_label="Purple",
            ),
        )
        path = gen.generate(_make_defn([slide]), tmp_path / "venn_colors.pptx")
        assert path.exists()


# ---------------------------------------------------------------------------
# Cross-cutting tests
# ---------------------------------------------------------------------------


class TestDiagramsCrossCutting:
    @pytest.mark.parametrize("preset_name", ["executive", "technical", "minimal", "creative"])
    def test_org_chart_multiple_presets(self, tmp_path: Path, preset_name: str) -> None:
        """Org chart renders with different brand presets."""
        brand = PRESETS[preset_name].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        slide = SlideDefinition(
            slide_type=SlideType.ORG_CHART,
            title=f"Org ({preset_name})",
            org_chart=OrgNode(
                id="r", name="Root", role="Boss",
                children=[OrgNode(id="a", name="A", role="Worker")],
            ),
        )
        path = gen.generate(_make_defn([slide]), tmp_path / f"org_{preset_name}.pptx")
        assert path.exists()
