"""Tests for markdown conversion utilities."""

import pytest

from zaira.mdconv import (
    markdown_to_storage,
    markdown_to_jira_wiki,
    jira_wiki_to_markdown,
    is_jira_wiki,
    storage_to_markdown,
    extract_local_images,
    convert_images_to_attachments,
    convert_attachments_to_images,
)
from tests.jira_wiki_samples import (
    ALL_SAMPLES,
    HEADINGS,
    TEXT_EFFECTS,
    COLOR,
    TEXT_BREAKS,
    LINKS,
    IMAGES,
    BULLET_LISTS,
    NUMBERED_LISTS,
    MIXED_LISTS,
    BLOCKQUOTES,
    TABLES,
    CODE_BLOCKS,
    NOFORMAT,
    PANELS,
    FULL_DOCUMENT,
)


class TestExtractLocalImages:
    """Tests for extract_local_images function."""

    def test_extracts_local_images(self):
        """Extracts local image references."""
        md = "![Alt text](./images/photo.png)\n![Another](path/to/image.jpg)"
        result = extract_local_images(md)

        assert len(result) == 2
        assert ("Alt text", "./images/photo.png") in result
        assert ("Another", "path/to/image.jpg") in result

    def test_skips_http_urls(self):
        """Skips HTTP/HTTPS URLs."""
        md = "![Web](https://example.com/image.png)\n![Local](./local.png)"
        result = extract_local_images(md)

        assert len(result) == 1
        assert ("Local", "./local.png") in result

    def test_skips_protocol_relative_urls(self):
        """Skips protocol-relative URLs."""
        md = "![Image](//cdn.example.com/img.png)"
        result = extract_local_images(md)

        assert len(result) == 0

    def test_handles_empty_alt_text(self):
        """Handles images with empty alt text."""
        md = "![](image.png)"
        result = extract_local_images(md)

        assert result == [("", "image.png")]

    def test_no_images(self):
        """Returns empty list when no images."""
        md = "Just text, no images"
        result = extract_local_images(md)

        assert result == []


class TestConvertImagesToAttachments:
    """Tests for convert_images_to_attachments function."""

    def test_converts_local_images(self):
        """Converts local image paths to attachment references."""
        md = "![Alt](./images/photo.png)"
        result = convert_images_to_attachments(md)

        assert result == "![Alt](attachment:photo.png)"

    def test_preserves_urls(self):
        """Preserves HTTP/HTTPS URLs."""
        md = "![Web](https://example.com/image.png)"
        result = convert_images_to_attachments(md)

        assert result == md

    def test_preserves_protocol_relative_urls(self):
        """Preserves protocol-relative URLs."""
        md = "![Img](//cdn.example.com/img.png)"
        result = convert_images_to_attachments(md)

        assert result == md

    def test_handles_nested_paths(self):
        """Extracts just filename from nested paths."""
        md = "![Img](path/to/deep/image.png)"
        result = convert_images_to_attachments(md)

        assert result == "![Img](attachment:image.png)"


class TestConvertAttachmentsToImages:
    """Tests for convert_attachments_to_images function."""

    def test_converts_attachment_references(self):
        """Converts attachment references to local paths."""
        md = "![Alt](attachment:photo.png)"
        result = convert_attachments_to_images(md)

        assert result == "![Alt](./images/photo.png)"

    def test_custom_image_dir(self):
        """Uses custom image directory."""
        md = "![Alt](attachment:photo.png)"
        result = convert_attachments_to_images(md, image_dir="./assets")

        assert result == "![Alt](./assets/photo.png)"

    def test_preserves_non_attachment_refs(self):
        """Preserves non-attachment image references."""
        md = "![Alt](./local/image.png)"
        result = convert_attachments_to_images(md)

        assert result == md

    def test_preserves_urls(self):
        """Preserves HTTP URLs."""
        md = "![Web](https://example.com/img.png)"
        result = convert_attachments_to_images(md)

        assert result == md


class TestMarkdownToStorage:
    """Tests for markdown_to_storage function."""

    def test_headers(self):
        md = "# H1\n## H2\n### H3"
        html = markdown_to_storage(md)
        assert "<h1>H1</h1>" in html
        assert "<h2>H2</h2>" in html
        assert "<h3>H3</h3>" in html

    def test_bold_italic(self):
        md = "This is **bold** and *italic*"
        html = markdown_to_storage(md)
        assert "<strong>bold</strong>" in html
        assert "<em>italic</em>" in html

    def test_links(self):
        md = "[link text](https://example.com)"
        html = markdown_to_storage(md)
        assert '<a href="https://example.com">link text</a>' in html

    def test_code_block_with_language(self):
        md = "```python\nprint('hello')\n```"
        html = markdown_to_storage(md)
        assert '<ac:structured-macro ac:name="code">' in html
        assert '<ac:parameter ac:name="language">python</ac:parameter>' in html
        assert "print('hello')" in html

    def test_code_block_language_mapping(self):
        """Test that language aliases are mapped correctly."""
        test_cases = [
            ("js", "javascript"),
            ("ts", "typescript"),
            ("py", "python"),
            ("sh", "bash"),
            ("yml", "yaml"),
            ("csharp", "c#"),
        ]
        for md_lang, confluence_lang in test_cases:
            md = f"```{md_lang}\ncode\n```"
            html = markdown_to_storage(md)
            assert (
                f'<ac:parameter ac:name="language">{confluence_lang}</ac:parameter>'
                in html
            )

    def test_code_block_without_language(self):
        md = "```\nplain code\n```"
        html = markdown_to_storage(md)
        assert '<ac:parameter ac:name="language">none</ac:parameter>' in html

    def test_code_block_html_entities(self):
        """Test that HTML in code blocks is preserved."""
        md = '```html\n<div class="foo">text</div>\n```'
        html = markdown_to_storage(md)
        assert '<div class="foo">text</div>' in html

    def test_toc(self):
        md = "# Title\n\n[TOC]\n\n## Section"
        html = markdown_to_storage(md)
        assert '<ac:structured-macro ac:name="toc"/>' in html
        assert "[TOC]" not in html

    def test_unordered_list(self):
        md = "- Item 1\n- Item 2"
        html = markdown_to_storage(md)
        assert "<ul>" in html
        assert "<li>Item 1</li>" in html
        assert "<li>Item 2</li>" in html

    def test_ordered_list(self):
        md = "1. First\n2. Second"
        html = markdown_to_storage(md)
        assert "<ol>" in html
        assert "<li>First</li>" in html

    def test_nested_list_2_space_indent(self):
        """Test that 2-space indented nested lists work."""
        md = "- Item 1\n  - Nested A\n  - Nested B\n- Item 2"
        html = markdown_to_storage(md)
        # Should have nested ul
        assert html.count("<ul>") == 2
        assert html.count("</ul>") == 2

    def test_nested_list_deep(self):
        """Test deeply nested lists with 2-space indent."""
        md = "- Item 1\n  - Nested A\n    - Deep\n  - Nested B\n- Item 2"
        html = markdown_to_storage(md)
        assert html.count("<ul>") == 3  # Top level + 2 nested levels

    def test_ordered_list_continuation(self):
        """Test sane_lists extension preserves list start number."""
        md = "1. First\n2. Second\n\nParagraph\n\n3. Third\n4. Fourth"
        html = markdown_to_storage(md)
        assert 'start="3"' in html

    def test_table(self):
        md = "| A | B |\n|---|---|\n| 1 | 2 |"
        html = markdown_to_storage(md)
        assert "<table>" in html
        assert "<th>A</th>" in html
        assert "<td>1</td>" in html


class TestStorageToMarkdown:
    """Tests for storage_to_markdown function."""

    def test_headers(self):
        html = "<h1>Title</h1><h2>Section</h2>"
        md = storage_to_markdown(html)
        assert "# Title" in md
        assert "## Section" in md

    def test_bold_italic(self):
        html = "<p>This is <strong>bold</strong> and <em>italic</em></p>"
        md = storage_to_markdown(html)
        assert "**bold**" in md
        assert "*italic*" in md

    def test_links(self):
        html = '<a href="https://example.com">link</a>'
        md = storage_to_markdown(html)
        assert "[link](https://example.com)" in md

    def test_code_macro(self):
        html = (
            '<ac:structured-macro ac:name="code">'
            '<ac:parameter ac:name="language">python</ac:parameter>'
            '<ac:plain-text-body><![CDATA[print("hello")]]></ac:plain-text-body>'
            "</ac:structured-macro>"
        )
        md = storage_to_markdown(html)
        assert "```python" in md
        assert 'print("hello")' in md
        assert "```" in md

    def test_code_macro_language_mapping(self):
        """Test reverse language mapping."""
        html = (
            '<ac:structured-macro ac:name="code">'
            '<ac:parameter ac:name="language">html/xml</ac:parameter>'
            "<ac:plain-text-body><![CDATA[<div></div>]]></ac:plain-text-body>"
            "</ac:structured-macro>"
        )
        md = storage_to_markdown(html)
        assert "```xml" in md

    def test_code_macro_none_language(self):
        html = (
            '<ac:structured-macro ac:name="code">'
            '<ac:parameter ac:name="language">none</ac:parameter>'
            "<ac:plain-text-body><![CDATA[code]]></ac:plain-text-body>"
            "</ac:structured-macro>"
        )
        md = storage_to_markdown(html)
        # Should have empty language (just ```)
        assert "```\n" in md

    def test_toc_macro(self):
        html = '<ac:structured-macro ac:name="toc"/>'
        md = storage_to_markdown(html)
        assert "[TOC]" in md

    def test_unordered_list(self):
        html = "<ul><li>Item 1</li><li>Item 2</li></ul>"
        md = storage_to_markdown(html)
        assert "- Item 1" in md
        assert "- Item 2" in md

    def test_ordered_list(self):
        html = "<ol><li>First</li><li>Second</li></ol>"
        md = storage_to_markdown(html)
        assert "1. First" in md
        assert "2. Second" in md

    def test_ordered_list_start(self):
        html = '<ol start="3"><li>Third</li><li>Fourth</li></ol>'
        md = storage_to_markdown(html)
        assert "3. Third" in md
        assert "4. Fourth" in md

    def test_nested_list(self):
        html = "<ul><li>Item 1<ul><li>Nested</li></ul></li><li>Item 2</li></ul>"
        md = storage_to_markdown(html)
        assert "- Item 1" in md
        assert "  - Nested" in md
        assert "- Item 2" in md

    def test_table(self):
        html = (
            "<table><thead><tr><th>A</th><th>B</th></tr></thead>"
            "<tbody><tr><td>1</td><td>2</td></tr></tbody></table>"
        )
        md = storage_to_markdown(html)
        assert "| A | B |" in md
        assert "|---|---|" in md
        assert "| 1 | 2 |" in md


class TestMarkdownToJiraWiki:
    """Tests for markdown_to_jira_wiki function."""

    def test_headers(self):
        assert markdown_to_jira_wiki("## Section") == "h2. Section"
        assert markdown_to_jira_wiki("### Sub") == "h3. Sub"

    def test_h1_not_converted(self):
        """Single # is Jira numbered list — not converted to h1."""
        assert markdown_to_jira_wiki("# Title") == "# Title"

    def test_bold(self):
        result = markdown_to_jira_wiki("This is **bold** text")
        assert result == "This is *bold* text"

    def test_italic_single_star_unchanged(self):
        """Single *text* is valid Jira bold — left as-is to avoid mangling."""
        result = markdown_to_jira_wiki("This is *italic* text")
        assert result == "This is *italic* text"

    def test_bold_double_underscore(self):
        result = markdown_to_jira_wiki("This is __bold__ text")
        assert result == "This is *bold* text"

    def test_inline_code(self):
        result = markdown_to_jira_wiki("Use `print()` here")
        assert result == "Use {{print()}} here"

    def test_link(self):
        result = markdown_to_jira_wiki("[click here](https://example.com)")
        assert result == "[click here|https://example.com]"

    def test_image(self):
        result = markdown_to_jira_wiki("![alt text](image.png)")
        assert result == "!image.png!"

    def test_image_before_link(self):
        """Images are processed before links to avoid double-conversion."""
        result = markdown_to_jira_wiki("![alt](img.png) and [link](url)")
        assert result == "!img.png! and [link|url]"

    def test_fenced_code_block(self):
        md = "```python\nprint('hello')\n```"
        result = markdown_to_jira_wiki(md)
        assert result == "{code:language=python}\nprint('hello')\n{code}"

    def test_fenced_code_block_no_lang(self):
        md = "```\nplain code\n```"
        result = markdown_to_jira_wiki(md)
        assert result == "{code}\nplain code\n{code}"

    def test_fenced_code_block_lang_alias(self):
        md = "```py\ncode\n```"
        result = markdown_to_jira_wiki(md)
        assert "{code:language=python}" in result

    def test_bullet_list(self):
        md = "- Item 1\n- Item 2"
        result = markdown_to_jira_wiki(md)
        assert result == "* Item 1\n* Item 2"

    def test_nested_bullet_list(self):
        md = "- Item 1\n  - Nested\n- Item 2"
        result = markdown_to_jira_wiki(md)
        assert "* Item 1" in result
        assert "** Nested" in result
        assert "* Item 2" in result

    def test_numbered_list(self):
        md = "1. First\n2. Second"
        result = markdown_to_jira_wiki(md)
        assert result == "# First\n# Second"

    def test_blockquote(self):
        result = markdown_to_jira_wiki("> This is a quote")
        assert result == "bq. This is a quote"

    def test_horizontal_rule(self):
        assert markdown_to_jira_wiki("---") == "----"
        assert markdown_to_jira_wiki("***") == "----"

    def test_strikethrough(self):
        result = markdown_to_jira_wiki("~~deleted~~")
        assert result == "-deleted-"

    def test_jira_wiki_passthrough(self):
        """Jira wiki syntax passes through unchanged."""
        jira = "h2. Heading\n\n*bold* and _italic_\n\n[link|https://example.com]"
        result = markdown_to_jira_wiki(jira)
        # h2. heading line: no markdown headers, so _convert_inline_md processes it
        assert "h2. Heading" in result
        assert "[link|https://example.com]" in result

    def test_full_document(self):
        md = "## Overview\n\nThis is **important** and uses [the API](https://api.example.com).\n\n- Step 1\n- Step 2\n\n```python\ncode()\n```"
        result = markdown_to_jira_wiki(md)
        assert "h2. Overview" in result
        assert "*important*" in result
        assert "[the API|https://api.example.com]" in result
        assert "* Step 1" in result
        assert "* Step 2" in result
        assert "{code:language=python}" in result
        assert "code()" in result
        assert "{code}" in result


class TestMarkdownToJiraWikiSymmetry:
    """Verify that markdown_to_jira_wiki output is stable.

    After conversion, detect_markdown(result) must be False — meaning the
    output is valid Jira wiki that won't be re-converted if passed through
    the pipeline again.
    """

    CASES = [
        "## Heading",
        "# Title",
        "**bold text**",
        "*italic text*",
        "__also bold__",
        "[click here](https://example.com)",
        "![alt](image.png)",
        "```python\nprint('hi')\n```",
        "```\nplain code\n```",
        "`inline code`",
        "- bullet one\n- bullet two",
        "  - nested bullet",
        "1. first\n2. second",
        "> blockquote text",
        "~~strikethrough~~",
        "## Heading\n\n**bold** and *italic* with [a link](https://x.com).\n\n- item\n\n```py\ncode()\n```",
    ]

    def test_converted_output_not_detected_as_markdown(self):
        """Each converted result must not trigger detect_markdown."""
        from zaira.create import detect_markdown

        for md in self.CASES:
            result = markdown_to_jira_wiki(md)
            assert not detect_markdown(result), (
                f"Output still detected as markdown after conversion.\n"
                f"  Input:  {md!r}\n"
                f"  Output: {result!r}"
            )

    def test_idempotent_under_pipeline(self):
        """Converting already-converted Jira wiki does not change it further."""
        from zaira.create import detect_markdown

        for md in self.CASES:
            first = markdown_to_jira_wiki(md)
            # Simulate passing through the pipeline a second time:
            # detect_markdown guards the call in practice, but call directly
            # to verify the output is stable.
            if not detect_markdown(first):
                second = markdown_to_jira_wiki(first)
                assert second == first, (
                    f"Output changed on second pass.\n"
                    f"  Input:   {md!r}\n"
                    f"  Pass 1:  {first!r}\n"
                    f"  Pass 2:  {second!r}"
                )


class TestRoundTrip:
    """Test that markdown survives round-trip conversion."""

    def test_simple_document(self):
        md = "# Title\n\nParagraph with **bold**."
        storage = markdown_to_storage(md)
        back = storage_to_markdown(storage)
        storage2 = markdown_to_storage(back)
        assert storage == storage2

    def test_code_blocks(self):
        md = "```python\ndef foo():\n    pass\n```"
        storage = markdown_to_storage(md)
        back = storage_to_markdown(storage)
        storage2 = markdown_to_storage(back)
        assert storage == storage2

    def test_nested_lists(self):
        md = "- Item 1\n  - Nested A\n  - Nested B\n- Item 2"
        storage = markdown_to_storage(md)
        back = storage_to_markdown(storage)
        storage2 = markdown_to_storage(back)
        assert storage == storage2

    def test_nested_lists_deep(self):
        md = (
            "- First item\n"
            "- Second item\n"
            "- Third item\n"
            "  - Nested item\n"
            "  - Another nested item\n"
            "    - Deep nested item\n"
            "- Fourth item"
        )
        storage = markdown_to_storage(md)
        back = storage_to_markdown(storage)
        # Verify nesting is preserved
        assert "  - Nested item" in back
        assert "  - Another nested item" in back
        assert "    - Deep nested item" in back
        # Verify round-trip stability
        storage2 = markdown_to_storage(back)
        assert storage == storage2

    def test_ordered_lists(self):
        md = "1. First\n  1. Sub 1\n  2. Sub 2\n2. Second"
        storage = markdown_to_storage(md)
        back = storage_to_markdown(storage)
        storage2 = markdown_to_storage(back)
        assert storage == storage2

    def test_table(self):
        md = "| A | B |\n|---|---|\n| 1 | 2 |"
        storage = markdown_to_storage(md)
        back = storage_to_markdown(storage)
        storage2 = markdown_to_storage(back)
        assert storage == storage2

    def test_toc(self):
        md = "# Doc\n\n[TOC]\n\n## Section"
        storage = markdown_to_storage(md)
        back = storage_to_markdown(storage)
        storage2 = markdown_to_storage(back)
        assert storage == storage2

    def test_comprehensive_document(self):
        md = """# Document

[TOC]

## Introduction

This is **bold** and *italic* with a [link](https://example.com).

## Code

```python
def hello():
    print("hello")
```

## Lists

- Item 1
  - Nested
- Item 2

1. First
2. Second

## Table

| Col1 | Col2 |
|------|------|
| A    | B    |
"""
        storage = markdown_to_storage(md)
        back = storage_to_markdown(storage)
        storage2 = markdown_to_storage(back)
        assert storage == storage2


class TestJiraWikiToMarkdown:
    """Tests for jira_wiki_to_markdown function."""

    def test_headers(self):
        assert jira_wiki_to_markdown("h2. Section") == "## Section"
        assert jira_wiki_to_markdown("h3. Sub") == "### Sub"
        assert jira_wiki_to_markdown("h1. Title") == "# Title"

    def test_bold(self):
        result = jira_wiki_to_markdown("This is *bold* text")
        assert result == "This is **bold** text"

    def test_italic(self):
        result = jira_wiki_to_markdown("This is _italic_ text")
        assert result == "This is *italic* text"

    def test_strikethrough(self):
        result = jira_wiki_to_markdown("This is -deleted- text")
        assert result == "This is ~~deleted~~ text"

    def test_inline_code(self):
        result = jira_wiki_to_markdown("Use {{print()}} here")
        assert result == "Use `print()` here"

    def test_link(self):
        result = jira_wiki_to_markdown("[click here|https://example.com]")
        assert result == "[click here](https://example.com)"

    def test_image(self):
        result = jira_wiki_to_markdown("!image.png!")
        assert result == "![](image.png)"

    def test_code_block_with_language(self):
        wiki = "{code:language=python}\nprint('hello')\n{code}"
        result = jira_wiki_to_markdown(wiki)
        assert result == "```python\nprint('hello')\n```"

    def test_code_block_no_language(self):
        wiki = "{code}\nplain code\n{code}"
        result = jira_wiki_to_markdown(wiki)
        assert result == "```\nplain code\n```"

    def test_code_block_short_language(self):
        wiki = "{code:python}\ncode\n{code}"
        result = jira_wiki_to_markdown(wiki)
        assert result == "```python\ncode\n```"

    def test_bullet_list(self):
        wiki = "* Item 1\n* Item 2"
        result = jira_wiki_to_markdown(wiki)
        assert result == "- Item 1\n- Item 2"

    def test_nested_bullet_list(self):
        wiki = "* Item 1\n** Nested\n* Item 2"
        result = jira_wiki_to_markdown(wiki)
        assert "- Item 1" in result
        assert "  - Nested" in result
        assert "- Item 2" in result

    def test_numbered_list(self):
        wiki = "# First\n# Second"
        result = jira_wiki_to_markdown(wiki)
        assert result == "1. First\n1. Second"

    def test_nested_numbered_list(self):
        wiki = "# First\n## Nested\n# Second"
        result = jira_wiki_to_markdown(wiki)
        assert "1. First" in result
        assert "  1. Nested" in result
        assert "1. Second" in result

    def test_blockquote(self):
        result = jira_wiki_to_markdown("bq. This is a quote")
        assert result == "> This is a quote"

    def test_horizontal_rule(self):
        assert jira_wiki_to_markdown("----") == "---"

    def test_table(self):
        wiki = "||Name||Age||\n|Alice|30|\n|Bob|25|"
        result = jira_wiki_to_markdown(wiki)
        assert "| Name | Age |" in result
        assert "|---|---|" in result
        assert "| Alice | 30 |" in result
        assert "| Bob | 25 |" in result

    def test_full_document(self):
        wiki = (
            "h2. Overview\n\n"
            "This is *important* and uses [the API|https://api.example.com].\n\n"
            "* Step 1\n* Step 2\n\n"
            "{code:language=python}\ncode()\n{code}"
        )
        result = jira_wiki_to_markdown(wiki)
        assert "## Overview" in result
        assert "**important**" in result
        assert "[the API](https://api.example.com)" in result
        assert "- Step 1" in result
        assert "- Step 2" in result
        assert "```python" in result
        assert "code()" in result
        assert "```" in result

    def test_strikethrough_not_in_words(self):
        """Hyphens inside words should not be treated as strikethrough."""
        result = jira_wiki_to_markdown("well-known")
        assert result == "well-known"

    def test_noformat_block(self):
        wiki = "{noformat}\nsome preformatted text\n{noformat}"
        result = jira_wiki_to_markdown(wiki)
        assert result == "```\nsome preformatted text\n```"

    def test_noformat_inline_start(self):
        """Noformat starting on same line as content."""
        wiki = "{noformat}var x = 1;\nvar y = 2;\n{noformat}"
        result = jira_wiki_to_markdown(wiki)
        assert "```" in result
        assert "var x = 1;" in result
        assert "var y = 2;" in result

    def test_noformat_single_line(self):
        """Noformat opening and closing on same line."""
        wiki = "{noformat}some code{noformat}"
        result = jira_wiki_to_markdown(wiki)
        assert result == "`some code`"

    def test_plain_text_unchanged(self):
        result = jira_wiki_to_markdown("Just plain text here.")
        assert result == "Just plain text here."


class TestIsJiraWiki:
    """Tests for is_jira_wiki detection function."""

    def test_detects_headers(self):
        assert is_jira_wiki("h2. Section title")

    def test_detects_code_blocks(self):
        assert is_jira_wiki("{code:language=python}\nprint()\n{code}")

    def test_detects_table_headers(self):
        assert is_jira_wiki("||Name||Age||")

    def test_detects_blockquote(self):
        assert is_jira_wiki("bq. Some quote")

    def test_detects_inline_code(self):
        assert is_jira_wiki("Use {{print()}} here")

    def test_detects_bold(self):
        assert is_jira_wiki("This is *bold* text")

    def test_detects_noformat(self):
        assert is_jira_wiki("{noformat}some text{noformat}")

    def test_detects_links(self):
        assert is_jira_wiki("[click here|https://example.com]")

    def test_plain_text_not_detected(self):
        assert not is_jira_wiki("Just plain text.")

    def test_markdown_not_detected(self):
        assert not is_jira_wiki("## Heading\n\n**bold** and [link](url)")

    def test_empty_string(self):
        assert not is_jira_wiki("")


class TestJiraWikiRoundTrip:
    """Test that markdown_to_jira_wiki output fed to jira_wiki_to_markdown
    produces equivalent markdown."""

    CASES = [
        ("## Heading", "## Heading"),
        ("### Sub heading", "### Sub heading"),
        ("This is **bold** text", "This is **bold** text"),
        ("`inline code`", "`inline code`"),
        ("[click here](https://example.com)", "[click here](https://example.com)"),
        ("- bullet one\n- bullet two", "- bullet one\n- bullet two"),
        ("- outer\n  - nested", "- outer\n  - nested"),
        ("1. first\n2. second", "1. first\n1. second"),
        ("> blockquote text", "> blockquote text"),
        ("~~strikethrough~~", "~~strikethrough~~"),
        ("---", "---"),
        ("```python\ncode()\n```", "```python\ncode()\n```"),
        ("```\nplain\n```", "```\nplain\n```"),
    ]

    def test_round_trip(self):
        for md_input, expected in self.CASES:
            jira = markdown_to_jira_wiki(md_input)
            back = jira_wiki_to_markdown(jira)
            assert back == expected, (
                f"Round-trip failed.\n"
                f"  Input:    {md_input!r}\n"
                f"  Jira:     {jira!r}\n"
                f"  Back:     {back!r}\n"
                f"  Expected: {expected!r}"
            )


class TestJiraWikiToMarkdownRoundTrip:
    """Test that jira_wiki_to_markdown output fed to markdown_to_jira_wiki
    produces equivalent Jira wiki."""

    CASES = [
        # (wiki input, expected after round-trip)
        ("h2. Heading", "h2. Heading"),
        ("h3. Sub heading", "h3. Sub heading"),
        ("This is *bold* text", "This is *bold* text"),
        ("{{inline_code}}", "{{inline_code}}"),
        ("[click here|https://example.com]", "[click here|https://example.com]"),
        ("* bullet one\n* bullet two", "* bullet one\n* bullet two"),
        ("* outer\n** nested", "* outer\n** nested"),
        ("# first\n# second", "# first\n# second"),
        ("bq. blockquote text", "bq. blockquote text"),
        ("-deleted-", "-deleted-"),
        ("----", "----"),
        (
            "{code:language=python}\ncode()\n{code}",
            "{code:language=python}\ncode()\n{code}",
        ),
        ("{code}\nplain\n{code}", "{code}\nplain\n{code}"),
        # Tables: markdown_to_jira_wiki passes markdown tables through,
        # so the round-trip stabilizes on markdown table format
        ("||Name||Age||\n|Alice|30|", "| Name | Age |\n|---|---|\n| Alice | 30 |"),
    ]

    def test_round_trip(self):
        for wiki_input, expected in self.CASES:
            md = jira_wiki_to_markdown(wiki_input)
            back = markdown_to_jira_wiki(md)
            assert back == expected, (
                f"Round-trip failed.\n"
                f"  Input:    {wiki_input!r}\n"
                f"  Markdown: {md!r}\n"
                f"  Back:     {back!r}\n"
                f"  Expected: {expected!r}"
            )

    def test_real_ticket_description(self):
        """Round-trip a realistic Jira wiki description like AC-1538."""
        wiki = (
            "h2. Summary\n\n"
            "Update Dynatrace OneAgent version.\n\n"
            "h2. Version Changes\n\n"
            "||*Platform*||*Old Version*||*New Version*||\n"
            "|Linux|1.291.165|1.299.73|\n"
            "|Windows|1.291.107|1.299.73|\n\n"
            "h2. Implementation\n\n"
            "Repository: {{alusta-aws-node-prereq}}\n\n"
            "*Linux files:*\n\n"
            "* {{3rd_party_urls_linux.txt}}\n"
            "* {{Installers/install-dynatrace-agent.sh}}\n\n"
            "h2. Acceptance Criteria\n\n"
            "* Agent version is upgraded\n"
            "* Data is passed to server"
        )
        md = jira_wiki_to_markdown(wiki)
        # Verify key conversions happened
        assert "## Summary" in md
        assert "## Version Changes" in md
        assert "| **Platform** | **Old Version** | **New Version** |" in md
        assert "|---|---|---|" in md
        assert "`alusta-aws-node-prereq`" in md
        assert "`3rd_party_urls_linux.txt`" in md
        assert "- Agent version is upgraded" in md
        # Round-trip back
        back = markdown_to_jira_wiki(md)
        assert "h2. Summary" in back
        assert "h2. Version Changes" in back
        assert "{{alusta-aws-node-prereq}}" in back
        assert "{{3rd_party_urls_linux.txt}}" in back
        assert "* Agent version is upgraded" in back


class TestAtlassianWikiSamples:
    """Tests based on Atlassian's official Text Formatting Notation reference.

    Source: https://jira.atlassian.com/secure/WikiRendererHelpAction.jspa
    """

    @pytest.mark.parametrize(
        "wiki,expected", HEADINGS, ids=[f"h{i + 1}" for i in range(len(HEADINGS))]
    )
    def test_headings(self, wiki, expected):
        assert jira_wiki_to_markdown(wiki) == expected

    @pytest.mark.parametrize(
        "wiki,expected",
        TEXT_EFFECTS,
        ids=[w[:30].replace(" ", "_") for w, _ in TEXT_EFFECTS],
    )
    def test_text_effects(self, wiki, expected):
        assert jira_wiki_to_markdown(wiki) == expected

    @pytest.mark.parametrize("wiki,expected", COLOR, ids=[w[:30] for w, _ in COLOR])
    def test_color(self, wiki, expected):
        assert jira_wiki_to_markdown(wiki) == expected

    @pytest.mark.parametrize(
        "wiki,expected", TEXT_BREAKS, ids=[repr(w)[:30] for w, _ in TEXT_BREAKS]
    )
    def test_text_breaks(self, wiki, expected):
        assert jira_wiki_to_markdown(wiki) == expected

    @pytest.mark.parametrize("wiki,expected", LINKS, ids=[w[:40] for w, _ in LINKS])
    def test_links(self, wiki, expected):
        assert jira_wiki_to_markdown(wiki) == expected

    @pytest.mark.parametrize("wiki,expected", IMAGES, ids=[w[:40] for w, _ in IMAGES])
    def test_images(self, wiki, expected):
        assert jira_wiki_to_markdown(wiki) == expected

    @pytest.mark.parametrize(
        "wiki,expected",
        BULLET_LISTS,
        ids=[f"bullet_{i}" for i in range(len(BULLET_LISTS))],
    )
    def test_bullet_lists(self, wiki, expected):
        assert jira_wiki_to_markdown(wiki) == expected

    @pytest.mark.parametrize(
        "wiki,expected",
        NUMBERED_LISTS,
        ids=[f"numbered_{i}" for i in range(len(NUMBERED_LISTS))],
    )
    def test_numbered_lists(self, wiki, expected):
        assert jira_wiki_to_markdown(wiki) == expected

    @pytest.mark.parametrize(
        "wiki,expected",
        MIXED_LISTS,
        ids=[f"mixed_{i}" for i in range(len(MIXED_LISTS))],
    )
    def test_mixed_lists(self, wiki, expected):
        assert jira_wiki_to_markdown(wiki) == expected

    @pytest.mark.parametrize(
        "wiki,expected", BLOCKQUOTES, ids=[f"bq_{i}" for i in range(len(BLOCKQUOTES))]
    )
    def test_blockquotes(self, wiki, expected):
        assert jira_wiki_to_markdown(wiki) == expected

    @pytest.mark.parametrize(
        "wiki,expected", TABLES, ids=[f"table_{i}" for i in range(len(TABLES))]
    )
    def test_tables(self, wiki, expected):
        assert jira_wiki_to_markdown(wiki) == expected

    @pytest.mark.parametrize(
        "wiki,expected", CODE_BLOCKS, ids=[f"code_{i}" for i in range(len(CODE_BLOCKS))]
    )
    def test_code_blocks(self, wiki, expected):
        assert jira_wiki_to_markdown(wiki) == expected

    @pytest.mark.parametrize(
        "wiki,expected", NOFORMAT, ids=[f"noformat_{i}" for i in range(len(NOFORMAT))]
    )
    def test_noformat(self, wiki, expected):
        assert jira_wiki_to_markdown(wiki) == expected

    @pytest.mark.parametrize(
        "wiki,expected", PANELS, ids=[f"panel_{i}" for i in range(len(PANELS))]
    )
    def test_panels(self, wiki, expected):
        assert jira_wiki_to_markdown(wiki) == expected

    def test_full_document(self):
        wiki_input, expected = FULL_DOCUMENT
        assert jira_wiki_to_markdown(wiki_input) == expected


class TestMixedWikiAndMarkdown:
    """Tests for mixed Confluence wiki markup and markdown syntax."""

    def test_wiki_heading_with_markdown(self):
        """Wiki h1. heading is converted alongside markdown."""
        md = "Regular markdown\n\nh1. Wiki Heading\n\nMore *markdown*"
        result = markdown_to_storage(md)

        assert "<h1>Wiki Heading</h1>" in result
        assert "<p>Regular markdown</p>" in result
        # When wiki syntax is detected, *text* is treated as wiki bold (not markdown italic)
        assert "<strong>markdown</strong>" in result

    def test_wiki_blockquote_with_markdown(self):
        """Wiki bq. blockquote works with markdown."""
        md = "Start\n\nbq. Wiki quote\n\nEnd with _italic_"
        result = markdown_to_storage(md)

        assert "<blockquote>" in result
        assert "Wiki quote" in result
        # Wiki markup uses _italic_ for italics
        assert "<em>italic</em>" in result

    def test_wiki_code_block_with_markdown(self):
        """Wiki {code} block alongside markdown code."""
        md = "Markdown code:\n\n```python\ncode1\n```\n\nWiki code:\n\n{code:language=java}\ncode2\n{code}"
        result = markdown_to_storage(md)

        # Code blocks are converted to code macros
        assert 'ac:name="code"' in result
        assert "code1" in result
        assert "code2" in result


class TestRawXMLPassthrough:
    """Tests for raw Confluence storage XML passthrough."""

    def test_raw_macro_xml_passthrough(self):
        """Raw structured macro XML is preserved."""
        md = """Content

<ac:structured-macro ac:name="status">
  <ac:parameter ac:name="colour">green</ac:parameter>
</ac:structured-macro>

More content"""
        result = markdown_to_storage(md)

        assert 'ac:name="status"' in result
        assert "colour" in result

    def test_raw_html_elements_passthrough(self):
        """Raw HTML elements are passed through."""
        md = "Text\n\n<blockquote><p>Raw quote</p></blockquote>\n\nMore"
        result = markdown_to_storage(md)

        assert "<blockquote>" in result
        assert "Raw quote" in result

    def test_raw_image_macro_passthrough(self):
        """Raw image macro XML is preserved."""
        md = '<ac:image><ri:attachment ri:filename="test.png"/></ac:image>'
        result = markdown_to_storage(md)

        assert "ac:image" in result
        assert "test.png" in result


class TestConfluenceMacros:
    """Tests for Confluence macro handling."""

    def test_page_tree_macro_markdown_to_storage(self):
        """Converts page-tree macro syntax to structured macro XML."""
        md = "Documentation\n\n{page-tree:root=@self}"
        result = markdown_to_storage(md)

        assert '<ac:structured-macro ac:name="page-tree">' in result
        assert '<ac:parameter ac:name="root">@self</ac:parameter>' in result

    def test_toc_macro_markdown_to_storage(self):
        """Converts toc macro without params."""
        md = "Content\n\n{toc}"
        result = markdown_to_storage(md)

        assert '<ac:structured-macro ac:name="toc">' in result

    def test_macro_with_multiple_params(self):
        """Converts macros with multiple parameters."""
        md = "{info:title=Note|icon=true}"
        result = markdown_to_storage(md)

        assert '<ac:structured-macro ac:name="info">' in result
        assert 'ac:name="title"' in result
        assert 'ac:name="icon"' in result

    def test_macro_in_paragraph(self):
        """Preserves macro that appears in its own paragraph."""
        md = "Some text\n\n{page-tree:root=@self}\n\nMore text"
        result = markdown_to_storage(md)

        assert "<ac:structured-macro" in result
        assert "<p>Some text</p>" in result
        assert "<p>More text</p>" in result

    def test_storage_to_markdown_page_tree(self):
        """Converts storage format back to macro markdown."""
        storage = (
            "<p>Documentation</p>"
            '<ac:structured-macro ac:name="page-tree">'
            '<ac:parameter ac:name="root">@self</ac:parameter>'
            "</ac:structured-macro>"
        )
        result = storage_to_markdown(storage)

        assert "{page-tree:root=@self}" in result
        assert "Documentation" in result

    def test_round_trip_page_tree(self):
        """Round-trip: markdown -> storage -> markdown preserves macro."""
        original_md = "Page listing\n\n{page-tree:root=@self}"

        # Markdown -> storage
        storage = markdown_to_storage(original_md)
        assert '<ac:structured-macro ac:name="page-tree">' in storage

        # Storage -> markdown
        result_md = storage_to_markdown(storage)
        assert "{page-tree:root=@self}" in result_md

        # Back to storage
        storage_again = markdown_to_storage(result_md)
        assert '<ac:structured-macro ac:name="page-tree">' in storage_again
