"""Tests for the PPTX generation engine."""

from __future__ import annotations

import json
from pathlib import Path

import pytest
from pptx import Presentation

from pptx_mcp.brand.defaults import executive_default
from pptx_mcp.engine.generator import DeckGenerator
from pptx_mcp.engine.shapes import apply_gradient_fill, parse_hex_color
from pptx_mcp.models.slides import (
    AgendaItem,
    FunnelStage,
    IconItem,
    KpiCard,
    ProcessStep,
    SwotData,
    TeamMember,
    ChartData,
    ChartDataSeries,
    ChartElement,
    ChartType,
    ColumnContent,
    PresentationDefinition,
    PresentationMetadata,
    SlideDefinition,
    SlideType,
    TableCell,
    TableElement,
)


@pytest.fixture
def output_dir(tmp_path: Path) -> Path:
    return tmp_path


@pytest.fixture
def brand():
    return executive_default()


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


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


class TestBasicGeneration:
    def test_title_slide(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.TITLE,
                title="Test Title",
                subtitle="Test Subtitle",
            ),
        ])
        path = generator.generate(definition, output_dir / "title.pptx")
        assert path.exists()

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

    def test_content_slide_with_bullets(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.CONTENT,
                title="Key Points",
                bullets=["Point one", "Point two", "Point three"],
            ),
        ])
        path = generator.generate(definition, output_dir / "content.pptx")
        assert path.exists()

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

    def test_two_column_slide(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.TWO_COLUMN,
                title="Two Columns",
                columns=[
                    ColumnContent(title="Left", bullets=["A", "B"]),
                    ColumnContent(title="Right", bullets=["C", "D"]),
                ],
            ),
        ])
        path = generator.generate(definition, output_dir / "columns.pptx")
        assert path.exists()

    def test_pros_cons_slide(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.PROS_CONS,
                title="Pros and Cons",
                pros=["Fast", "Cheap"],
                cons=["Risky", "Complex"],
            ),
        ])
        path = generator.generate(definition, output_dir / "proscons.pptx")
        assert path.exists()

    def test_section_slide(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.SECTION,
                title="Section Break",
                subtitle="Transition slide",
            ),
        ])
        path = generator.generate(definition, output_dir / "section.pptx")
        assert path.exists()


class TestChartGeneration:
    def test_bar_chart(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.CHART,
                title="Revenue Chart",
                elements=[
                    ChartElement(
                        chart_type=ChartType.BAR,
                        data=ChartData(
                            categories=["Q1", "Q2", "Q3", "Q4"],
                            series=[
                                ChartDataSeries(name="Revenue", values=[100, 120, 115, 140]),
                            ],
                        ),
                        title="Revenue by Quarter",
                    ),
                ],
            ),
        ])
        path = generator.generate(definition, output_dir / "chart.pptx")
        assert path.exists()


class TestTableGeneration:
    def test_basic_table(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.TABLE,
                title="Data Table",
                elements=[
                    TableElement(
                        rows=[
                            [TableCell(text="Name"), TableCell(text="Value")],
                            [TableCell(text="Alpha"), TableCell(text="100")],
                            [TableCell(text="Beta"), TableCell(text="200")],
                        ],
                        header_row=True,
                    ),
                ],
            ),
        ])
        path = generator.generate(definition, output_dir / "table.pptx")
        assert path.exists()


class TestMultiSlide:
    def test_full_deck(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(slide_type=SlideType.TITLE, title="Full Deck", subtitle="Test"),
            SlideDefinition(
                slide_type=SlideType.CONTENT,
                title="Agenda",
                bullets=["Topic A", "Topic B"],
            ),
            SlideDefinition(slide_type=SlideType.SECTION, title="Details"),
            SlideDefinition(
                slide_type=SlideType.TWO_COLUMN,
                title="Comparison",
                columns=[
                    ColumnContent(title="Option A", bullets=["Fast", "Simple"]),
                    ColumnContent(title="Option B", bullets=["Robust", "Scalable"]),
                ],
            ),
            SlideDefinition(
                slide_type=SlideType.CONTENT,
                title="Summary",
                bullets=["Takeaway 1", "Takeaway 2"],
            ),
        ])
        path = generator.generate(definition, output_dir / "full.pptx")
        assert path.exists()

        prs = Presentation(str(path))
        assert len(prs.slides) == 5

    def test_speaker_notes(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.CONTENT,
                title="With Notes",
                bullets=["Point"],
                speaker_notes="These are the speaker notes.",
            ),
        ])
        path = generator.generate(definition, output_dir / "notes.pptx")
        prs = Presentation(str(path))
        slide = prs.slides[0]
        assert slide.has_notes_slide
        assert "speaker notes" in slide.notes_slide.notes_text_frame.text.lower()


class TestAdvancedSlideTypes:
    def test_quote_slide(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.QUOTE,
                body="The best way to predict the future is to create it.",
                quote_attribution="Peter Drucker",
                quote_attribution_role="Management Consultant",
            ),
        ])
        path = generator.generate(definition, output_dir / "quote.pptx")
        assert path.exists()
        prs = Presentation(str(path))
        assert len(prs.slides) == 1

    def test_big_number_slide(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.BIG_NUMBER,
                body="47%",
                title="Customer Satisfaction",
                subtitle="Q4 2025",
                bullets=["Up from 32% last quarter"],
            ),
        ])
        path = generator.generate(definition, output_dir / "big_number.pptx")
        assert path.exists()

    def test_process_slide(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.PROCESS,
                title="Implementation Plan",
                active_step=1,
                steps=[
                    ProcessStep(title="Discovery", description="Gather requirements", status="completed"),
                    ProcessStep(title="Design", description="Create architecture", status="active"),
                    ProcessStep(title="Build", description="Implement solution", status="future"),
                ],
            ),
        ])
        path = generator.generate(definition, output_dir / "process.pptx")
        assert path.exists()

    def test_timeline_slide(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.TIMELINE,
                title="Project Milestones",
                steps=[
                    ProcessStep(title="Kickoff", date="Jan 2025", status="completed"),
                    ProcessStep(title="Beta", date="Mar 2025", status="active"),
                    ProcessStep(title="GA", date="Jun 2025", status="future"),
                ],
            ),
        ])
        path = generator.generate(definition, output_dir / "timeline.pptx")
        assert path.exists()

    def test_step_cards_slide(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.STEP_CARDS,
                title="How It Works",
                steps=[
                    ProcessStep(title="Sign Up", description="Create your account"),
                    ProcessStep(title="Configure", description="Set preferences"),
                    ProcessStep(title="Launch", description="Go live"),
                ],
            ),
        ])
        path = generator.generate(definition, output_dir / "step_cards.pptx")
        assert path.exists()


class TestCompositionSlideTypes:
    def test_dashboard_slide(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.DASHBOARD,
                title="Q4 Metrics",
                kpis=[
                    KpiCard(value="$1.2M", label="Revenue", change="+15%", trend="up"),
                    KpiCard(value="92%", label="Retention", change="+3%", trend="up"),
                    KpiCard(value="67", label="NPS Score", change="-2", trend="down"),
                    KpiCard(value="4.2s", label="Avg Response", trend="neutral"),
                ],
            ),
        ])
        path = generator.generate(definition, output_dir / "dashboard.pptx")
        assert path.exists()
        prs = Presentation(str(path))
        assert len(prs.slides) == 1

    def test_image_text_slide(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.IMAGE_TEXT,
                title="Product Overview",
                body="Our next-gen platform for teams.",
                bullets=["Fast", "Scalable", "Secure"],
                image_position="left",
            ),
        ])
        path = generator.generate(definition, output_dir / "image_text.pptx")
        assert path.exists()

    def test_image_text_right(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.IMAGE_TEXT,
                title="Details",
                bullets=["Point A", "Point B"],
                image_position="right",
            ),
        ])
        path = generator.generate(definition, output_dir / "image_text_r.pptx")
        assert path.exists()

    def test_icon_grid_slide(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.ICON_GRID,
                title="Our Values",
                icons=[
                    IconItem(icon="\U0001F680", title="Speed", description="Ship fast"),
                    IconItem(icon="\U0001F91D", title="Trust", description="Be transparent"),
                    IconItem(icon="\U0001F4A1", title="Innovation", description="Think big"),
                    IconItem(icon="\U0001F4AA", title="Resilience", description="Embrace failure"),
                ],
            ),
        ])
        path = generator.generate(definition, output_dir / "icon_grid.pptx")
        assert path.exists()
        prs = Presentation(str(path))
        assert len(prs.slides) == 1


class TestGradientFill:
    def test_gradient_on_shape(self, output_dir: Path) -> None:
        from pptx import Presentation as PrsClass
        from pptx.util import Inches
        from pptx.oxml.ns import qn

        prs = PrsClass()
        slide = prs.slides.add_slide(prs.slide_layouts[6])  # blank
        shape = slide.shapes.add_shape(1, Inches(1), Inches(1), Inches(4), Inches(2))
        apply_gradient_fill(shape, ["#1B2A4A", "#3A5A8A"], angle=90.0)

        # Verify the gradient XML was created
        sp_pr = shape._element.find(qn("p:spPr"))
        grad = sp_pr.find(qn("a:gradFill"))
        assert grad is not None
        gs_lst = grad.find(qn("a:gsLst"))
        stops = gs_lst.findall(qn("a:gs"))
        assert len(stops) == 2
        assert stops[0].get("pos") == "0"
        assert stops[1].get("pos") == "100000"

    def test_gradient_three_colors(self) -> None:
        from pptx import Presentation as PrsClass
        from pptx.util import Inches
        from pptx.oxml.ns import qn

        prs = PrsClass()
        slide = prs.slides.add_slide(prs.slide_layouts[6])
        shape = slide.shapes.add_shape(1, Inches(1), Inches(1), Inches(4), Inches(2))
        apply_gradient_fill(shape, ["#FF0000", "#00FF00", "#0000FF"])

        sp_pr = shape._element.find(qn("p:spPr"))
        grad = sp_pr.find(qn("a:gradFill"))
        stops = grad.find(qn("a:gsLst")).findall(qn("a:gs"))
        assert len(stops) == 3
        assert stops[1].get("pos") == "50000"

    def test_gradient_requires_two_colors(self) -> None:
        from pptx import Presentation as PrsClass
        from pptx.util import Inches

        prs = PrsClass()
        slide = prs.slides.add_slide(prs.slide_layouts[6])
        shape = slide.shapes.add_shape(1, Inches(1), Inches(1), Inches(4), Inches(2))
        with pytest.raises(ValueError, match="at least 2 colors"):
            apply_gradient_fill(shape, ["#FF0000"])

    def test_gradient_custom_stops(self) -> None:
        from pptx import Presentation as PrsClass
        from pptx.util import Inches
        from pptx.oxml.ns import qn

        prs = PrsClass()
        slide = prs.slides.add_slide(prs.slide_layouts[6])
        shape = slide.shapes.add_shape(1, Inches(1), Inches(1), Inches(4), Inches(2))
        apply_gradient_fill(shape, ["#000000", "#FFFFFF"], stops=[0.0, 0.7])

        sp_pr = shape._element.find(qn("p:spPr"))
        stops = sp_pr.find(qn("a:gradFill")).find(qn("a:gsLst")).findall(qn("a:gs"))
        assert stops[1].get("pos") == "70000"


class TestMetadata:
    def test_core_properties(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = PresentationDefinition(
            metadata=PresentationMetadata(
                title="Metadata Test",
                author="Test Author",
                subject="Testing",
                category="Tests",
            ),
            slides=[
                SlideDefinition(slide_type=SlideType.TITLE, title="Metadata Test"),
            ],
        )
        path = generator.generate(definition, output_dir / "meta.pptx")
        prs = Presentation(str(path))
        assert prs.core_properties.title == "Metadata Test"
        assert prs.core_properties.author == "Test Author"


class TestWave1SlideTypes:
    """Tests for Wave 1 slide types: team, closing, swot, funnel, agenda."""

    def test_team_slide(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.TEAM,
                title="Leadership Team",
                team_members=[
                    TeamMember(name="Jane Smith", role="CEO", bio="20 years in SaaS"),
                    TeamMember(name="John Doe", role="CTO"),
                    TeamMember(name="Alice Chen", role="CFO", bio="IPO expert"),
                ],
            ),
        ])
        path = generator.generate(definition, output_dir / "team.pptx")
        assert path.exists()
        prs = Presentation(str(path))
        assert len(prs.slides) == 1

    def test_closing_slide(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.CLOSING,
                title="Thank You",
                subtitle="Questions? Let's connect.",
                cta_text="Schedule a Demo",
                contact_info=["hello@company.com", "+1 (555) 123-4567"],
            ),
        ])
        path = generator.generate(definition, output_dir / "closing.pptx")
        assert path.exists()
        prs = Presentation(str(path))
        assert len(prs.slides) == 1

    def test_swot_slide(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.SWOT,
                title="SWOT Analysis",
                swot=SwotData(
                    strengths=["Strong brand", "Loyal customers"],
                    weaknesses=["High costs", "Limited reach"],
                    opportunities=["New markets", "Partnerships"],
                    threats=["Competition", "Regulation"],
                ),
            ),
        ])
        path = generator.generate(definition, output_dir / "swot.pptx")
        assert path.exists()
        prs = Presentation(str(path))
        assert len(prs.slides) == 1

    def test_funnel_slide(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.FUNNEL,
                title="Sales Pipeline",
                funnel_stages=[
                    FunnelStage(label="Prospects", value="10,000", description="Website visitors"),
                    FunnelStage(label="Leads", value="2,500"),
                    FunnelStage(label="Qualified", value="800"),
                    FunnelStage(label="Closed", value="120"),
                ],
            ),
        ])
        path = generator.generate(definition, output_dir / "funnel.pptx")
        assert path.exists()
        prs = Presentation(str(path))
        assert len(prs.slides) == 1

    def test_agenda_slide(self, generator: DeckGenerator, output_dir: Path) -> None:
        definition = _make_definition([
            SlideDefinition(
                slide_type=SlideType.AGENDA,
                title="Today's Agenda",
                active_agenda_item=1,
                agenda_items=[
                    AgendaItem(title="Opening Remarks", status="completed"),
                    AgendaItem(title="Q4 Review", description="Revenue and forecast", status="active"),
                    AgendaItem(title="Strategy", description="Growth plan", status="upcoming"),
                    AgendaItem(title="Q&A", status="upcoming"),
                ],
            ),
        ])
        path = generator.generate(definition, output_dir / "agenda.pptx")
        assert path.exists()
        prs = Presentation(str(path))
        assert len(prs.slides) == 1
