"""Tests for rules engine."""

import pytest

from lattice.core.rules import (
    PROMOTED_TAG,
    assemble_instincts,
    count_rule_tokens,
    count_tokens_approximate,
    is_promoted,
    merge_rules,
    strip_promoted_tag,
    total_token_count,
)
from lattice.core.types.rule import Rule


class TestTokenCounting:
    """Tests for token counting functions."""

    def test_count_tokens_empty_string(self) -> None:
        """Empty string should return 0 tokens."""
        assert count_tokens_approximate("") == 0

    def test_count_tokens_single_word(self) -> None:
        """Single word should return ~1-2 tokens."""
        result = count_tokens_approximate("hello")
        assert 1 <= result <= 2

    def test_count_tokens_multiple_words(self) -> None:
        """Multiple words should return ~1.3x word count."""
        text = "hello world foo bar"
        result = count_tokens_approximate(text)
        assert 4 <= result <= 8

    def test_count_rule_tokens_basic(self) -> None:
        """Rule token count should count content only."""
        rule = Rule(file_path="test.md", title="Test Title", content="Hello world")
        result = count_rule_tokens(rule)
        assert result >= 0

    def test_count_rule_tokens_empty_content(self) -> None:
        """Rule with empty content should return 0."""
        rule = Rule(file_path="test.md", title="Test", content="")
        assert count_rule_tokens(rule) == 0


class TestPromotedTag:
    """Tests for promoted tag parsing."""

    def test_is_promoted_with_tag(self) -> None:
        """Content with promoted tag should return True."""
        content = f"# Rule\n{PROMOTED_TAG}\nContent"
        assert is_promoted(content) is True

    def test_is_promoted_without_tag(self) -> None:
        """Content without promoted tag should return False."""
        content = "# Rule\nContent"
        assert is_promoted(content) is False

    def test_is_promoted_empty_content(self) -> None:
        """Empty content should return False."""
        assert is_promoted("") is False

    def test_strip_promoted_tag_removes_tag(self) -> None:
        """Strip should remove the promoted tag."""
        content = f"# Rule\n{PROMOTED_TAG}\nContent"
        result = strip_promoted_tag(content)
        assert PROMOTED_TAG not in result

    def test_strip_promoted_tag_preserves_content(self) -> None:
        """Strip should preserve other content."""
        content = f"# Rule\n{PROMOTED_TAG}\nContent here"
        result = strip_promoted_tag(content)
        assert "Rule" in result
        assert "Content here" in result

    def test_strip_promoted_tag_no_tag(self) -> None:
        """Strip on content without tag should return unchanged."""
        content = "# Rule\nContent"
        result = strip_promoted_tag(content)
        assert result == content.strip()


class TestMergeRules:
    """Tests for rule merging."""

    def test_merge_rules_empty_lists(self) -> None:
        """Merging empty lists should return empty list."""
        result = merge_rules([], [])
        assert result == []

    def test_merge_rules_global_only(self) -> None:
        """Global rules only should be returned as-is."""
        global_rules = [
            Rule(file_path="a.md", title="A", content="Global A"),
            Rule(file_path="b.md", title="B", content="Global B"),
        ]
        result = merge_rules(global_rules, [])
        assert len(result) == 2

    def test_merge_rules_project_only(self) -> None:
        """Project rules only should be returned as-is."""
        project_rules = [
            Rule(file_path="a.md", title="A", content="Project A"),
        ]
        result = merge_rules([], project_rules)
        assert len(result) == 1

    def test_merge_rules_project_overrides_global(self) -> None:
        """Project rules should override global rules with same file_path."""
        global_rules = [
            Rule(file_path="conventions.md", title="Global", content="Global content"),
        ]
        project_rules = [
            Rule(
                file_path="conventions.md", title="Project", content="Project content"
            ),
        ]
        result = merge_rules(global_rules, project_rules)
        assert len(result) == 1
        assert result[0].title == "Project"

    def test_merge_rules_different_files(self) -> None:
        """Rules with different file paths should all be included."""
        global_rules = [
            Rule(file_path="global.md", title="Global", content="G"),
        ]
        project_rules = [
            Rule(file_path="local.md", title="Local", content="L"),
        ]
        result = merge_rules(global_rules, project_rules)
        assert len(result) == 2

    def test_merge_rules_excludes_promoted(self) -> None:
        """Promoted project rules should be excluded."""
        global_rules = []
        project_rules = [
            Rule(
                file_path="promoted.md",
                title="Promoted",
                content="P",
                is_promoted=True,
            ),
            Rule(file_path="local.md", title="Local", content="L"),
        ]
        result = merge_rules(global_rules, project_rules)
        assert len(result) == 1
        assert result[0].file_path == "local.md"


class TestTotalTokenCount:
    """Tests for total token counting."""

    def test_total_token_count_empty(self) -> None:
        """Empty list should return 0."""
        assert total_token_count([]) == 0

    def test_total_token_count_single_rule(self) -> None:
        """Single rule should return rule's token count."""
        rule = Rule(file_path="t.md", title="T", content="Hello world")
        single_count = count_rule_tokens(rule)
        total = total_token_count([rule])
        assert total == single_count

    def test_total_token_count_multiple_rules(self) -> None:
        """Multiple rules should sum their token counts."""
        rules = [
            Rule(file_path="a.md", title="A", content="Hello"),
            Rule(file_path="b.md", title="B", content="World"),
        ]
        total = total_token_count(rules)
        assert total >= 0


class TestAssembleInstincts:
    """Tests for instinct assembly."""

    def test_assemble_instincts_empty(self) -> None:
        """Empty rules should return empty string."""
        result = assemble_instincts([])
        assert result == ""

    def test_assemble_instincts_single_rule(self) -> None:
        """Single rule should be formatted correctly."""
        rules = [Rule(file_path="test.md", title="Test Rule", content="Content here")]
        result = assemble_instincts(rules)
        assert "Test Rule" in result
        assert "Content here" in result

    def test_assemble_instincts_with_global_section(self) -> None:
        """Global rules should be in Global section."""
        rules = [Rule(file_path="global.md", title="Global Rule", content="G")]
        result = assemble_instincts(rules, global_rule_names={"global.md"})
        assert "### Global" in result
        assert "Global Rule" in result

    def test_assemble_instincts_with_project_section(self) -> None:
        """Project rules should be in Project section."""
        rules = [Rule(file_path="local.md", title="Local Rule", content="L")]
        result = assemble_instincts(rules, global_rule_names=set())
        assert "### Project" in result
        assert "Local Rule" in result

    def test_assemble_instincts_both_sections(self) -> None:
        """Both global and project rules should appear."""
        rules = [
            Rule(file_path="global.md", title="G Rule", content="Global"),
            Rule(file_path="local.md", title="L Rule", content="Local"),
        ]
        result = assemble_instincts(rules, global_rule_names={"global.md"})
        assert "### Global" in result
        assert "### Project" in result
        assert "G Rule" in result
        assert "L Rule" in result

    def test_assemble_instincts_includes_memory_tools(self) -> None:
        """Should include memory tools section."""
        rules = [Rule(file_path="t.md", title="T", content="C")]
        result = assemble_instincts(rules)
        assert "Memory Tools" in result
        assert "lattice_search" in result

    def test_assemble_instincts_header(self) -> None:
        """Should include proper header."""
        rules = [Rule(file_path="t.md", title="T", content="C")]
        result = assemble_instincts(rules)
        assert "Lattice" in result
        assert "Active Instincts" in result
