"""Unit tests for metric naming conventions (Story 2.6).

This module tests the naming convention rules and validation functions
defined in naming_conventions.py.
"""

import pytest
from mca_sdk.validation.naming_conventions import (
    is_valid_metric_name,
    sanitize_metric_name,
    METRIC_NAME_PATTERN,
    MIN_METRIC_NAME_LENGTH,
    MAX_METRIC_NAME_LENGTH,
)


class TestMetricNamingConventionRules:
    """Test naming convention rules."""

    def test_must_start_with_lowercase_letter(self):
        """Test metric name must start with lowercase letter (a-z)."""
        assert is_valid_metric_name("metric_name") is True
        assert is_valid_metric_name("a_metric") is True
        assert is_valid_metric_name("z_metric") is True

        # Invalid: starts with digit
        assert is_valid_metric_name("1metric") is False
        assert is_valid_metric_name("123_metric") is False

        # Invalid: starts with uppercase
        assert is_valid_metric_name("Metric") is False
        assert is_valid_metric_name("METRIC") is False

        # Invalid: starts with special char
        assert is_valid_metric_name("_metric") is False
        assert is_valid_metric_name(".metric") is False

    def test_may_contain_lowercase_letters(self):
        """Test metric name may contain lowercase letters."""
        assert is_valid_metric_name("abcdefghijklmnopqrstuvwxyz") is True
        assert is_valid_metric_name("metric") is True
        assert is_valid_metric_name("test") is True

    def test_may_contain_digits(self):
        """Test metric name may contain digits (0-9)."""
        assert is_valid_metric_name("metric123") is True
        assert is_valid_metric_name("test_0123456789") is True
        assert is_valid_metric_name("metric_1") is True

    def test_may_contain_underscores(self):
        """Test metric name may contain underscores (_)."""
        assert is_valid_metric_name("metric_name") is True
        assert is_valid_metric_name("test_metric_name") is True
        assert is_valid_metric_name("metric__name") is True  # consecutive underscores

    def test_may_contain_dots(self):
        """Test metric name may contain dots (.) for namespacing."""
        assert is_valid_metric_name("model.predictions_total") is True
        assert is_valid_metric_name("genai.tokens.prompt") is True
        assert is_valid_metric_name("a.b.c.d") is True
        assert is_valid_metric_name("metric..name") is True  # consecutive dots

    def test_must_not_contain_hyphens(self):
        """Test metric name must NOT contain hyphens (-)."""
        assert is_valid_metric_name("metric-name") is False
        assert is_valid_metric_name("test-metric-name") is False
        assert is_valid_metric_name("invalid-name") is False

    def test_must_not_contain_spaces(self):
        """Test metric name must NOT contain spaces."""
        assert is_valid_metric_name("metric name") is False
        assert is_valid_metric_name("test metric") is False
        assert is_valid_metric_name("a b c") is False

    def test_must_not_contain_special_characters(self):
        """Test metric name must NOT contain special characters."""
        assert is_valid_metric_name("metric@name") is False
        assert is_valid_metric_name("metric#name") is False
        assert is_valid_metric_name("metric$name") is False
        assert is_valid_metric_name("metric%name") is False
        assert is_valid_metric_name("metric&name") is False
        assert is_valid_metric_name("metric*name") is False

    def test_must_not_start_with_digit(self):
        """Test metric name must NOT start with digit."""
        assert is_valid_metric_name("123_metric") is False
        assert is_valid_metric_name("1metric") is False
        assert is_valid_metric_name("9test") is False

    def test_recommended_format_with_prefix(self):
        """Test recommended format: {prefix}.{metric_name}."""
        # Model metrics
        assert is_valid_metric_name("model.predictions_total") is True
        assert is_valid_metric_name("model.latency_seconds") is True

        # GenAI metrics
        assert is_valid_metric_name("genai.tokens.prompt") is True
        assert is_valid_metric_name("genai.cost_usd") is True

        # Vendor metrics
        assert is_valid_metric_name("vendor.predictions_total") is True


class TestMetricNamePattern:
    """Test METRIC_NAME_PATTERN regex."""

    def test_pattern_matches_valid_names(self):
        """Test pattern matches valid metric names."""
        valid_names = [
            "model.predictions_total",
            "valid_metric_name",
            "abc",
            "test_123",
            "a.b.c",
        ]
        for name in valid_names:
            assert METRIC_NAME_PATTERN.match(name) is not None, f"Pattern should match {name}"

    def test_pattern_rejects_invalid_names(self):
        """Test pattern rejects invalid metric names."""
        invalid_names = [
            "invalid-name",  # hyphen
            "123_invalid",  # starts with digit
            "MetricName",  # uppercase
            "_metric",  # starts with underscore
            ".metric",  # starts with dot
        ]
        for name in invalid_names:
            assert METRIC_NAME_PATTERN.match(name) is None, f"Pattern should not match {name}"


class TestMetricNameLengthConstraints:
    """Test length constraints."""

    def test_minimum_length(self):
        """Test minimum length constraint (>= 3 characters)."""
        assert MIN_METRIC_NAME_LENGTH == 3

        # Valid: exactly 3 chars
        assert is_valid_metric_name("abc") is True

        # Invalid: less than 3 chars
        assert is_valid_metric_name("ab") is False
        assert is_valid_metric_name("a") is False
        assert is_valid_metric_name("") is False

    def test_maximum_length(self):
        """Test maximum length constraint (<= 256 characters)."""
        assert MAX_METRIC_NAME_LENGTH == 256

        # Valid: exactly 256 chars
        assert is_valid_metric_name("a" * 256) is True

        # Invalid: more than 256 chars
        assert is_valid_metric_name("a" * 257) is False
        assert is_valid_metric_name("a" * 300) is False


class TestSanitizeMetricNameTransformations:
    """Test sanitize_metric_name() transformations."""

    def test_replace_hyphens_with_underscores(self):
        """Test hyphens are replaced with underscores."""
        assert sanitize_metric_name("metric-name") == "metric_name"
        assert sanitize_metric_name("test-metric-name") == "test_metric_name"
        assert sanitize_metric_name("a-b-c") == "a_b_c"

    def test_replace_spaces_with_underscores(self):
        """Test spaces are replaced with underscores."""
        assert sanitize_metric_name("metric name") == "metric_name"
        assert sanitize_metric_name("test metric name") == "test_metric_name"
        assert sanitize_metric_name("a b c") == "a_b_c"

    def test_convert_to_lowercase(self):
        """Test uppercase is converted to lowercase."""
        assert sanitize_metric_name("MetricName") == "metricname"
        assert sanitize_metric_name("METRIC") == "metric"
        assert sanitize_metric_name("Metric_Name") == "metric_name"
        assert sanitize_metric_name("TEST") == "test"

    def test_remove_invalid_characters(self):
        """Test invalid characters are removed."""
        assert sanitize_metric_name("metric@name") == "metricname"
        assert sanitize_metric_name("metric#name$test") == "metricnametest"
        assert sanitize_metric_name("metric%name&test") == "metricnametest"
        assert sanitize_metric_name("metric*name!test") == "metricnametest"

    def test_prepend_m_if_starts_with_digit(self):
        """Test 'm_' is prepended if starts with digit."""
        assert sanitize_metric_name("123_metric") == "m_123_metric"
        assert sanitize_metric_name("1metric") == "m_1metric"
        assert sanitize_metric_name("9test") == "m_9test"

    def test_ensure_minimum_length(self):
        """Test minimum length is enforced."""
        result = sanitize_metric_name("ab")
        assert len(result) >= MIN_METRIC_NAME_LENGTH
        assert result.startswith("metric_")
        assert is_valid_metric_name(result) is True

        result = sanitize_metric_name("a")
        assert len(result) >= MIN_METRIC_NAME_LENGTH
        assert is_valid_metric_name(result) is True

    def test_truncate_to_maximum_length(self):
        """Test maximum length is enforced."""
        long_name = "a" * 300
        result = sanitize_metric_name(long_name)
        assert len(result) <= MAX_METRIC_NAME_LENGTH
        assert is_valid_metric_name(result) is True

    def test_combined_transformations(self):
        """Test multiple transformations applied together."""
        # Hyphen + uppercase + space
        result = sanitize_metric_name("Invalid-Metric Name")
        assert result == "invalid_metric_name"
        assert is_valid_metric_name(result) is True

        # Digit start + hyphen + uppercase
        result = sanitize_metric_name("123-Invalid-Metric")
        assert result == "m_123_invalid_metric"
        assert is_valid_metric_name(result) is True

        # All transformations
        result = sanitize_metric_name("123-Invalid Metric@Name!")
        assert result.startswith("m_")
        assert is_valid_metric_name(result) is True


class TestSanitizedNamesAreValid:
    """Test all sanitized names pass validation."""

    @pytest.mark.parametrize(
        "invalid_name",
        [
            "invalid-name",
            "123_metric",
            "MetricName",
            "metric name",
            "metric@name",
            "ab",
            "UPPERCASE",
            "Test-Metric Name",
        ],
    )
    def test_sanitized_names_valid(self, invalid_name):
        """Test sanitized names pass validation."""
        sanitized = sanitize_metric_name(invalid_name)
        assert (
            is_valid_metric_name(sanitized) is True
        ), f"Sanitized name '{sanitized}' from '{invalid_name}' should be valid"
