#!/usr/bin/env python3
"""Tests for the MkDocs -> Discourse markdown converter.

Run: python3 docs_sp/tools/test_converter.py
"""

import sys
from pathlib import Path

# Ensure the tools directory is importable
sys.path.insert(0, str(Path(__file__).parent))

from converter import (
    clean_blank_lines,
    convert,
    convert_admonitions,
    convert_emoji_shortcodes,
    convert_grid_cards,
    convert_tabs,
    resolve_internal_links,
    strip_front_matter,
)

# ---------------------------------------------------------------------------
# 1. Strip YAML Front Matter
# ---------------------------------------------------------------------------


def test_strip_front_matter_basic():
    input_text = """---
title: My Page
description: A test page
---

# Hello
"""
    expected = """# Hello
"""
    result = strip_front_matter(input_text)
    assert result == expected, f"FAIL:\n{result!r}\n!=\n{expected!r}"
    print("  PASS: strip_front_matter_basic")


def test_strip_front_matter_absent():
    input_text = "# Hello\n\nContent here.\n"
    result = strip_front_matter(input_text)
    assert result == input_text, f"FAIL:\n{result!r}\n!=\n{input_text!r}"
    print("  PASS: strip_front_matter_absent")


# ---------------------------------------------------------------------------
# 2. Convert Admonitions
# ---------------------------------------------------------------------------


def test_basic_warning():
    input_text = """!!! warning "Important"
    sunnypilot is a **driver assistance** system.
    Always pay attention.
"""
    expected = """> [!WARNING] Important
> sunnypilot is a **driver assistance** system.
> Always pay attention.
"""
    result = convert_admonitions(input_text)
    assert result == expected, f"FAIL basic_warning:\n{result!r}\n!=\n{expected!r}"
    print("  PASS: basic_warning")


def test_info_no_title():
    input_text = """!!! info
    Content line 1
    Content line 2
"""
    expected = """> [!INFO]
> Content line 1
> Content line 2
"""
    result = convert_admonitions(input_text)
    assert result == expected, f"FAIL info_no_title:\n{result!r}\n!=\n{expected!r}"
    print("  PASS: info_no_title")


def test_info_with_title():
    input_text = """!!! info "Requirements"
    - Longitudinal control must be available
    - ICBM must be enabled
"""
    expected = """> [!INFO] Requirements
> - Longitudinal control must be available
> - ICBM must be enabled
"""
    result = convert_admonitions(input_text)
    assert result == expected, f"FAIL info_with_title:\n{result!r}\n!=\n{expected!r}"
    print("  PASS: info_with_title")


def test_danger():
    input_text = """!!! danger "Important"
    sunnypilot is a **driver assistance** system. It is **NOT** a self-driving system.
"""
    expected = """> [!DANGER] Important
> sunnypilot is a **driver assistance** system. It is **NOT** a self-driving system.
"""
    result = convert_admonitions(input_text)
    assert result == expected, f"FAIL danger:\n{result!r}\n!=\n{expected!r}"
    print("  PASS: danger")


def test_tip():
    input_text = """!!! tip
    The more detail you provide, the faster we can diagnose and fix the issue.
"""
    expected = """> [!TIP]
> The more detail you provide, the faster we can diagnose and fix the issue.
"""
    result = convert_admonitions(input_text)
    assert result == expected, f"FAIL tip:\n{result!r}\n!=\n{expected!r}"
    print("  PASS: tip")


def test_multiline_with_blank():
    input_text = """!!! warning
    Line 1

    Line 2 after blank
"""
    expected = """> [!WARNING]
> Line 1
>
> Line 2 after blank
"""
    result = convert_admonitions(input_text)
    assert result == expected, f"FAIL multiline_with_blank:\n{result!r}\n!=\n{expected!r}"
    print("  PASS: multiline_with_blank")


def test_collapsible():
    input_text = """??? warning "Click to see"
    Hidden content
"""
    expected = """> [!WARNING] Click to see
> Hidden content
"""
    result = convert_admonitions(input_text)
    assert result == expected, f"FAIL collapsible:\n{result!r}\n!=\n{expected!r}"
    print("  PASS: collapsible")


def test_collapsible_open():
    input_text = """???+ info "Open by default"
    Visible content
"""
    expected = """> [!INFO] Open by default
> Visible content
"""
    result = convert_admonitions(input_text)
    assert result == expected, f"FAIL collapsible_open:\n{result!r}\n!=\n{expected!r}"
    print("  PASS: collapsible_open")


def test_surrounded_by_content():
    input_text = """Some text before.

!!! note "Note Title"
    Note content here.

Some text after.
"""
    expected = """Some text before.

> [!NOTE] Note Title
> Note content here.

Some text after.
"""
    result = convert_admonitions(input_text)
    assert result == expected, f"FAIL surrounded:\n{result!r}\n!=\n{expected!r}"
    print("  PASS: surrounded_by_content")


def test_multiple_admonitions():
    input_text = """!!! info "Requirements"
    - Req 1
    - Req 2

!!! warning "Vehicle Restrictions"
    - Tesla: disabled on release
    - Rivian: always disabled
"""
    expected = """> [!INFO] Requirements
> - Req 1
> - Req 2

> [!WARNING] Vehicle Restrictions
> - Tesla: disabled on release
> - Rivian: always disabled
"""
    result = convert_admonitions(input_text)
    assert result == expected, f"FAIL multiple:\n{result!r}\n!=\n{expected!r}"
    print("  PASS: multiple_admonitions")


def test_real_doc_snippet():
    """Test with an actual snippet from docs_sp content."""
    input_text = """## Speed Limit Mode

| Property | Value |
|----------|-------|
| **Param** | `SpeedLimitMode` |
| **Type** | Multi-button selector |

!!! info "Requirements"
    - Longitudinal control must be available, **or** ICBM must be enabled

!!! warning "Vehicle Restrictions"
    - **Tesla:** Speed Limit Assist mode is disabled on release branches
    - **Rivian:** Speed Limit Assist mode is always disabled

---
"""
    expected = """## Speed Limit Mode

| Property | Value |
|----------|-------|
| **Param** | `SpeedLimitMode` |
| **Type** | Multi-button selector |

> [!INFO] Requirements
> - Longitudinal control must be available, **or** ICBM must be enabled

> [!WARNING] Vehicle Restrictions
> - **Tesla:** Speed Limit Assist mode is disabled on release branches
> - **Rivian:** Speed Limit Assist mode is always disabled

---
"""
    result = convert_admonitions(input_text)
    assert result == expected, f"FAIL real_doc:\n{result!r}\n!=\n{expected!r}"
    print("  PASS: real_doc_snippet")


# ---------------------------------------------------------------------------
# 3. Convert Tabs
# ---------------------------------------------------------------------------


def test_tabs_basic():
    input_text = """=== "Tab One"
    Content for tab one.

=== "Tab Two"
    Content for tab two.
"""
    expected = """**Tab One**

Content for tab one.

---

**Tab Two**

Content for tab two.

---

"""
    result = convert_tabs(input_text)
    assert result == expected, f"FAIL tabs_basic:\n{result!r}\n!=\n{expected!r}"
    print("  PASS: tabs_basic")


def test_tabs_multiline():
    input_text = """=== "Details"
    Line 1
    Line 2
    Line 3
"""
    expected = """**Details**

Line 1
Line 2
Line 3

---

"""
    result = convert_tabs(input_text)
    assert result == expected, f"FAIL tabs_multiline:\n{result!r}\n!=\n{expected!r}"
    print("  PASS: tabs_multiline")


# ---------------------------------------------------------------------------
# 4. Convert Grid Cards
# ---------------------------------------------------------------------------


def test_grid_cards_stripped():
    input_text = """<div class="grid cards" markdown>

- **Card 1** - Description
- **Card 2** - Description

</div>
"""
    expected = """

- **Card 1** - Description
- **Card 2** - Description

"""
    result = convert_grid_cards(input_text)
    # Normalize whitespace for comparison
    assert result.strip() == expected.strip(), f"FAIL grid_cards:\n{result!r}\n!=\n{expected!r}"
    print("  PASS: grid_cards_stripped")


# ---------------------------------------------------------------------------
# 5. Convert Emoji Shortcodes
# ---------------------------------------------------------------------------


def test_emoji_known():
    input_text = ":material-check: Supported | :material-close: Not supported"
    expected = "Y Supported | N Not supported"
    result = convert_emoji_shortcodes(input_text)
    assert result == expected, f"FAIL emoji_known:\n{result!r}\n!=\n{expected!r}"
    print("  PASS: emoji_known")


def test_emoji_unknown_stripped():
    input_text = ":material-unknown-icon: Some text"
    expected = " Some text"
    result = convert_emoji_shortcodes(input_text)
    assert result == expected, f"FAIL emoji_unknown:\n{result!r}\n!=\n{expected!r}"
    print("  PASS: emoji_unknown_stripped")


def test_emoji_in_grid_card():
    input_text = "- :material-rocket-launch: **[Feature](link.md)**"
    expected = "-  **[Feature](link.md)**"
    result = convert_emoji_shortcodes(input_text)
    assert result == expected, f"FAIL emoji_grid:\n{result!r}\n!=\n{expected!r}"
    print("  PASS: emoji_in_grid_card")


# ---------------------------------------------------------------------------
# 6. Resolve Internal Links
# ---------------------------------------------------------------------------


def test_internal_link_relative():
    input_text = "See [ICBM](../features/cruise/icbm.md) for details."
    result = resolve_internal_links(
        input_text,
        file_path="settings/cruise/speed-limit.md",
    )
    assert "/search?q=" in result, f"FAIL internal_link missing search URL:\n{result!r}"
    assert "docs-sync-id" in result, f"FAIL internal_link missing sync-id:\n{result!r}"
    assert "features%2Fcruise%2Ficbm.md" in result or "features/cruise/icbm.md" in result, (
        f"FAIL internal_link wrong path:\n{result!r}"
    )
    print("  PASS: internal_link_relative")


def test_internal_link_with_anchor():
    input_text = "See [section](./safety.md#driver-responsibility)."
    result = resolve_internal_links(
        input_text,
        file_path="safety/index.md",
    )
    # Anchors are stripped — Discourse search cannot target sections
    assert "#driver-responsibility" not in result, f"FAIL anchor should be stripped:\n{result!r}"
    assert "docs-sync-id" in result, f"FAIL missing sync-id:\n{result!r}"
    assert "safety" in result, f"FAIL wrong path:\n{result!r}"
    print("  PASS: internal_link_with_anchor")


def test_external_link_untouched():
    input_text = "Visit [GitHub](https://github.com/sunnypilot/sunnypilot)."
    result = resolve_internal_links(
        input_text,
        file_path="index.md",
    )
    assert result == input_text, f"FAIL external_link:\n{result!r}"
    print("  PASS: external_link_untouched")


# ---------------------------------------------------------------------------
# 7. Clean Blank Lines
# ---------------------------------------------------------------------------


def test_clean_blank_lines():
    input_text = "Line 1\n\n\n\n\nLine 2\n"
    expected = "Line 1\n\n\nLine 2\n"
    result = clean_blank_lines(input_text)
    assert result == expected, f"FAIL clean_blanks:\n{result!r}\n!=\n{expected!r}"
    print("  PASS: clean_blank_lines")


def test_clean_blank_lines_no_change():
    input_text = "Line 1\n\nLine 2\n"
    result = clean_blank_lines(input_text)
    assert result == input_text, f"FAIL clean_blanks_noop:\n{result!r}"
    print("  PASS: clean_blank_lines_no_change")


# ---------------------------------------------------------------------------
# Integration: full convert()
# ---------------------------------------------------------------------------


def test_full_convert():
    input_text = """---
title: Test Doc
---

# Test Document

!!! warning "Important"
    Pay attention to the road.

See [safety info](../safety/safety.md) for more.

:material-check: Feature supported
"""
    result = convert(
        input_text,
        file_path="features/index.md",
    )
    # Front matter stripped
    assert "---\ntitle:" not in result, f"FAIL front matter not stripped:\n{result!r}"
    # Admonition converted
    assert "> [!WARNING] Important" in result, f"FAIL admonition:\n{result!r}"
    assert "> Pay attention to the road." in result, f"FAIL admonition content:\n{result!r}"
    # Link resolved to Discourse search
    assert "/search?q=" in result, f"FAIL link not converted to search:\n{result!r}"
    assert "docs-sync-id" in result, f"FAIL link missing sync-id:\n{result!r}"
    # Emoji converted
    assert ":material-check:" not in result, f"FAIL emoji:\n{result!r}"
    assert "Y Feature supported" in result, f"FAIL emoji replacement:\n{result!r}"
    print("  PASS: full_convert")


# ---------------------------------------------------------------------------
# Runner
# ---------------------------------------------------------------------------


if __name__ == "__main__":
    print("Testing MkDocs -> Discourse converter:")
    tests = [
        # 1. Front matter
        test_strip_front_matter_basic,
        test_strip_front_matter_absent,
        # 2. Admonitions
        test_basic_warning,
        test_info_no_title,
        test_info_with_title,
        test_danger,
        test_tip,
        test_multiline_with_blank,
        test_collapsible,
        test_collapsible_open,
        test_surrounded_by_content,
        test_multiple_admonitions,
        test_real_doc_snippet,
        # 3. Tabs
        test_tabs_basic,
        test_tabs_multiline,
        # 4. Grid cards
        test_grid_cards_stripped,
        # 5. Emoji
        test_emoji_known,
        test_emoji_unknown_stripped,
        test_emoji_in_grid_card,
        # 6. Internal links
        test_internal_link_relative,
        test_internal_link_with_anchor,
        test_external_link_untouched,
        # 7. Blank lines
        test_clean_blank_lines,
        test_clean_blank_lines_no_change,
        # Integration
        test_full_convert,
    ]
    passed = 0
    failed = 0
    for test in tests:
        try:
            test()
            passed += 1
        except AssertionError as e:
            print(f"  FAIL: {test.__name__}: {e}")
            failed += 1
        except Exception as e:
            print(f"  ERROR: {test.__name__}: {e}")
            failed += 1

    print(f"\n{passed}/{passed + failed} tests passed")
    sys.exit(1 if failed > 0 else 0)
