"""Edge case and boundary tests for pptx_mcp models and generation."""

from __future__ import annotations

import json
from pathlib import Path

import pytest
from pydantic import ValidationError

from pptx_mcp.brand.defaults import executive_default
from pptx_mcp.engine.generator import DeckGenerator
from pptx_mcp.models.slides import (
    ChartData,
    ChartDataSeries,
    ChartElement,
    ChartType,
    ColumnContent,
    PresentationDefinition,
    PresentationMetadata,
    SlideDefinition,
    SlideType,
    TableCell,
    TableElement,
)


def _make_definition(
    slides: list[SlideDefinition],
    **kwargs,
) -> PresentationDefinition:
    return PresentationDefinition(
        metadata=PresentationMetadata(title="Edge Case Test"),
        slides=slides,
        **kwargs,
    )


@pytest.fixture
def generator() -> DeckGenerator:
    return DeckGenerator(brand=executive_default())


class TestEmptyPresentation:
    """Test behavior with zero slides."""

    def test_empty_slides_list_allowed_by_model(self) -> None:
        """An empty slides list is valid at the Pydantic level (max_length=100, no min)."""
        definition = _make_definition([])
        assert len(definition.slides) == 0

    def test_generate_empty_deck(self, generator: DeckGenerator, tmp_path: Path) -> None:
        """Generator should handle empty slides list (produces a file with 0 slides)."""
        definition = _make_definition([])
        path = generator.generate(definition, tmp_path / "empty.pptx")
        assert path.exists()


class TestSingleSlidePresentation:
    """Test with exactly one slide."""

    def test_single_title_slide(self, generator: DeckGenerator, tmp_path: Path) -> None:
        definition = _make_definition([
            SlideDefinition(slide_type=SlideType.TITLE, title="Only Slide"),
        ])
        path = generator.generate(definition, tmp_path / "single.pptx")
        assert path.exists()

        from pptx import Presentation
        prs = Presentation(str(path))
        assert len(prs.slides) == 1


class TestBulletLimits:
    """Test bullet count boundaries on SlideDefinition."""

    def test_max_bullets_30(self) -> None:
        """30 bullets should be accepted (max_length=30)."""
        slide = SlideDefinition(
            slide_type=SlideType.CONTENT,
            title="Max Bullets",
            bullets=[f"Bullet {i}" for i in range(30)],
        )
        assert len(slide.bullets) == 30

    def test_over_limit_bullets_31(self) -> None:
        """31 bullets should raise a ValidationError."""
        with pytest.raises(ValidationError):
            SlideDefinition(
                slide_type=SlideType.CONTENT,
                title="Too Many",
                bullets=[f"Bullet {i}" for i in range(31)],
            )

    def test_generate_with_30_bullets(self, generator: DeckGenerator, tmp_path: Path) -> None:
        """Generating a slide with 30 bullets should succeed."""
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.CONTENT,
                title="Many Bullets",
                bullets=[f"Bullet {i}" for i in range(30)],
            ),
        ])
        path = generator.generate(definition, tmp_path / "bullets.pptx")
        assert path.exists()


class TestChartEdgeCases:
    """Test chart slides with edge-case data."""

    def test_chart_with_empty_series_list(self) -> None:
        """ChartData with an empty series list should be valid at the model level."""
        chart_data = ChartData(
            categories=["Q1", "Q2"],
            series=[],
        )
        assert len(chart_data.series) == 0

    def test_chart_with_single_data_point(self, generator: DeckGenerator, tmp_path: Path) -> None:
        """Chart with one category and one value."""
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.CHART,
                title="Single Point",
                elements=[
                    ChartElement(
                        chart_type=ChartType.BAR,
                        data=ChartData(
                            categories=["Only"],
                            series=[ChartDataSeries(name="Data", values=[42.0])],
                        ),
                    ),
                ],
            ),
        ])
        path = generator.generate(definition, tmp_path / "single_chart.pptx")
        assert path.exists()


class TestTableEdgeCases:
    """Test table slides with edge-case data."""

    def test_table_with_header_only(self, generator: DeckGenerator, tmp_path: Path) -> None:
        """Table with only a header row and no data rows."""
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.TABLE,
                title="Header Only Table",
                elements=[
                    TableElement(
                        rows=[
                            [TableCell(text="Col A"), TableCell(text="Col B")],
                        ],
                        header_row=True,
                    ),
                ],
            ),
        ])
        path = generator.generate(definition, tmp_path / "header_table.pptx")
        assert path.exists()


class TestTitleLengthBoundaries:
    """Test title field length constraints."""

    def test_title_at_limit_500(self) -> None:
        """Title at exactly 500 characters should be valid."""
        title = "A" * 500
        slide = SlideDefinition(slide_type=SlideType.CONTENT, title=title)
        assert len(slide.title) == 500

    def test_title_over_limit_501(self) -> None:
        """Title at 501 characters should fail validation."""
        title = "A" * 501
        with pytest.raises(ValidationError):
            SlideDefinition(slide_type=SlideType.CONTENT, title=title)


class TestWidthInchBoundaries:
    """Test width_inches constraints on PresentationDefinition."""

    def test_width_at_minimum_1(self) -> None:
        """width_inches=1.0 should be valid (ge=1.0)."""
        defn = _make_definition(
            [SlideDefinition(slide_type=SlideType.TITLE, title="Min")],
            width_inches=1.0,
        )
        assert defn.width_inches == 1.0

    def test_width_at_maximum_50(self) -> None:
        """width_inches=50.0 should be valid (le=50.0)."""
        defn = _make_definition(
            [SlideDefinition(slide_type=SlideType.TITLE, title="Max")],
            width_inches=50.0,
        )
        assert defn.width_inches == 50.0

    def test_width_below_minimum(self) -> None:
        """width_inches=0.5 should fail (ge=1.0)."""
        with pytest.raises(ValidationError):
            _make_definition(
                [SlideDefinition(slide_type=SlideType.TITLE, title="TooNarrow")],
                width_inches=0.5,
            )

    def test_width_above_maximum(self) -> None:
        """width_inches=51.0 should fail (le=50.0)."""
        with pytest.raises(ValidationError):
            _make_definition(
                [SlideDefinition(slide_type=SlideType.TITLE, title="TooWide")],
                width_inches=51.0,
            )


class TestBrandConfigPathInJson:
    """Test that brand_config_path in JSON is silently ignored."""

    def test_extra_field_ignored_by_model_validate(self) -> None:
        """PresentationDefinition should not error when extra fields are present via model_validate."""
        data = {
            "metadata": {"title": "Extra Fields Test"},
            "slides": [{"slide_type": "title", "title": "Test"}],
            "brand_config_path": "/some/path.yaml",
        }
        # Pydantic v2 model_validate with default config ignores extra fields
        defn = PresentationDefinition.model_validate(data)
        assert defn.metadata.title == "Extra Fields Test"
        assert len(defn.slides) == 1

    def test_extra_field_not_in_dump(self) -> None:
        """The brand_config_path should not appear in the model dump."""
        data = {
            "metadata": {"title": "Dump Test"},
            "slides": [{"slide_type": "title", "title": "Test"}],
            "brand_config_path": "/some/path.yaml",
        }
        defn = PresentationDefinition.model_validate(data)
        dumped = defn.model_dump()
        assert "brand_config_path" not in dumped
