"""Tests for v0.6.0 diagram models, chart data models, and connector improvements."""

from __future__ import annotations

import pytest
from pydantic import ValidationError

from pptx_mcp.models.diagrams import (
    CycleStep,
    GanttTask,
    MatrixData,
    MatrixQuadrant,
    OrgNode,
    PyramidLayer,
    SpokeItem,
    VennCircle,
    VennData,
    WaterfallBar,
)
from pptx_mcp.models.slides import (
    BubbleChartDataModel,
    BubbleDataPoint,
    BubbleDataSeries,
    ChartElement,
    ChartType,
    ConnectorElement,
    ShapeElement,
    ShapeType,
    SlideDefinition,
    SlideType,
    XyChartData,
    XyDataPoint,
    XyDataSeries,
)


# ---------------------------------------------------------------------------
# OrgNode tests
# ---------------------------------------------------------------------------


class TestOrgNode:
    def test_simple_node(self):
        node = OrgNode(id="ceo", name="Jane Doe", role="CEO")
        assert node.id == "ceo"
        assert node.name == "Jane Doe"
        assert node.role == "CEO"
        assert node.children == []

    def test_nested_tree(self):
        tree = OrgNode(
            id="root",
            name="Root",
            children=[
                OrgNode(id="a", name="A", children=[
                    OrgNode(id="a1", name="A1"),
                ]),
                OrgNode(id="b", name="B"),
            ],
        )
        assert len(tree.children) == 2
        assert len(tree.children[0].children) == 1

    def test_optional_fields(self):
        node = OrgNode(id="x", name="X")
        assert node.role is None
        assert node.color is None


# ---------------------------------------------------------------------------
# MatrixData tests
# ---------------------------------------------------------------------------


class TestMatrixData:
    def test_basic_matrix(self):
        m = MatrixData(
            top_left=MatrixQuadrant(label="TL", items=["a"]),
            top_right=MatrixQuadrant(label="TR"),
            bottom_left=MatrixQuadrant(label="BL"),
            bottom_right=MatrixQuadrant(label="BR"),
        )
        assert m.top_left.label == "TL"
        assert m.x_axis_label is None

    def test_with_axis_labels(self):
        m = MatrixData(
            x_axis_label="Impact",
            y_axis_label="Effort",
            top_left=MatrixQuadrant(label="TL"),
            top_right=MatrixQuadrant(label="TR"),
            bottom_left=MatrixQuadrant(label="BL"),
            bottom_right=MatrixQuadrant(label="BR"),
        )
        assert m.x_axis_label == "Impact"
        assert m.y_axis_label == "Effort"


# ---------------------------------------------------------------------------
# PyramidLayer tests
# ---------------------------------------------------------------------------


class TestPyramidLayer:
    def test_basic(self):
        layer = PyramidLayer(label="Strategy")
        assert layer.label == "Strategy"
        assert layer.description is None

    def test_with_description(self):
        layer = PyramidLayer(label="Ops", description="Operational layer", color="#FF0000")
        assert layer.description == "Operational layer"
        assert layer.color == "#FF0000"


# ---------------------------------------------------------------------------
# VennData tests
# ---------------------------------------------------------------------------


class TestVennData:
    def test_two_circles(self):
        v = VennData(
            circles=[
                VennCircle(label="A", items=["x", "y"]),
                VennCircle(label="B"),
            ],
        )
        assert len(v.circles) == 2

    def test_three_circles(self):
        v = VennData(
            circles=[
                VennCircle(label="A"),
                VennCircle(label="B"),
                VennCircle(label="C"),
            ],
            intersection_label="Core",
        )
        assert len(v.circles) == 3
        assert v.intersection_label == "Core"

    def test_too_few_circles(self):
        with pytest.raises(ValidationError):
            VennData(circles=[VennCircle(label="A")])

    def test_too_many_circles(self):
        with pytest.raises(ValidationError):
            VennData(circles=[VennCircle(label=c) for c in "ABCD"])


# ---------------------------------------------------------------------------
# WaterfallBar tests
# ---------------------------------------------------------------------------


class TestWaterfallBar:
    def test_defaults(self):
        bar = WaterfallBar(label="Q1", value=100)
        assert bar.bar_type == "increase"
        assert bar.color is None

    def test_decrease(self):
        bar = WaterfallBar(label="Churn", value=-20, bar_type="decrease")
        assert bar.bar_type == "decrease"

    def test_total(self):
        bar = WaterfallBar(label="Net", value=80, bar_type="total")
        assert bar.bar_type == "total"


# ---------------------------------------------------------------------------
# SpokeItem / CycleStep tests
# ---------------------------------------------------------------------------


class TestSpokeItem:
    def test_basic(self):
        s = SpokeItem(label="Marketing")
        assert s.label == "Marketing"
        assert s.description is None


class TestCycleStep:
    def test_basic(self):
        c = CycleStep(label="Plan", description="Planning phase")
        assert c.label == "Plan"


# ---------------------------------------------------------------------------
# GanttTask tests
# ---------------------------------------------------------------------------


class TestGanttTask:
    def test_basic(self):
        t = GanttTask(label="Design", start=0.0, duration=0.3)
        assert t.progress == 0.0
        assert t.group is None

    def test_with_progress(self):
        t = GanttTask(label="Build", start=0.2, duration=0.5, progress=0.75)
        assert t.progress == 0.75

    def test_invalid_duration(self):
        with pytest.raises(ValidationError):
            GanttTask(label="Bad", start=0.0, duration=0.0)

    def test_invalid_start(self):
        with pytest.raises(ValidationError):
            GanttTask(label="Bad", start=-1.0, duration=0.5)


# ---------------------------------------------------------------------------
# XyChartData tests
# ---------------------------------------------------------------------------


class TestXyChartData:
    def test_basic(self):
        data = XyChartData(series=[
            XyDataSeries(name="S1", points=[
                XyDataPoint(x=1.0, y=2.0),
                XyDataPoint(x=3.0, y=4.0),
            ]),
        ])
        assert len(data.series) == 1
        assert len(data.series[0].points) == 2


# ---------------------------------------------------------------------------
# BubbleChartDataModel tests
# ---------------------------------------------------------------------------


class TestBubbleChartDataModel:
    def test_basic(self):
        data = BubbleChartDataModel(series=[
            BubbleDataSeries(name="S1", points=[
                BubbleDataPoint(x=1.0, y=2.0, size=10.0),
            ]),
        ])
        assert data.series[0].points[0].size == 10.0

    def test_invalid_size(self):
        with pytest.raises(ValidationError):
            BubbleDataPoint(x=1.0, y=2.0, size=0.0)


# ---------------------------------------------------------------------------
# ChartElement validator tests
# ---------------------------------------------------------------------------


class TestChartElementValidator:
    def test_scatter_requires_data(self):
        with pytest.raises(ValidationError):
            ChartElement(chart_type=ChartType.SCATTER)

    def test_scatter_with_xy_data(self):
        elem = ChartElement(
            chart_type=ChartType.SCATTER,
            xy_data=XyChartData(series=[
                XyDataSeries(name="S1", points=[XyDataPoint(x=1, y=2)]),
            ]),
        )
        assert elem.chart_type == ChartType.SCATTER

    def test_bubble_requires_data(self):
        with pytest.raises(ValidationError):
            ChartElement(chart_type=ChartType.BUBBLE)

    def test_bubble_with_bubble_data(self):
        elem = ChartElement(
            chart_type=ChartType.BUBBLE,
            bubble_data=BubbleChartDataModel(series=[
                BubbleDataSeries(name="S1", points=[
                    BubbleDataPoint(x=1, y=2, size=5),
                ]),
            ]),
        )
        assert elem.chart_type == ChartType.BUBBLE


# ---------------------------------------------------------------------------
# ConnectorElement validator tests
# ---------------------------------------------------------------------------


class TestConnectorElementValidator:
    def test_index_based(self):
        c = ConnectorElement(start_shape_index=0, end_shape_index=1)
        assert c.connector_type == "straight"

    def test_id_based(self):
        c = ConnectorElement(start_shape_id="a", end_shape_id="b")
        assert c.start_shape_index is None

    def test_mixed(self):
        c = ConnectorElement(start_shape_id="a", end_shape_index=1)
        assert c.start_shape_id == "a"

    def test_missing_start(self):
        with pytest.raises(ValidationError):
            ConnectorElement(end_shape_index=1)

    def test_missing_end(self):
        with pytest.raises(ValidationError):
            ConnectorElement(start_shape_index=0)

    def test_elbow_type(self):
        c = ConnectorElement(
            start_shape_id="a", end_shape_id="b", connector_type="elbow"
        )
        assert c.connector_type == "elbow"


# ---------------------------------------------------------------------------
# ShapeElement id field
# ---------------------------------------------------------------------------


class TestShapeElementId:
    def test_default_none(self):
        s = ShapeElement(shape_type=ShapeType.RECTANGLE)
        assert s.id is None

    def test_with_id(self):
        s = ShapeElement(id="box1", shape_type=ShapeType.RECTANGLE)
        assert s.id == "box1"


# ---------------------------------------------------------------------------
# SlideDefinition diagram fields
# ---------------------------------------------------------------------------


class TestSlideDefinitionDiagramFields:
    def test_org_chart_field(self):
        sd = SlideDefinition(
            slide_type=SlideType.ORG_CHART,
            title="Org",
            org_chart=OrgNode(id="ceo", name="CEO"),
        )
        assert sd.org_chart is not None

    def test_matrix_field(self):
        sd = SlideDefinition(
            slide_type=SlideType.MATRIX,
            title="Matrix",
            matrix=MatrixData(
                top_left=MatrixQuadrant(label="TL"),
                top_right=MatrixQuadrant(label="TR"),
                bottom_left=MatrixQuadrant(label="BL"),
                bottom_right=MatrixQuadrant(label="BR"),
            ),
        )
        assert sd.matrix is not None

    def test_pyramid_field(self):
        sd = SlideDefinition(
            slide_type=SlideType.PYRAMID,
            title="Pyramid",
            pyramid_layers=[
                PyramidLayer(label="Top"),
                PyramidLayer(label="Bottom"),
            ],
        )
        assert len(sd.pyramid_layers) == 2

    def test_venn_field(self):
        sd = SlideDefinition(
            slide_type=SlideType.VENN,
            title="Venn",
            venn=VennData(circles=[
                VennCircle(label="A"),
                VennCircle(label="B"),
            ]),
        )
        assert sd.venn is not None

    def test_waterfall_field(self):
        sd = SlideDefinition(
            slide_type=SlideType.WATERFALL,
            title="Waterfall",
            waterfall_bars=[
                WaterfallBar(label="Start", value=100, bar_type="total"),
            ],
        )
        assert len(sd.waterfall_bars) == 1

    def test_hub_spoke_fields(self):
        sd = SlideDefinition(
            slide_type=SlideType.HUB_SPOKE,
            title="Hub",
            hub_spoke_center="Core",
            spokes=[SpokeItem(label="A"), SpokeItem(label="B")],
        )
        assert sd.hub_spoke_center == "Core"
        assert len(sd.spokes) == 2

    def test_cycle_field(self):
        sd = SlideDefinition(
            slide_type=SlideType.CYCLE,
            title="Cycle",
            cycle_steps=[CycleStep(label="Plan"), CycleStep(label="Do")],
        )
        assert len(sd.cycle_steps) == 2

    def test_gantt_fields(self):
        sd = SlideDefinition(
            slide_type=SlideType.GANTT,
            title="Gantt",
            gantt_tasks=[GanttTask(label="Task 1", start=0.0, duration=0.5)],
            gantt_phases=["Phase 1", "Phase 2"],
        )
        assert len(sd.gantt_tasks) == 1
        assert len(sd.gantt_phases) == 2

    def test_all_diagram_defaults(self):
        """All diagram fields default to None or empty list."""
        sd = SlideDefinition(slide_type=SlideType.CONTENT, title="Test")
        assert sd.org_chart is None
        assert sd.matrix is None
        assert sd.pyramid_layers == []
        assert sd.venn is None
        assert sd.waterfall_bars == []
        assert sd.hub_spoke_center is None
        assert sd.spokes == []
        assert sd.cycle_steps == []
        assert sd.gantt_tasks == []
        assert sd.gantt_phases == []
