"""
Unit tests for commit categorization module.

Tests the grouping of synced files into logical categories for atomic commits.
"""

from persuade.commit_categorization import (
    MODIFIED_FILE_PRIORITY_OFFSET,
    CommitGroup,
    categorize_added_and_modified,
    categorize_for_commits,
    format_commit_commands,
    format_commit_summary,
)


class TestCommitGroup:
    """Tests for CommitGroup dataclass."""

    def test_full_message(self) -> None:
        """Test full_message property combines type and message."""
        group = CommitGroup(commit_type="docs", message="add git knowledge base")
        assert group.full_message == "docs: add git knowledge base"

    def test_add_file(self) -> None:
        """Test adding files to group."""
        group = CommitGroup(commit_type="docs", message="add docs")
        group.add_file("file1.md")
        group.add_file("file2.md")
        assert group.files == ["file1.md", "file2.md"]

    def test_add_file_no_duplicates(self) -> None:
        """Test that duplicate files are not added."""
        group = CommitGroup(commit_type="docs", message="add docs")
        group.add_file("file1.md")
        group.add_file("file1.md")  # Duplicate
        assert group.files == ["file1.md"]

    def test_add_files_preserves_order(self) -> None:
        """Test that files are added in the order they are received."""
        group = CommitGroup(commit_type="docs", message="add docs")
        group.add_file("z_file.md")
        group.add_file("a_file.md")
        group.add_file("m_file.md")
        assert group.files == ["z_file.md", "a_file.md", "m_file.md"]

    def test_add_same_file_to_multiple_groups(self) -> None:
        """Test that the same file can be added to different groups."""
        group1 = CommitGroup(commit_type="docs", message="docs group")
        group2 = CommitGroup(commit_type="chore", message="chore group")

        group1.add_file("shared.md")
        group2.add_file("shared.md")

        # Each group should have the file independently
        assert "shared.md" in group1.files
        assert "shared.md" in group2.files


class TestCategorizeForCommits:
    """Tests for categorize_for_commits function."""

    def test_knowledge_infrastructure_category(self) -> None:
        """Test infrastructure knowledge files are grouped correctly."""
        files = [
            ".claude/knowledge/infrastructure/storage.md",
            ".claude/knowledge/infrastructure/compute.md",
        ]
        groups = categorize_for_commits(files)

        assert len(groups) == 1
        assert groups[0].commit_type == "docs"
        assert "infrastructure" in groups[0].message.lower()
        assert len(groups[0].files) == 2

    def test_knowledge_pr_feedback_category(self) -> None:
        """Test PR feedback knowledge files are grouped correctly."""
        files = [
            ".claude/knowledge/pr-feedback/README.md",
            ".claude/knowledge/pr-feedback/branch-creation.md",
        ]
        groups = categorize_for_commits(files)

        assert len(groups) == 1
        assert groups[0].commit_type == "docs"
        assert "pr feedback" in groups[0].message.lower()

    def test_standards_category(self) -> None:
        """Test standards files are grouped correctly."""
        files = [
            ".claude/standards/ENGINEERING_STANDARDS.md",
            ".claude/standards/CLAUDE_CODE_RULES.md",
        ]
        groups = categorize_for_commits(files)

        assert len(groups) == 1
        assert groups[0].commit_type == "docs"
        assert "standards" in groups[0].message.lower()

    def test_github_workflows_category(self) -> None:
        """Test GitHub workflow files are grouped correctly."""
        files = [
            ".github/workflows/ci.yml",
            ".github/workflows/test.yml",
        ]
        groups = categorize_for_commits(files)

        assert len(groups) == 1
        assert groups[0].commit_type == "ci"
        assert "workflow" in groups[0].message.lower()

    def test_multiple_categories(self) -> None:
        """Test files from different categories create separate groups."""
        files = [
            ".claude/knowledge/git/commits.md",
            ".claude/standards/ENGINEERING.md",
            ".github/workflows/ci.yml",
            ".bandit",
        ]
        groups = categorize_for_commits(files)

        # Should have 4 separate groups
        assert len(groups) == 4
        commit_types = {g.commit_type for g in groups}
        assert "docs" in commit_types
        assert "ci" in commit_types
        assert "chore" in commit_types

    def test_empty_files_list(self) -> None:
        """Test empty files list returns empty groups."""
        groups = categorize_for_commits([])
        assert groups == []

    def test_groups_sorted_by_priority(self) -> None:
        """Test groups are sorted by priority (lower first)."""
        files = [
            ".bandit",  # chore, priority 71
            ".claude/knowledge/git/commits.md",  # docs, priority 13
            ".github/workflows/ci.yml",  # ci, priority 50
        ]
        groups = categorize_for_commits(files)

        # Knowledge should come before workflows, which comes before config
        messages = [g.message for g in groups]
        git_idx = next(i for i, m in enumerate(messages) if "git" in m.lower())
        workflow_idx = next(i for i, m in enumerate(messages) if "workflow" in m.lower())
        bandit_idx = next(i for i, m in enumerate(messages) if "bandit" in m.lower())

        assert git_idx < workflow_idx < bandit_idx

    def test_update_message_prefix(self) -> None:
        """Test that 'update' prefix replaces 'add' in messages."""
        files = [".claude/knowledge/git/commits.md"]
        groups = categorize_for_commits(files, existing_message_prefix="update")

        assert "update" in groups[0].message.lower()
        assert "add" not in groups[0].message.lower()


class TestCategorizeAddedAndModified:
    """Tests for categorize_added_and_modified function."""

    def test_added_files_use_add_prefix(self) -> None:
        """Test added files get 'add' message prefix."""
        groups = categorize_added_and_modified(
            added_files=[".claude/knowledge/git/commits.md"],
            modified_files=[],
        )

        assert len(groups) == 1
        assert "add" in groups[0].message.lower()

    def test_modified_files_use_update_prefix(self) -> None:
        """Test modified files get 'update' message prefix."""
        groups = categorize_added_and_modified(
            added_files=[],
            modified_files=[".claude/knowledge/git/commits.md"],
        )

        assert len(groups) == 1
        assert "update" in groups[0].message.lower()

    def test_both_added_and_modified(self) -> None:
        """Test handling both added and modified files."""
        groups = categorize_added_and_modified(
            added_files=[".claude/knowledge/git/README.md"],
            modified_files=[".claude/settings.json"],
        )

        assert len(groups) == 2

        # Find each group
        git_group = next(g for g in groups if "git" in g.message.lower())
        settings_group = next(g for g in groups if "settings" in g.message.lower())

        assert "add" in git_group.message.lower()
        assert "update" in settings_group.message.lower()

    def test_empty_lists(self) -> None:
        """Test empty input lists."""
        groups = categorize_added_and_modified([], [])
        assert groups == []

    def test_modified_files_have_priority_offset(self) -> None:
        """Test modified files have priority offset applied."""
        # Combine added and modified files from same category
        combined = categorize_added_and_modified(
            added_files=[".claude/knowledge/git/new.md"],
            modified_files=[".claude/knowledge/git/existing.md"],
        )

        # Find the add and update groups
        add_group = next(g for g in combined if "add" in g.message)
        update_group = next(g for g in combined if "update" in g.message)

        # The update group should have priority offset applied
        assert update_group.priority == add_group.priority + MODIFIED_FILE_PRIORITY_OFFSET


class TestFormatCommitCommands:
    """Tests for format_commit_commands function."""

    def test_basic_command(self) -> None:
        """Test basic commit command generation."""
        group = CommitGroup(
            commit_type="docs",
            message="add git knowledge base",
            files=[".claude/knowledge/git/README.md"],
        )
        commands = format_commit_commands([group])

        assert len(commands) == 1
        assert "git add" in commands[0]
        assert "git commit" in commands[0]
        assert "docs: add git knowledge base" in commands[0]

    def test_force_add_flag(self) -> None:
        """Test force add flag is included when requested."""
        group = CommitGroup(
            commit_type="docs",
            message="add docs",
            files=["file.md"],
        )
        commands = format_commit_commands([group], force_add=True)

        assert "-f" in commands[0]

    def test_multiple_groups(self) -> None:
        """Test multiple commit commands for multiple groups."""
        groups = [
            CommitGroup("docs", "add docs", files=["doc1.md"]),
            CommitGroup("chore", "add config", files=["config.yml"]),
        ]
        commands = format_commit_commands(groups)

        assert len(commands) == 2

    def test_empty_files_skipped(self) -> None:
        """Test groups with no files are skipped."""
        groups = [
            CommitGroup("docs", "add docs", files=[]),
            CommitGroup("chore", "add config", files=["config.yml"]),
        ]
        commands = format_commit_commands(groups)

        assert len(commands) == 1


class TestFormatCommitSummary:
    """Tests for format_commit_summary function."""

    def test_basic_summary(self) -> None:
        """Test basic summary formatting."""
        groups = [
            CommitGroup("docs", "add git knowledge base", files=["f1.md", "f2.md"]),
        ]
        summary = format_commit_summary(groups)

        assert "Atomic commits" in summary
        assert "docs: add git knowledge base" in summary
        assert "2 files" in summary

    def test_single_file_word(self) -> None:
        """Test singular 'file' word for single file."""
        groups = [
            CommitGroup("docs", "add docs", files=["f1.md"]),
        ]
        summary = format_commit_summary(groups)

        assert "1 file" in summary
        assert "1 files" not in summary

    def test_empty_groups(self) -> None:
        """Test empty groups message."""
        summary = format_commit_summary([])
        assert "No commits" in summary

    def test_multiple_groups_numbered(self) -> None:
        """Test multiple groups are numbered."""
        groups = [
            CommitGroup("docs", "add docs", files=["f1.md"]),
            CommitGroup("chore", "add config", files=["f2.yml"]),
        ]
        summary = format_commit_summary(groups)

        assert "[1]" in summary
        assert "[2]" in summary
