"""Tests for Phase 2 editing power tools — delete, copy, find/replace, batch, ungroup, reorder."""

from __future__ import annotations

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

from pptx_mcp.engine.editor import (
    batch_modify,
    copy_element,
    delete_element,
    find_and_replace,
    list_elements,
    reorder_slides,
    ungroup_elements,
)
from pptx_mcp.models.editor import (
    BatchModification,
    ElementLocator,
    ElementModification,
    FindReplaceResult,
)


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


def _make_prs_with_textboxes(texts: list[str]) -> Presentation:
    """Create a Presentation with one slide containing textboxes."""
    prs = Presentation()
    slide = prs.slides.add_slide(prs.slide_layouts[6])
    for i, text in enumerate(texts):
        txBox = slide.shapes.add_textbox(
            Inches(1.0 + i * 2), Inches(1.0), Inches(1.5), Inches(0.5),
        )
        txBox.text_frame.paragraphs[0].text = text
        txBox.name = f"TextBox_{i}"
    return prs


def _make_prs_multi_slide(slide_count: int = 3) -> Presentation:
    """Create a Presentation with multiple slides, each having a textbox."""
    prs = Presentation()
    for i in range(slide_count):
        slide = prs.slides.add_slide(prs.slide_layouts[6])
        txBox = slide.shapes.add_textbox(
            Inches(1.0), Inches(1.0), Inches(3.0), Inches(0.5),
        )
        txBox.text_frame.paragraphs[0].text = f"Slide {i} content"
        txBox.name = f"Slide{i}_Text"
    return prs


# ---------------------------------------------------------------------------
# delete_element
# ---------------------------------------------------------------------------


class TestDeleteElement:
    def test_delete_by_index(self) -> None:
        prs = _make_prs_with_textboxes(["A", "B", "C"])
        slide = prs.slides[0]
        assert len(list(slide.shapes)) == 3
        delete_element(slide, ElementLocator(index=1))
        assert len(list(slide.shapes)) == 2

    def test_delete_by_name(self) -> None:
        prs = _make_prs_with_textboxes(["A", "B"])
        slide = prs.slides[0]
        delete_element(slide, ElementLocator(name="TextBox_0"))
        shapes = list(slide.shapes)
        assert len(shapes) == 1
        assert shapes[0].name == "TextBox_1"

    def test_delete_by_text_match(self) -> None:
        prs = _make_prs_with_textboxes(["Hello World", "Goodbye"])
        slide = prs.slides[0]
        delete_element(slide, ElementLocator(text_match="Goodbye"))
        shapes = list(slide.shapes)
        assert len(shapes) == 1

    def test_delete_out_of_range_raises(self) -> None:
        prs = _make_prs_with_textboxes(["A"])
        slide = prs.slides[0]
        with pytest.raises(ValueError, match="out of range"):
            delete_element(slide, ElementLocator(index=5))


# ---------------------------------------------------------------------------
# copy_element
# ---------------------------------------------------------------------------


class TestCopyElement:
    def test_copy_same_slide(self) -> None:
        prs = _make_prs_with_textboxes(["Original"])
        slide = prs.slides[0]
        initial_count = len(list(slide.shapes))
        result = copy_element(prs, 0, ElementLocator(index=0), 0)
        assert len(list(slide.shapes)) == initial_count + 1
        assert result.index == initial_count

    def test_copy_cross_slide(self) -> None:
        prs = _make_prs_multi_slide(2)
        slide_0_count = len(list(prs.slides[0].shapes))
        slide_1_count = len(list(prs.slides[1].shapes))
        result = copy_element(prs, 0, ElementLocator(index=0), 1)
        assert len(list(prs.slides[0].shapes)) == slide_0_count  # source unchanged
        assert len(list(prs.slides[1].shapes)) == slide_1_count + 1

    def test_copy_with_offset(self) -> None:
        prs = _make_prs_with_textboxes(["Original"])
        result = copy_element(prs, 0, ElementLocator(index=0), 0, offset_x=1.0, offset_y=1.0)
        assert result is not None

    def test_copy_invalid_slide_raises(self) -> None:
        prs = _make_prs_with_textboxes(["A"])
        with pytest.raises(ValueError, match="out of range"):
            copy_element(prs, 0, ElementLocator(index=0), 99)


# ---------------------------------------------------------------------------
# find_and_replace
# ---------------------------------------------------------------------------


class TestFindAndReplace:
    def test_basic_replace(self) -> None:
        prs = _make_prs_with_textboxes(["Hello World", "Hello Again"])
        results = find_and_replace(prs, "Hello", "Hi")
        assert len(results) == 2
        assert all(r.replacements >= 1 for r in results)

    def test_case_insensitive(self) -> None:
        prs = _make_prs_with_textboxes(["HELLO world", "hello World"])
        results = find_and_replace(prs, "hello", "Hi", case_sensitive=False)
        assert len(results) == 2

    def test_specific_slides(self) -> None:
        prs = _make_prs_multi_slide(3)
        results = find_and_replace(prs, "content", "text", slide_indices=[0, 2])
        assert all(r.slide_index in (0, 2) for r in results)
        assert len(results) == 2

    def test_no_match(self) -> None:
        prs = _make_prs_with_textboxes(["Hello"])
        results = find_and_replace(prs, "xyz_not_found", "replacement")
        assert len(results) == 0

    def test_result_model(self) -> None:
        prs = _make_prs_with_textboxes(["Replace me"])
        results = find_and_replace(prs, "me", "you")
        assert len(results) == 1
        r = results[0]
        assert isinstance(r, FindReplaceResult)
        assert r.replacements == 1
        assert r.slide_index == 0


# ---------------------------------------------------------------------------
# batch_modify
# ---------------------------------------------------------------------------


class TestBatchModify:
    def test_multiple_modifications(self) -> None:
        prs = _make_prs_with_textboxes(["A", "B"])
        slide = prs.slides[0]
        mods = [
            BatchModification(
                locator=ElementLocator(index=0),
                modification=ElementModification(text="A_modified"),
            ),
            BatchModification(
                locator=ElementLocator(index=1),
                modification=ElementModification(text="B_modified"),
            ),
        ]
        results = batch_modify(slide, mods)
        assert len(results) == 2

    def test_empty_list(self) -> None:
        prs = _make_prs_with_textboxes(["A"])
        slide = prs.slides[0]
        results = batch_modify(slide, [])
        assert results == []


# ---------------------------------------------------------------------------
# ungroup_elements
# ---------------------------------------------------------------------------


class TestUngroupElements:
    def test_ungroup_basic(self) -> None:
        prs = _make_prs_with_textboxes(["A", "B"])
        slide = prs.slides[0]
        # Group two shapes first
        from pptx_mcp.engine.editor import group_elements
        group_info = group_elements(slide, [
            ElementLocator(index=0),
            ElementLocator(index=1),
        ])
        # Now ungroup
        results = ungroup_elements(slide, ElementLocator(name=group_info.name))
        assert len(results) >= 2

    def test_ungroup_non_group_raises(self) -> None:
        prs = _make_prs_with_textboxes(["A"])
        slide = prs.slides[0]
        with pytest.raises(ValueError, match="not a group"):
            ungroup_elements(slide, ElementLocator(index=0))


# ---------------------------------------------------------------------------
# reorder_slides
# ---------------------------------------------------------------------------


class TestReorderSlides:
    def test_basic_reorder(self) -> None:
        prs = _make_prs_multi_slide(3)
        # Original: slide 0, 1, 2. Reverse to: 2, 1, 0
        reorder_slides(prs, [2, 1, 0])
        # Verify by checking textbox content
        assert prs.slides[0].shapes[0].text_frame.text == "Slide 2 content"
        assert prs.slides[1].shapes[0].text_frame.text == "Slide 1 content"
        assert prs.slides[2].shapes[0].text_frame.text == "Slide 0 content"

    def test_identity_reorder(self) -> None:
        prs = _make_prs_multi_slide(3)
        reorder_slides(prs, [0, 1, 2])
        assert prs.slides[0].shapes[0].text_frame.text == "Slide 0 content"

    def test_invalid_order_raises(self) -> None:
        prs = _make_prs_multi_slide(3)
        with pytest.raises(ValueError, match="permutation"):
            reorder_slides(prs, [0, 1])  # incomplete

    def test_invalid_order_duplicates_raises(self) -> None:
        prs = _make_prs_multi_slide(3)
        with pytest.raises(ValueError, match="permutation"):
            reorder_slides(prs, [0, 0, 0])
