"""Unit tests for ID generation utility."""

import pytest
import re
from unittest.mock import patch

from agile_mcp.utils.id_generator import (
    generate_id,
    generate_story_id,
    generate_task_id,
    generate_sprint_id,
    generate_epic_id,
)


class TestIDGenerator:
    """Test cases for ID generation functions."""

    def test_generate_id_basic_format(self):
        """Test that generate_id produces correctly formatted IDs."""
        result = generate_id("STORY")

        # Should match format PREFIX-XXXXXXXX where X is hex digit
        pattern = r"^STORY-[0-9A-F]{8}$"
        assert re.match(pattern, result), f"ID '{result}' doesn't match expected format"

    def test_generate_id_uniqueness(self):
        """Test that generate_id produces unique IDs."""
        ids = set()
        for _ in range(1000):  # Generate many IDs to test collision resistance
            new_id = generate_id("TEST")
            assert new_id not in ids, f"Duplicate ID generated: {new_id}"
            ids.add(new_id)

    def test_generate_id_with_different_prefixes(self):
        """Test generate_id with various prefixes."""
        prefixes = ["STORY", "TASK", "SPRINT", "EPIC", "TEST", "A", "123"]

        for prefix in prefixes:
            result = generate_id(prefix)
            assert result.startswith(f"{prefix}-"), f"ID '{result}' doesn't start with '{prefix}-'"

            # Check hex part is 8 characters
            hex_part = result.split("-", 1)[1]
            assert len(hex_part) == 8, f"Hex part '{hex_part}' is not 8 characters"
            assert re.match(r"^[0-9A-F]{8}$", hex_part), f"Hex part '{hex_part}' contains non-hex characters"

    def test_generate_id_empty_prefix_raises_error(self):
        """Test that generate_id raises ValueError for empty prefix."""
        with pytest.raises(ValueError, match="Prefix cannot be empty"):
            generate_id("")

    def test_generate_id_none_prefix_raises_error(self):
        """Test that generate_id raises ValueError for None prefix."""
        with pytest.raises(ValueError, match="Prefix cannot be empty"):
            generate_id(None)

    def test_generate_story_id(self):
        """Test story ID generation."""
        result = generate_story_id()
        assert re.match(r"^STORY-[0-9A-F]{8}$", result)

    def test_generate_task_id(self):
        """Test task ID generation."""
        result = generate_task_id()
        assert re.match(r"^TASK-[0-9A-F]{8}$", result)

    def test_generate_sprint_id(self):
        """Test sprint ID generation."""
        result = generate_sprint_id()
        assert re.match(r"^SPRINT-[0-9A-F]{8}$", result)

    def test_generate_epic_id(self):
        """Test epic ID generation."""
        result = generate_epic_id()
        assert re.match(r"^EPIC-[0-9A-F]{8}$", result)

    def test_specific_functions_uniqueness(self):
        """Test that specific generation functions produce unique IDs."""
        functions = [generate_story_id, generate_task_id, generate_sprint_id, generate_epic_id]

        for func in functions:
            ids = set()
            for _ in range(100):  # Test fewer iterations for performance
                new_id = func()
                assert new_id not in ids, f"Duplicate ID generated by {func.__name__}: {new_id}"
                ids.add(new_id)

    def test_id_format_consistency_across_functions(self):
        """Test that all generation functions follow the same format."""
        expected_patterns = {
            generate_story_id: r"^STORY-[0-9A-F]{8}$",
            generate_task_id: r"^TASK-[0-9A-F]{8}$",
            generate_sprint_id: r"^SPRINT-[0-9A-F]{8}$",
            generate_epic_id: r"^EPIC-[0-9A-F]{8}$",
        }

        for func, pattern in expected_patterns.items():
            result = func()
            assert re.match(pattern, result), f"{func.__name__} produced invalid format: {result}"

    @patch("agile_mcp.utils.id_generator.secrets.token_hex")
    def test_generate_id_uses_secrets_token_hex(self, mock_token_hex):
        """Test that generate_id uses secrets.token_hex for cryptographic randomness."""
        mock_token_hex.return_value = "abcd1234"

        result = generate_id("TEST")

        mock_token_hex.assert_called_once_with(4)  # 4 bytes = 8 hex characters
        assert result == "TEST-ABCD1234"  # Should be uppercase

    def test_hex_part_is_uppercase(self):
        """Test that the hex part of generated IDs is uppercase."""
        for _ in range(10):
            result = generate_id("TEST")
            hex_part = result.split("-", 1)[1]
            # Check that all alphabetic characters are uppercase
            assert hex_part == hex_part.upper(), f"Hex part '{hex_part}' is not uppercase"
            # Also verify it's a valid hex string
            assert re.match(r"^[0-9A-F]{8}$", hex_part), f"Hex part '{hex_part}' is not valid hex"

    def test_total_id_length(self):
        """Test the total length of generated IDs."""
        test_cases = [
            ("A", 10),  # A-XXXXXXXX = 10 chars
            ("STORY", 14),  # STORY-XXXXXXXX = 14 chars
            ("SPRINT", 15),  # SPRINT-XXXXXXXX = 15 chars
        ]

        for prefix, expected_length in test_cases:
            result = generate_id(prefix)
            assert len(result) == expected_length, f"ID '{result}' has length {len(result)}, expected {expected_length}"
