"""Unit tests for macro parser."""

import pytest
from markitect.prompts.templates.parser import MacroParser, MacroParsingError
from markitect.prompts.templates.models import MacroKind


class TestMacroParser:
    """Tests for MacroParser."""

    def setup_method(self):
        """Setup parser for each test."""
        self.parser = MacroParser()

    def test_parse_required_macro(self):
        """Test parsing required macro."""
        content = "Some text {{require:glossary}} more text"
        macros = self.parser.parse(content)

        assert len(macros) == 1
        assert macros[0].kind == MacroKind.REQUIRED
        assert macros[0].target == "glossary"
        assert macros[0].parameters == {}

    def test_parse_optional_macro(self):
        """Test parsing optional macro."""
        content = "Text {{optional:constraints}}"
        macros = self.parser.parse(content)

        assert len(macros) == 1
        assert macros[0].kind == MacroKind.OPTIONAL
        assert macros[0].target == "constraints"

    def test_parse_generate_macro(self):
        """Test parsing generate macro."""
        content = "{{generate:examples}}"
        macros = self.parser.parse(content)

        assert len(macros) == 1
        assert macros[0].kind == MacroKind.GENERATE
        assert macros[0].target == "examples"

    def test_parse_macro_with_parameters(self):
        """Test parsing macro with parameters."""
        content = "{{generate:code|language=python|framework=fastapi}}"
        macros = self.parser.parse(content)

        assert len(macros) == 1
        assert macros[0].target == "code"
        assert macros[0].parameters == {
            "language": "python",
            "framework": "fastapi",
        }

    def test_parse_multiple_macros(self):
        """Test parsing multiple macros."""
        content = """
        {{require:glossary}}
        Some text here
        {{optional:notes}}
        {{generate:examples|lang=python}}
        """
        macros = self.parser.parse(content)

        assert len(macros) == 3
        assert macros[0].kind == MacroKind.REQUIRED
        assert macros[1].kind == MacroKind.OPTIONAL
        assert macros[2].kind == MacroKind.GENERATE

    def test_parse_with_line_numbers(self):
        """Test that line numbers are recorded."""
        content = """Line 1
        {{require:dep1}}
        Line 3
        {{optional:dep2}}
        """
        macros = self.parser.parse(content)

        assert macros[0].line_number == 2
        assert macros[1].line_number == 4

    def test_parse_case_insensitive(self):
        """Test macro kind is case insensitive."""
        content = "{{REQUIRE:test}} {{Optional:test2}} {{Generate:test3}}"
        macros = self.parser.parse(content)

        assert len(macros) == 3
        assert all(m.kind in MacroKind for m in macros)

    def test_parse_alias_gen_for_generate(self):
        """Test 'gen' alias for 'generate'."""
        content = "{{gen:test}}"
        macros = self.parser.parse(content)

        assert len(macros) == 1
        assert macros[0].kind == MacroKind.GENERATE

    def test_parse_alias_required_for_require(self):
        """Test 'required' alias for 'require'."""
        content = "{{required:test}}"
        macros = self.parser.parse(content)

        assert len(macros) == 1
        assert macros[0].kind == MacroKind.REQUIRED

    def test_parse_preserves_raw_text(self):
        """Test raw text is preserved."""
        content = "{{require:test|a=1}}"
        macros = self.parser.parse(content)

        assert macros[0].raw_text == "{{require:test|a=1}}"

    def test_parse_empty_content(self):
        """Test parsing empty content."""
        macros = self.parser.parse("")
        assert macros == []

    def test_parse_no_macros(self):
        """Test parsing content without macros."""
        content = "Just regular text without any macros"
        macros = self.parser.parse(content)
        assert macros == []

    def test_parse_invalid_macro_kind_raises_error(self):
        """Test invalid macro kind raises error."""
        content = "{{invalid:target}}"
        with pytest.raises(MacroParsingError, match="Invalid macro kind"):
            self.parser.parse(content)

    def test_parse_empty_target_raises_error(self):
        """Test empty target raises error."""
        content = "{{require:}}"
        with pytest.raises(MacroParsingError, match="target cannot be empty"):
            self.parser.parse(content)

    def test_find_macro_positions(self):
        """Test finding macro positions."""
        content = "Start {{require:dep1}} middle {{optional:dep2}} end"
        positions = self.parser.find_macro_positions(content)

        assert len(positions) == 2
        assert content[positions[0][0]:positions[0][1]] == "{{require:dep1}}"
        assert content[positions[1][0]:positions[1][1]] == "{{optional:dep2}}"

    def test_count_macros(self):
        """Test macro counting."""
        content = """
        {{require:dep1}}
        {{require:dep2}}
        {{optional:dep3}}
        {{generate:gen1}}
        """
        counts = self.parser.count_macros(content)

        assert counts['required'] == 2
        assert counts['optional'] == 1
        assert counts['generate'] == 1

    def test_has_macros_true(self):
        """Test has_macros returns True when macros present."""
        content = "Text {{require:test}} text"
        assert self.parser.has_macros(content) is True

    def test_has_macros_false(self):
        """Test has_macros returns False when no macros."""
        content = "Just plain text"
        assert self.parser.has_macros(content) is False

    def test_parse_macro_with_spaces_in_target(self):
        """Test target with spaces is trimmed."""
        content = "{{require:  my-artifact  }}"
        macros = self.parser.parse(content)
        assert macros[0].target == "my-artifact"

    def test_parse_parameter_with_spaces(self):
        """Test parameters with spaces are trimmed."""
        content = "{{generate:test| key = value }}"
        macros = self.parser.parse(content)
        assert macros[0].parameters == {"key": "value"}
