import sys
import types

if "soundfile" not in sys.modules:
    soundfile_stub = types.ModuleType("soundfile")

    class _SoundFileStub:  # pragma: no cover - placeholder to satisfy imports
        def __init__(self, *args: object, **kwargs: object) -> None:
            raise RuntimeError("soundfile is not installed in the test environment")

    soundfile_stub.SoundFile = _SoundFileStub  # type: ignore[attr-defined]
    sys.modules["soundfile"] = soundfile_stub

if "static_ffmpeg" not in sys.modules:
    sys.modules["static_ffmpeg"] = types.ModuleType("static_ffmpeg")

if "ebooklib" not in sys.modules:
    ebooklib_stub = types.ModuleType("ebooklib")
    ebooklib_epub_stub = types.ModuleType("ebooklib.epub")
    ebooklib_stub.epub = ebooklib_epub_stub  # type: ignore[attr-defined]
    sys.modules["ebooklib"] = ebooklib_stub
    sys.modules["ebooklib.epub"] = ebooklib_epub_stub

if "fitz" not in sys.modules:
    sys.modules["fitz"] = types.ModuleType("fitz")

if "markdown" not in sys.modules:
    markdown_stub = types.ModuleType("markdown")

    class _MarkdownStub:
        def __init__(self, *args: object, **kwargs: object) -> None:
            self.toc_tokens = []

        def convert(self, text: str) -> str:
            return text

    markdown_stub.Markdown = _MarkdownStub  # type: ignore[attr-defined]
    sys.modules["markdown"] = markdown_stub

if "bs4" not in sys.modules:
    bs4_stub = types.ModuleType("bs4")

    class _BeautifulSoupStub:
        def __init__(self, *args: object, **kwargs: object) -> None:
            self._text = ""

        def find(self, *args: object, **kwargs: object) -> None:
            return None

        def get_text(self) -> str:
            return self._text

        def decompose(self) -> None:  # pragma: no cover - compatibility shim
            return None

    class _NavigableStringStub(str):
        pass

    bs4_stub.BeautifulSoup = _BeautifulSoupStub  # type: ignore[attr-defined]
    bs4_stub.NavigableString = _NavigableStringStub  # type: ignore[attr-defined]
    sys.modules["bs4"] = bs4_stub


from abogen.webui.conversion_runner import (
    _format_spoken_chapter_title,
    _headings_equivalent,
    _normalize_chapter_opening_caps,
    _strip_duplicate_heading_line,
)


def test_format_spoken_chapter_title_adds_prefix() -> None:
    assert _format_spoken_chapter_title("1: A Tale", 1, True) == "Chapter 1. A Tale"


def test_format_spoken_chapter_title_respects_existing_prefix() -> None:
    assert (
        _format_spoken_chapter_title("Chapter 2: Story", 2, True) == "Chapter 2: Story"
    )


def test_format_spoken_chapter_title_handles_empty_title() -> None:
    assert _format_spoken_chapter_title("", 4, True) == "Chapter 4"


def test_format_spoken_chapter_title_trims_delimiters() -> None:
    assert (
        _format_spoken_chapter_title("7 - Into the Wild", 7, True)
        == "Chapter 7. Into the Wild"
    )


def test_headings_equivalent_ignores_case_and_prefix() -> None:
    assert _headings_equivalent("1: The House", "Chapter 1: The House")


def test_strip_duplicate_heading_line_removes_first_match() -> None:
    text, removed = _strip_duplicate_heading_line(
        "Chapter 3: Intro\nBody text", "Chapter 3: Intro"
    )
    assert removed is True
    assert text.strip() == "Body text"


def test_normalize_chapter_opening_caps_basic_title() -> None:
    normalized, changed = _normalize_chapter_opening_caps("ALL CAPS TITLE")
    assert normalized == "All Caps Title"
    assert changed is True


def test_normalize_chapter_opening_caps_respects_acronyms() -> None:
    normalized, changed = _normalize_chapter_opening_caps("NASA MISSION LOG")
    assert normalized == "NASA Mission Log"
    assert changed is True


def test_normalize_chapter_opening_caps_handles_roman_numerals() -> None:
    normalized, changed = _normalize_chapter_opening_caps("IV. THE RETURN")
    assert normalized == "IV. The Return"
    assert changed is True


def test_normalize_chapter_opening_caps_keeps_mixed_case() -> None:
    normalized, changed = _normalize_chapter_opening_caps("Already Mixed Case")
    assert normalized == "Already Mixed Case"
    assert changed is False


class TestApplyChapterTextTransforms:
    """Tests for the combined heading-strip + opening-caps helper."""

    def test_both_enabled_heading_matches(self) -> None:
        from abogen.domain.chapter_titles import apply_chapter_text_transforms

        text, heading_removed, caps_changed = apply_chapter_text_transforms(
            "Chapter 1: The Beginning\nBody text here",
            heading_text="Chapter 1: The Beginning",
            raw_title="Chapter 1: The Beginning",
            strip_heading=True,
            normalize_caps=True,
        )
        assert heading_removed is True
        assert "Body text here" in text
        assert "Chapter 1" not in text

    def test_heading_fallback_to_number(self) -> None:
        from abogen.domain.chapter_titles import apply_chapter_text_transforms

        text, heading_removed, caps_changed = apply_chapter_text_transforms(
            "1. The Beginning\nBody text",
            heading_text="Chapter 1: The Beginning",
            raw_title="1: The Beginning",
            strip_heading=True,
            normalize_caps=False,
        )
        assert heading_removed is True
        assert "Body text" in text

    def test_only_heading_strip(self) -> None:
        from abogen.domain.chapter_titles import apply_chapter_text_transforms

        text, heading_removed, caps_changed = apply_chapter_text_transforms(
            "Chapter 1: Title\nBody text",
            heading_text="Chapter 1: Title",
            raw_title="",
            strip_heading=True,
            normalize_caps=False,
        )
        assert heading_removed is True
        assert caps_changed is False

    def test_only_opening_caps(self) -> None:
        from abogen.domain.chapter_titles import apply_chapter_text_transforms

        text, heading_removed, caps_changed = apply_chapter_text_transforms(
            "ALL CAPS START OF CHAPTER",
            heading_text="Chapter 1",
            raw_title="",
            strip_heading=False,
            normalize_caps=True,
        )
        assert heading_removed is False
        assert caps_changed is True
        assert text == "All Caps Start Of Chapter"

    def test_both_disabled_no_change(self) -> None:
        from abogen.domain.chapter_titles import apply_chapter_text_transforms

        original = "Some text here"
        text, heading_removed, caps_changed = apply_chapter_text_transforms(
            original,
            heading_text="Chapter 1",
            raw_title="",
            strip_heading=False,
            normalize_caps=False,
        )
        assert text == original
        assert heading_removed is False
        assert caps_changed is False

    def test_heading_not_matching(self) -> None:
        from abogen.domain.chapter_titles import apply_chapter_text_transforms

        text, heading_removed, caps_changed = apply_chapter_text_transforms(
            "Completely different text",
            heading_text="Chapter 1: Title",
            raw_title="",
            strip_heading=True,
            normalize_caps=False,
        )
        assert heading_removed is False
        assert text == "Completely different text"

    def test_empty_text(self) -> None:
        from abogen.domain.chapter_titles import apply_chapter_text_transforms

        text, heading_removed, caps_changed = apply_chapter_text_transforms(
            "",
            heading_text="Chapter 1",
            raw_title="",
            strip_heading=True,
            normalize_caps=True,
        )
        assert text == ""
        assert heading_removed is False
        assert caps_changed is False

    def test_both_enabled_text_only_has_caps(self) -> None:
        from abogen.domain.chapter_titles import apply_chapter_text_transforms

        text, heading_removed, caps_changed = apply_chapter_text_transforms(
            "NASA MISSION LOG",
            heading_text="Chapter 1",
            raw_title="",
            strip_heading=True,
            normalize_caps=True,
        )
        assert heading_removed is False
        assert caps_changed is True
        assert text == "NASA Mission Log"
