"""Tests for connector ID-based resolution and connector types."""

from __future__ import annotations

import pytest
from pptx import Presentation
from pptx.util import Inches

from pptx_mcp.engine.shapes import add_connector_to_slide, add_shape_to_slide
from pptx_mcp.models.slides import ConnectorElement, Position, ShapeElement, ShapeType


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

def _make_slide():
    """Create a blank Presentation and slide."""
    prs = Presentation()
    slide = prs.slides.add_slide(prs.slide_layouts[6])
    return prs, slide


def _two_shapes(slide, id1="box1", id2="box2"):
    """Add two rectangle shapes to a slide, return (shape_objects, shape_id_map)."""
    s1 = ShapeElement(
        id=id1, shape_type=ShapeType.RECTANGLE, text="A",
        position=Position(left=1, top=2, width=2, height=1),
    )
    s2 = ShapeElement(
        id=id2, shape_type=ShapeType.RECTANGLE, text="B",
        position=Position(left=6, top=2, width=2, height=1),
    )
    add_shape_to_slide(slide, s1, 0)
    shape1 = slide.shapes[-1]
    add_shape_to_slide(slide, s2, 1)
    shape2 = slide.shapes[-1]

    shape_objects = [shape1, shape2]
    shape_id_map = {}
    if id1:
        shape_id_map[id1] = shape1
    if id2:
        shape_id_map[id2] = shape2
    return shape_objects, shape_id_map


# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------

class TestIdBasedConnector:
    """Connector using start_shape_id / end_shape_id."""

    def test_id_based_connector(self, tmp_path):
        prs, slide = _make_slide()
        shapes, id_map = _two_shapes(slide)

        conn = ConnectorElement(
            start_shape_id="box1",
            end_shape_id="box2",
            color="#333333",
            width=2.0,
        )
        add_connector_to_slide(slide, conn, shapes, id_map)

        out = tmp_path / "conn_id.pptx"
        prs.save(str(out))
        assert out.exists()
        # 2 shapes + 1 connector = 3 total
        assert len(slide.shapes) == 3

    def test_id_based_elbow(self, tmp_path):
        prs, slide = _make_slide()
        shapes, id_map = _two_shapes(slide)

        conn = ConnectorElement(
            start_shape_id="box1",
            end_shape_id="box2",
            connector_type="elbow",
        )
        add_connector_to_slide(slide, conn, shapes, id_map)

        out = tmp_path / "conn_elbow.pptx"
        prs.save(str(out))
        assert out.exists()
        assert len(slide.shapes) == 3


class TestIndexBasedConnector:
    """Backward-compatible index-based connectors still work."""

    def test_index_based_connector(self, tmp_path):
        prs, slide = _make_slide()
        shapes, id_map = _two_shapes(slide)

        conn = ConnectorElement(
            start_shape_index=0,
            end_shape_index=1,
            color="#FF0000",
        )
        add_connector_to_slide(slide, conn, shapes, id_map)

        out = tmp_path / "conn_index.pptx"
        prs.save(str(out))
        assert out.exists()
        assert len(slide.shapes) == 3

    def test_index_without_id_map(self, tmp_path):
        """Index-based works even when shape_id_map is None (old call signature)."""
        prs, slide = _make_slide()
        shapes, _ = _two_shapes(slide)

        conn = ConnectorElement(
            start_shape_index=0,
            end_shape_index=1,
        )
        add_connector_to_slide(slide, conn, shapes)

        assert len(slide.shapes) == 3


class TestMixedRefs:
    """Start by ID, end by index (or vice versa)."""

    def test_start_id_end_index(self, tmp_path):
        prs, slide = _make_slide()
        shapes, id_map = _two_shapes(slide)

        conn = ConnectorElement(
            start_shape_id="box1",
            end_shape_index=1,
        )
        add_connector_to_slide(slide, conn, shapes, id_map)
        assert len(slide.shapes) == 3

    def test_start_index_end_id(self, tmp_path):
        prs, slide = _make_slide()
        shapes, id_map = _two_shapes(slide)

        conn = ConnectorElement(
            start_shape_index=0,
            end_shape_id="box2",
        )
        add_connector_to_slide(slide, conn, shapes, id_map)
        assert len(slide.shapes) == 3


class TestMissingRefs:
    """Connector with missing shape refs should not crash."""

    def test_missing_id_no_crash(self):
        prs, slide = _make_slide()
        shapes, id_map = _two_shapes(slide)

        conn = ConnectorElement(
            start_shape_id="nonexistent",
            end_shape_id="box2",
        )
        # Should log warning and return without adding a connector
        add_connector_to_slide(slide, conn, shapes, id_map)
        assert len(slide.shapes) == 2  # only the two rectangles

    def test_missing_end_id_no_crash(self):
        prs, slide = _make_slide()
        shapes, id_map = _two_shapes(slide)

        conn = ConnectorElement(
            start_shape_id="box1",
            end_shape_id="ghost",
        )
        add_connector_to_slide(slide, conn, shapes, id_map)
        assert len(slide.shapes) == 2

    def test_index_out_of_range_no_crash(self):
        prs, slide = _make_slide()
        shapes, id_map = _two_shapes(slide)

        conn = ConnectorElement(
            start_shape_index=99,
            end_shape_index=1,
        )
        add_connector_to_slide(slide, conn, shapes, id_map)
        assert len(slide.shapes) == 2


class TestConnectorType:
    """Connector type selection — straight (default) and elbow."""

    def test_straight_default(self, tmp_path):
        prs, slide = _make_slide()
        shapes, id_map = _two_shapes(slide)

        conn = ConnectorElement(
            start_shape_id="box1",
            end_shape_id="box2",
            connector_type="straight",
        )
        add_connector_to_slide(slide, conn, shapes, id_map)

        out = tmp_path / "conn_straight.pptx"
        prs.save(str(out))
        assert out.exists()
        assert len(slide.shapes) == 3

    def test_elbow_connector(self, tmp_path):
        prs, slide = _make_slide()
        shapes, id_map = _two_shapes(slide)

        conn = ConnectorElement(
            start_shape_id="box1",
            end_shape_id="box2",
            connector_type="elbow",
        )
        add_connector_to_slide(slide, conn, shapes, id_map)

        out = tmp_path / "conn_elbow.pptx"
        prs.save(str(out))
        assert out.exists()
        assert len(slide.shapes) == 3


class TestModelValidation:
    """ConnectorElement model requires at least one ref per end."""

    def test_no_start_ref_raises(self):
        with pytest.raises(ValueError, match="start and end references"):
            ConnectorElement(
                end_shape_index=1,
            )

    def test_no_end_ref_raises(self):
        with pytest.raises(ValueError, match="start and end references"):
            ConnectorElement(
                start_shape_index=0,
            )

    def test_no_refs_at_all_raises(self):
        with pytest.raises(ValueError, match="start and end references"):
            ConnectorElement()

    def test_id_only_valid(self):
        conn = ConnectorElement(
            start_shape_id="a",
            end_shape_id="b",
        )
        assert conn.start_shape_id == "a"
        assert conn.end_shape_id == "b"
        assert conn.start_shape_index is None
        assert conn.end_shape_index is None

    def test_index_only_valid(self):
        conn = ConnectorElement(
            start_shape_index=0,
            end_shape_index=1,
        )
        assert conn.start_shape_index == 0
        assert conn.end_shape_index == 1


class TestIdPriorityOverIndex:
    """When both ID and index are provided, ID takes priority."""

    def test_id_takes_priority(self):
        prs, slide = _make_slide()
        shapes, id_map = _two_shapes(slide)

        # Provide both ID and index — ID should win
        conn = ConnectorElement(
            start_shape_id="box1",
            start_shape_index=99,  # invalid index, but ID should be used
            end_shape_id="box2",
            end_shape_index=99,
        )
        add_connector_to_slide(slide, conn, shapes, id_map)
        # Should succeed because IDs resolve — connector added
        assert len(slide.shapes) == 3
