"""Parser round-trip tests for v0.6.0 diagram types and expanded chart types.

Tests generate a PPTX with a specific slide type, then parse it back
and verify that the correct SlideType was detected and key fields
were populated.
"""

from __future__ import annotations

from pathlib import Path

import pytest

from pptx_mcp.brand.defaults import PRESETS
from pptx_mcp.engine.generator import DeckGenerator
from pptx_mcp.engine.parser import parse_presentation, ingest_presentation
from pptx_mcp.models.diagrams import (
    CycleStep,
    GanttTask,
    MatrixData,
    MatrixQuadrant,
    OrgNode,
    PyramidLayer,
    SpokeItem,
    VennCircle,
    VennData,
    WaterfallBar,
)
from pptx_mcp.models.slides import (
    ChartData,
    ChartDataSeries,
    ChartElement,
    ChartType,
    PresentationDefinition,
    PresentationMetadata,
    SlideDefinition,
    SlideType,
)


def _generate_and_parse(
    slide_def: SlideDefinition, tmp_path: Path, preset: str = "executive"
) -> SlideDefinition:
    """Helper: generate a PPTX with a title slide + target slide, parse back target.

    A preceding TITLE slide is added so the target slide gets slide_index=1,
    avoiding the first-slide TITLE heuristic in _classify_slide().
    """
    brand = PRESETS[preset].model_copy(deep=True)
    gen = DeckGenerator(brand=brand)
    title_slide = SlideDefinition(slide_type=SlideType.TITLE, title="Test Deck")
    defn = PresentationDefinition(
        metadata=PresentationMetadata(title="Round-trip Test"),
        slides=[title_slide, slide_def],
    )
    out = tmp_path / "roundtrip.pptx"
    gen.generate(defn, out)
    parsed = parse_presentation(out)
    assert len(parsed.slides) >= 2
    return parsed.slides[1]


# --- Diagram type round-trips ---


class TestOrgChartRoundTrip:
    def test_type_detected(self, tmp_path: Path) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.ORG_CHART,
            title="Organization Chart",
            org_chart=OrgNode(
                id="ceo", name="Alice", role="CEO",
                children=[
                    OrgNode(id="vp1", name="Bob", role="VP Engineering"),
                    OrgNode(id="vp2", name="Carol", role="VP Product"),
                ],
            ),
        )
        parsed = _generate_and_parse(slide, tmp_path)
        assert parsed.slide_type == SlideType.ORG_CHART

    def test_org_chart_populated(self, tmp_path: Path) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.ORG_CHART,
            title="Org Chart",
            org_chart=OrgNode(
                id="root", name="CEO", role="Chief",
                children=[OrgNode(id="c1", name="VP", role="Vice Pres")],
            ),
        )
        parsed = _generate_and_parse(slide, tmp_path)
        assert parsed.org_chart is not None
        assert parsed.org_chart.name  # has a non-empty name


class TestMatrixRoundTrip:
    def test_type_detected(self, tmp_path: Path) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.MATRIX,
            title="Prioritization Matrix",
            matrix=MatrixData(
                top_left=MatrixQuadrant(label="High Impact / Low Effort", items=["Quick wins"]),
                top_right=MatrixQuadrant(label="High Impact / High Effort"),
                bottom_left=MatrixQuadrant(label="Low Impact / Low Effort"),
                bottom_right=MatrixQuadrant(label="Low Impact / High Effort"),
            ),
        )
        parsed = _generate_and_parse(slide, tmp_path)
        assert parsed.slide_type == SlideType.MATRIX

    def test_matrix_populated(self, tmp_path: Path) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.MATRIX,
            title="2x2 Matrix",
            matrix=MatrixData(
                top_left=MatrixQuadrant(label="TL"),
                top_right=MatrixQuadrant(label="TR"),
                bottom_left=MatrixQuadrant(label="BL"),
                bottom_right=MatrixQuadrant(label="BR"),
            ),
        )
        parsed = _generate_and_parse(slide, tmp_path)
        assert parsed.matrix is not None


class TestPyramidRoundTrip:
    def test_type_detected(self, tmp_path: Path) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.PYRAMID,
            title="Pyramid Model",
            pyramid_layers=[
                PyramidLayer(label="Strategy"),
                PyramidLayer(label="Tactics"),
                PyramidLayer(label="Operations"),
            ],
        )
        parsed = _generate_and_parse(slide, tmp_path)
        assert parsed.slide_type == SlideType.PYRAMID

    def test_layers_populated(self, tmp_path: Path) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.PYRAMID,
            title="Pyramid Tiers",
            pyramid_layers=[
                PyramidLayer(label="Top"),
                PyramidLayer(label="Middle"),
                PyramidLayer(label="Base"),
            ],
        )
        parsed = _generate_and_parse(slide, tmp_path)
        assert len(parsed.pyramid_layers) >= 1


class TestVennRoundTrip:
    def test_type_detected(self, tmp_path: Path) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.VENN,
            title="Venn Diagram",
            venn=VennData(
                circles=[VennCircle(label="Design"), VennCircle(label="Engineering")],
                intersection_label="UX",
            ),
        )
        parsed = _generate_and_parse(slide, tmp_path)
        assert parsed.slide_type == SlideType.VENN

    def test_venn_populated(self, tmp_path: Path) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.VENN,
            title="Overlap Analysis",
            venn=VennData(
                circles=[VennCircle(label="A"), VennCircle(label="B")],
            ),
        )
        parsed = _generate_and_parse(slide, tmp_path)
        assert parsed.venn is not None
        assert len(parsed.venn.circles) >= 2


class TestWaterfallRoundTrip:
    def test_type_detected(self, tmp_path: Path) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.WATERFALL,
            title="Revenue Waterfall",
            waterfall_bars=[
                WaterfallBar(label="Start", value=100, bar_type="total"),
                WaterfallBar(label="Growth", value=30, bar_type="increase"),
                WaterfallBar(label="Loss", value=-10, bar_type="decrease"),
                WaterfallBar(label="End", value=120, bar_type="total"),
            ],
        )
        parsed = _generate_and_parse(slide, tmp_path)
        assert parsed.slide_type == SlideType.WATERFALL

    def test_bars_populated(self, tmp_path: Path) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.WATERFALL,
            title="Variance Bridge",
            waterfall_bars=[
                WaterfallBar(label="Baseline", value=100, bar_type="total"),
                WaterfallBar(label="Add", value=20, bar_type="increase"),
                WaterfallBar(label="Total", value=120, bar_type="total"),
            ],
        )
        parsed = _generate_and_parse(slide, tmp_path)
        assert len(parsed.waterfall_bars) >= 1


class TestHubSpokeRoundTrip:
    def test_type_detected(self, tmp_path: Path) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.HUB_SPOKE,
            title="Ecosystem Hub and Spoke",
            hub_spoke_center="Core Platform",
            spokes=[
                SpokeItem(label="API"),
                SpokeItem(label="SDK"),
                SpokeItem(label="Docs"),
            ],
        )
        parsed = _generate_and_parse(slide, tmp_path)
        assert parsed.slide_type == SlideType.HUB_SPOKE

    def test_spokes_populated(self, tmp_path: Path) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.HUB_SPOKE,
            title="Hub Spoke Model",
            hub_spoke_center="Hub",
            spokes=[SpokeItem(label="S1"), SpokeItem(label="S2")],
        )
        parsed = _generate_and_parse(slide, tmp_path)
        assert len(parsed.spokes) >= 1


class TestCycleRoundTrip:
    def test_type_detected(self, tmp_path: Path) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.CYCLE,
            title="Continuous Improvement Cycle",
            cycle_steps=[
                CycleStep(label="Plan"),
                CycleStep(label="Do"),
                CycleStep(label="Check"),
                CycleStep(label="Act"),
            ],
        )
        parsed = _generate_and_parse(slide, tmp_path)
        assert parsed.slide_type == SlideType.CYCLE

    def test_steps_populated(self, tmp_path: Path) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.CYCLE,
            title="Continuous Loop",
            cycle_steps=[CycleStep(label="A"), CycleStep(label="B"), CycleStep(label="C")],
        )
        parsed = _generate_and_parse(slide, tmp_path)
        assert len(parsed.cycle_steps) >= 1


class TestGanttRoundTrip:
    def test_type_detected(self, tmp_path: Path) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.GANTT,
            title="Project Plan Gantt",
            gantt_tasks=[
                GanttTask(label="Design", start=0.0, duration=0.3),
                GanttTask(label="Build", start=0.2, duration=0.4),
                GanttTask(label="Test", start=0.5, duration=0.3),
            ],
            gantt_phases=["Q1", "Q2"],
        )
        parsed = _generate_and_parse(slide, tmp_path)
        assert parsed.slide_type == SlideType.GANTT

    def test_tasks_populated(self, tmp_path: Path) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.GANTT,
            title="Gantt Chart",
            gantt_tasks=[
                GanttTask(label="T1", start=0.0, duration=0.3),
                GanttTask(label="T2", start=0.2, duration=0.4),
            ],
            gantt_phases=["P1", "P2"],
        )
        parsed = _generate_and_parse(slide, tmp_path)
        assert len(parsed.gantt_tasks) >= 1


# --- Chart type round-trips ---


class TestExpandedChartRoundTrip:
    def test_scatter_chart(self, tmp_path: Path) -> None:
        """Scatter charts use XyChartData internally — parser should detect SCATTER type."""
        from pptx_mcp.models.slides import XyChartData, XyDataSeries, XyDataPoint

        slide = SlideDefinition(
            slide_type=SlideType.CHART,
            title="Scatter Plot",
            elements=[
                ChartElement(
                    chart_type=ChartType.SCATTER,
                    xy_data=XyChartData(series=[
                        XyDataSeries(name="S1", points=[
                            XyDataPoint(x=1.0, y=2.0),
                            XyDataPoint(x=3.0, y=4.0),
                        ]),
                    ]),
                ),
            ],
        )
        parsed = _generate_and_parse(slide, tmp_path)
        assert parsed.slide_type == SlideType.CHART
        chart_elems = [e for e in parsed.elements if isinstance(e, ChartElement)]
        assert len(chart_elems) >= 1

    def test_doughnut_chart(self, tmp_path: Path) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.CHART,
            title="Doughnut Chart",
            elements=[
                ChartElement(
                    chart_type=ChartType.DOUGHNUT,
                    data=ChartData(
                        categories=["A", "B", "C"],
                        series=[ChartDataSeries(name="S1", values=[30.0, 50.0, 20.0])],
                    ),
                ),
            ],
        )
        parsed = _generate_and_parse(slide, tmp_path)
        assert parsed.slide_type == SlideType.CHART
        chart_elems = [e for e in parsed.elements if isinstance(e, ChartElement)]
        assert len(chart_elems) >= 1

    def test_radar_chart(self, tmp_path: Path) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.CHART,
            title="Radar Chart",
            elements=[
                ChartElement(
                    chart_type=ChartType.RADAR,
                    data=ChartData(
                        categories=["Speed", "Power", "Range"],
                        series=[ChartDataSeries(name="S1", values=[8.0, 6.0, 7.0])],
                    ),
                ),
            ],
        )
        parsed = _generate_and_parse(slide, tmp_path)
        chart_elems = [e for e in parsed.elements if isinstance(e, ChartElement)]
        assert len(chart_elems) >= 1

    def test_stacked_bar_chart(self, tmp_path: Path) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.CHART,
            title="Stacked Bar",
            elements=[
                ChartElement(
                    chart_type=ChartType.STACKED_BAR,
                    data=ChartData(
                        categories=["2023", "2024"],
                        series=[
                            ChartDataSeries(name="Product", values=[10.0, 20.0]),
                            ChartDataSeries(name="Service", values=[5.0, 15.0]),
                        ],
                    ),
                ),
            ],
        )
        parsed = _generate_and_parse(slide, tmp_path)
        chart_elems = [e for e in parsed.elements if isinstance(e, ChartElement)]
        assert len(chart_elems) >= 1

    def test_stacked_column_chart(self, tmp_path: Path) -> None:
        slide = SlideDefinition(
            slide_type=SlideType.CHART,
            title="Stacked Column",
            elements=[
                ChartElement(
                    chart_type=ChartType.STACKED_COLUMN,
                    data=ChartData(
                        categories=["Q1", "Q2"],
                        series=[
                            ChartDataSeries(name="A", values=[10.0, 20.0]),
                            ChartDataSeries(name="B", values=[5.0, 15.0]),
                        ],
                    ),
                ),
            ],
        )
        parsed = _generate_and_parse(slide, tmp_path)
        chart_elems = [e for e in parsed.elements if isinstance(e, ChartElement)]
        assert len(chart_elems) >= 1


# --- Ingest round-trip ---


class TestIngestDiagramTypes:
    """Verify ingest_presentation strips elements for diagram types."""

    @pytest.mark.parametrize("slide_type", [
        SlideType.ORG_CHART,
        SlideType.MATRIX,
        SlideType.PYRAMID,
        SlideType.VENN,
        SlideType.WATERFALL,
        SlideType.HUB_SPOKE,
        SlideType.CYCLE,
        SlideType.GANTT,
    ], ids=lambda t: t.value)
    def test_elements_stripped(self, slide_type: SlideType, tmp_path: Path) -> None:
        """Diagram types should have text/shape elements stripped by ingest."""
        from tests.test_brand_matrix import _minimal_slide

        slide_def = _minimal_slide(slide_type)
        title_slide = SlideDefinition(slide_type=SlideType.TITLE, title="Ingest Test")
        brand = PRESETS["executive"].model_copy(deep=True)
        gen = DeckGenerator(brand=brand)
        defn = PresentationDefinition(
            metadata=PresentationMetadata(title="Ingest Test"),
            slides=[title_slide, slide_def],
        )
        out = tmp_path / "ingest.pptx"
        gen.generate(defn, out)

        ingested = ingest_presentation(out)
        assert len(ingested.slides) >= 2
        parsed_slide = ingested.slides[1]
        # Should have no TextBox/ShapeElement in elements (they're stripped)
        from pptx_mcp.models.slides import TextBox, ShapeElement
        for elem in parsed_slide.elements:
            assert not isinstance(elem, (TextBox, ShapeElement)), (
                f"TextBox/ShapeElement not stripped for {slide_type.value}"
            )
