import pytest
from app.parsers.markdown_parser import MarkdownParser


@pytest.fixture
def parser():
    return MarkdownParser()


def test_parse_intro_and_headings(parser):
    content = "Preamble text.\n\n# Overview\nSome overview.\n\n## Details\nMore details."
    sections = parser.parse_sections(content)
    assert sections[0] == ("Introduction", 0, "Preamble text.")
    assert sections[1][0] == "Overview"
    assert sections[1][1] == 1
    assert sections[2][0] == "Details"
    assert sections[2][1] == 2


def test_parse_no_headings(parser):
    content = "Just plain text with no headings."
    sections = parser.parse_sections(content)
    assert len(sections) == 1
    assert sections[0][0] == "Introduction"


def test_parse_empty(parser):
    sections = parser.parse_sections("")
    assert sections == []


def test_parse_heading_only(parser):
    content = "# Title\n"
    sections = parser.parse_sections(content)
    assert sections[0][0] == "Title"
    assert sections[0][2] == ""


def test_parse_returns_tokens_for_structured_traversal(parser):
    result = parser.parse("# Title\nSome body")
    assert result.sections[0] == ("Title", 1, "Some body")
    assert any(token["type"] == "heading_open" for token in result.tokens)


def test_parse_normalizes_bom_and_newlines(parser):
    result = parser.parse("\ufeff# Title\r\nBody\r\n")
    assert result.normalized_content == "# Title\nBody\n"
    assert result.sections[0] == ("Title", 1, "Body")


def test_parse_invalid_input_type_raises(parser):
    with pytest.raises(ValueError):
        parser.parse(None)


def test_parse_markdownish_api_doc_without_hash_headings(parser):
    content = (
        "API\n"
        "Current Endpoints\n"
        "Method\tPath\tDescription\n"
        "GET\t/\tService status\n"
        "GET\t/healthz\tLiveness probe\n"
        "Future Endpoints\n"
        "Method\tPath\tDescription\n"
        "POST\t/webhooks/bitbucket\tReceive Bitbucket push events\n"
        "Environment Variables\n"
        "Variable\tDefault\tDescription\n"
        "HOST\t0.0.0.0\tBind host\n"
    )

    sections = parser.parse_sections(content)
    titles = [title for title, _, _ in sections]

    assert "API" in titles
    assert "Current Endpoints" in titles
    assert "Future Endpoints" in titles
    assert "Environment Variables" in titles


def test_parse_markdownish_architecture_doc_without_hash_headings(parser):
    content = (
        "Architecture\n"
        "Purpose\n"
        "Repo Sync Service owns repository acquisition and snapshot lifecycle.\n"
        "Boundaries\n"
        "REST control plane for webhooks, admin sync, and health/status\n"
        "Flow\n"
        "Bitbucket Webhook / Admin Sync\n"
        "Storage layers\n"
        "Layer\tTechnology\tData\n"
        "Git workspace\tFilesystem\tCloned repositories\n"
    )

    sections = parser.parse_sections(content)
    titles = [title for title, _, _ in sections]

    assert "Architecture" in titles
    assert "Purpose" in titles
    assert "Boundaries" in titles
    assert "Flow" in titles
    assert "Storage layers" in titles


def test_extracts_heading_hierarchy_metadata(parser):
    content = "# Architecture\nTop\n## Purpose\nDetails\n### Scope\nInner\n## Flow\nSteps"
    result = parser.parse(content)

    metadata = result.section_metadata
    assert metadata[0]["section_title"] == "Architecture"
    assert metadata[0]["section_level"] == 1
    assert metadata[0]["parent_section"] is None
    assert metadata[0]["heading_path"] == "Architecture"

    assert metadata[1]["section_title"] == "Purpose"
    assert metadata[1]["section_level"] == 2
    assert metadata[1]["parent_section"] == "Architecture"
    assert metadata[1]["heading_path"] == "Architecture / Purpose"

    assert metadata[2]["section_title"] == "Scope"
    assert metadata[2]["section_level"] == 3
    assert metadata[2]["parent_section"] == "Purpose"
    assert metadata[2]["heading_path"] == "Architecture / Purpose / Scope"

    assert metadata[3]["section_title"] == "Flow"
    assert metadata[3]["section_level"] == 2
    assert metadata[3]["parent_section"] == "Architecture"
    assert metadata[3]["heading_path"] == "Architecture / Flow"


def test_heading_metadata_has_ordered_line_spans(parser):
    content = "# A\nA text\n## B\nB text\n# C\nC text"
    result = parser.parse(content)

    metadata = result.section_metadata
    assert metadata[0]["section_title"] == "A"
    assert metadata[0]["start_line"] == 1
    assert metadata[0]["end_line"] == 2

    assert metadata[1]["section_title"] == "B"
    assert metadata[1]["start_line"] == 3
    assert metadata[1]["end_line"] == 4

    assert metadata[2]["section_title"] == "C"
    assert metadata[2]["start_line"] == 5
    assert metadata[2]["end_line"] == 6


def test_section_segmentation_associates_block_types(parser):
    content = (
        "# API\n"
        "Paragraph text.\n"
        "- Item one\n"
        "> quoted line\n"
        "```python\n"
        "print('ok')\n"
        "```\n"
        "## Endpoints\n"
        "| Method | Path |\n"
        "| --- | --- |\n"
        "| GET | /healthz |\n"
    )
    result = parser.parse(content)

    api_meta = result.section_metadata[0]
    endpoints_meta = result.section_metadata[1]

    assert api_meta["section_title"] == "API"
    assert "paragraph" in api_meta["content_types"]
    assert "list" in api_meta["content_types"]
    assert "blockquote" in api_meta["content_types"]
    assert "code_block" in api_meta["content_types"]

    assert endpoints_meta["section_title"] == "Endpoints"
    assert "table" in endpoints_meta["content_types"]


def test_section_segments_include_content_boundaries(parser):
    content = "# Architecture\nOverview\n## Boundaries\nDetails\n"
    result = parser.parse(content)

    segments = result.section_segments
    assert segments[0]["section_title"] == "Architecture"
    assert segments[0]["content_start_line"] == 2
    assert segments[0]["content_end_line"] == 2
    assert segments[1]["section_title"] == "Boundaries"
    assert segments[1]["content_start_line"] == 4
    assert segments[1]["content_end_line"] == 4


def test_extracts_link_metadata(parser):
    content = (
        "# Links\n"
        "See [Internal Guide](docs/guide.md), "
        "[Website](https://example.com), and [Section](#overview)."
    )

    result = parser.parse(content)

    assert {link["link_type"] for link in result.links} == {"internal", "external", "anchor"}
    assert any(link["text"] == "Internal Guide" and link["target"] == "docs/guide.md" for link in result.links)
    assert any(link["text"] == "Website" and link["target"] == "https://example.com" for link in result.links)
    assert any(link["text"] == "Section" and link["target"] == "#overview" for link in result.links)


def test_extracts_code_blocks_and_inline_code(parser):
    content = (
        "# Code\n"
        "Use `kubectl get pods` to inspect cluster state.\n"
        "```bash\n"
        "kubectl apply -f deploy.yaml\n"
        "```\n"
    )

    result = parser.parse(content)

    assert len(result.inline_code) == 1
    assert result.inline_code[0]["content"] == "kubectl get pods"

    assert len(result.code_blocks) == 1
    assert result.code_blocks[0]["language"] == "bash"
    assert "kubectl apply -f deploy.yaml" in result.code_blocks[0]["content"]


def test_extracts_tables_with_normalized_text(parser):
    content = (
        "# Table\n"
        "| Method | Path |\n"
        "| --- | --- |\n"
        "| GET | /healthz |\n"
        "| POST | /sync |\n"
    )

    result = parser.parse(content)

    assert len(result.tables) == 1
    assert result.tables[0]["headers"] == ["Method", "Path"]
    assert result.tables[0]["rows"] == [["GET", "/healthz"], ["POST", "/sync"]]
    assert "Columns: Method | Path" in result.tables[0]["normalized_text"]
    assert "Row 1: GET | /healthz" in result.tables[0]["normalized_text"]


def test_malformed_markdown_does_not_break_structured_extraction(parser):
    content = "# Broken\n[missing link(https://example.com\n| A | B |\n| - | - |\n| 1 | 2\n"

    result = parser.parse(content)

    assert isinstance(result.links, list)
    assert isinstance(result.code_blocks, list)
    assert isinstance(result.inline_code, list)
    assert isinstance(result.tables, list)


def test_normalization_removes_excessive_noise(parser):
    content = "# Title\r\n\r\n\r\nBody\tline\n_____\n_____\n\n\nNext"
    result = parser.parse(content)

    assert "\r" not in result.normalized_content
    assert "\n\n\n" not in result.normalized_content
    assert "Body    line" in result.normalized_content
    assert result.normalized_content.count("---") == 1


def test_normalization_preserves_code_tables_and_links(parser):
    content = (
        "# API\n"
        "\n"
        "Refer [Guide](docs/guide.md).\n"
        "\n"
        "```python\n"
        "if x:\n"
        "    print('ok')\n"
        "____\n"
        "```\n"
        "\n"
        "| Name | Value |\n"
        "| --- | --- |\n"
        "| A | 1 |\n"
    )

    result = parser.parse(content)

    assert any(link["target"] == "docs/guide.md" for link in result.links)
    assert len(result.code_blocks) == 1
    assert "____" in result.code_blocks[0]["content"]
    assert len(result.tables) == 1
