"""Unit tests for mca_sdk.validation.attributes module."""

import pytest

from mca_sdk.validation.attributes import (
    validate_resource_attributes,
    get_required_attributes,
    get_recommended_attributes,
    REQUIRED_ATTRIBUTES,
    RECOMMENDED_ATTRIBUTES,
)
from mca_sdk.utils.exceptions import ValidationError


class TestValidateResourceAttributes:
    """Test suite for validate_resource_attributes function."""

    def test_validate_internal_regression_success(self):
        """Test validation passes for valid internal regression model attributes."""
        attrs = {
            "service.name": "my-model",
            "model.id": "mdl-001",
            "model.version": "1.0.0",
            "team.name": "ml-team",
        }
        # Should not raise
        validate_resource_attributes(attrs, "internal", "regression", strict=True)

    def test_validate_internal_classification_success(self):
        """Test validation passes for internal classification model."""
        attrs = {
            "service.name": "classifier",
            "model.id": "mdl-002",
            "model.version": "2.0.0",
            "team.name": "data-science",
        }
        validate_resource_attributes(attrs, "internal", "classification", strict=True)

    def test_validate_internal_generative_success(self):
        """Test validation passes for internal generative (LLM) model."""
        attrs = {
            "service.name": "llm-app",
            "llm.provider": "openai",
            "llm.model": "gpt-4",
            "team.name": "ai-team",
        }
        validate_resource_attributes(attrs, "internal", "generative", strict=True)

    def test_validate_internal_timeseries_success(self):
        """Test validation passes for internal time-series model."""
        attrs = {
            "service.name": "forecaster",
            "model.id": "mdl-ts-001",
            "model.version": "1.5.0",
            "team.name": "analytics",
        }
        validate_resource_attributes(attrs, "internal", "time-series", strict=True)

    def test_validate_vendor_model_success(self):
        """Test validation passes for vendor model."""
        attrs = {
            "service.name": "epic-sepsis",
            "model.type": "classification",
            "vendor.name": "epic",
            "team.name": "integrations",
        }
        validate_resource_attributes(attrs, "vendor", "classification", strict=True)

    def test_validate_missing_service_name_raises_error(self):
        """Test validation fails when service.name is missing."""
        attrs = {
            "model.id": "mdl-001",
            "model.version": "1.0.0",
            "team.name": "ml-team",
        }
        with pytest.raises(ValidationError) as exc_info:
            validate_resource_attributes(attrs, "internal", "regression", strict=True)

        error_msg = str(exc_info.value)
        assert "service.name" in error_msg
        assert "Missing required resource attributes" in error_msg

    def test_validate_missing_model_id_raises_error(self):
        """Test validation fails when model.id is missing."""
        attrs = {
            "service.name": "my-model",
            "model.version": "1.0.0",
            "team.name": "ml-team",
        }
        with pytest.raises(ValidationError) as exc_info:
            validate_resource_attributes(attrs, "internal", "regression", strict=True)

        assert "model.id" in str(exc_info.value)

    def test_validate_multiple_missing_attributes(self):
        """Test validation error message includes all missing attributes."""
        attrs = {"service.name": "my-model"}
        with pytest.raises(ValidationError) as exc_info:
            validate_resource_attributes(attrs, "internal", "regression", strict=True)

        error_msg = str(exc_info.value)
        assert "model.id" in error_msg
        assert "model.version" in error_msg
        assert "team.name" in error_msg

    def test_validate_empty_string_treated_as_missing(self):
        """Test that empty string attribute values are treated as missing."""
        attrs = {
            "service.name": "my-model",
            "model.id": "",  # Empty string
            "model.version": "1.0.0",
            "team.name": "ml-team",
        }
        with pytest.raises(ValidationError) as exc_info:
            validate_resource_attributes(attrs, "internal", "regression", strict=True)

        assert "model.id" in str(exc_info.value)

    def test_validate_none_value_treated_as_missing(self):
        """Test that None attribute values are treated as missing."""
        attrs = {
            "service.name": "my-model",
            "model.id": "mdl-001",
            "model.version": None,  # None value
            "team.name": "ml-team",
        }
        with pytest.raises(ValidationError) as exc_info:
            validate_resource_attributes(attrs, "internal", "regression", strict=True)

        assert "model.version" in str(exc_info.value)

    def test_validate_attribute_value_too_long_raises_error(self):
        """Test that attribute values exceeding max length raise error."""
        long_value = "x" * 2000  # Exceeds 1024 character limit
        attrs = {
            "service.name": long_value,
            "model.id": "mdl-001",
            "model.version": "1.0.0",
            "team.name": "ml-team",
        }
        with pytest.raises(ValidationError) as exc_info:
            validate_resource_attributes(attrs, "internal", "regression", strict=True)

        error_msg = str(exc_info.value)
        assert "exceeds maximum length" in error_msg
        assert "service.name" in error_msg
        assert "1024" in error_msg

    def test_validate_non_strict_mode_skips_validation(self):
        """Test that non-strict mode skips validation."""
        attrs = {}  # Empty attributes
        # Should not raise in non-strict mode
        validate_resource_attributes(attrs, "internal", "regression", strict=False)

    def test_validate_non_dict_attributes_raises_error(self):
        """Test that non-dictionary attributes raise ValidationError."""
        with pytest.raises(ValidationError) as exc_info:
            validate_resource_attributes("not a dict", "internal", "regression")

        assert "must be a dictionary" in str(exc_info.value)

    def test_validate_invalid_taxonomy_raises_error(self):
        """Test that invalid taxonomy combination raises error."""
        attrs = {
            "service.name": "my-model",
            "model.id": "mdl-001",
            "model.version": "1.0.0",
            "team.name": "ml-team",
        }
        with pytest.raises(ValidationError) as exc_info:
            validate_resource_attributes(attrs, "invalid_category", "regression")

        assert "Invalid taxonomy combination" in str(exc_info.value)

    def test_error_message_includes_example_for_model_prefix(self):
        """Test error message includes example configuration for model prefix."""
        attrs = {"service.name": "test"}
        with pytest.raises(ValidationError) as exc_info:
            validate_resource_attributes(attrs, "internal", "regression")

        error_msg = str(exc_info.value)
        assert "Example configuration:" in error_msg
        assert "MCAClient(" in error_msg
        assert "model_category='internal'" in error_msg
        assert "model_type='regression'" in error_msg

    def test_error_message_includes_example_for_genai_prefix(self):
        """Test error message includes example configuration for genai prefix."""
        attrs = {"service.name": "test"}
        with pytest.raises(ValidationError) as exc_info:
            validate_resource_attributes(attrs, "internal", "generative")

        error_msg = str(exc_info.value)
        assert "llm_provider='openai'" in error_msg
        assert "llm_model='gpt-4'" in error_msg

    def test_error_message_includes_example_for_vendor_prefix(self):
        """Test error message includes example configuration for vendor prefix."""
        attrs = {"service.name": "test"}
        with pytest.raises(ValidationError) as exc_info:
            validate_resource_attributes(attrs, "vendor", "classification")

        error_msg = str(exc_info.value)
        assert "vendor_name='epic'" in error_msg
        assert "model_category='vendor'" in error_msg

    def test_error_message_shows_checkmarks_for_present_attributes(self):
        """Test error message shows checkmarks for attributes that are present."""
        attrs = {
            "service.name": "my-model",
            "model.id": "mdl-001",
            # Missing: model.version, team.name
        }
        with pytest.raises(ValidationError) as exc_info:
            validate_resource_attributes(attrs, "internal", "regression")

        error_msg = str(exc_info.value)
        # Should show ✓ for present attributes
        assert "✓ service.name" in error_msg
        assert "✓ model.id" in error_msg
        # Should show ✗ for missing attributes
        assert "✗ model.version" in error_msg
        assert "✗ team.name" in error_msg

    def test_validate_with_extra_attributes_succeeds(self):
        """Test that extra attributes beyond required ones are allowed."""
        attrs = {
            "service.name": "my-model",
            "model.id": "mdl-001",
            "model.version": "1.0.0",
            "team.name": "ml-team",
            "extra.attribute": "extra value",
            "service.version": "2.0.0",
        }
        # Should not raise
        validate_resource_attributes(attrs, "internal", "regression", strict=True)


class TestGetRequiredAttributes:
    """Test suite for get_required_attributes function."""

    def test_get_required_for_internal_regression(self):
        """Test getting required attributes for internal regression model."""
        result = get_required_attributes("internal", "regression")
        assert result == ["service.name", "model.id", "model.version", "team.name"]

    def test_get_required_for_internal_timeseries(self):
        """Test getting required attributes for internal time-series model."""
        result = get_required_attributes("internal", "time-series")
        assert result == ["service.name", "model.id", "model.version", "team.name"]

    def test_get_required_for_internal_classification(self):
        """Test getting required attributes for internal classification model."""
        result = get_required_attributes("internal", "classification")
        assert result == ["service.name", "model.id", "model.version", "team.name"]

    def test_get_required_for_internal_generative(self):
        """Test getting required attributes for internal generative model."""
        result = get_required_attributes("internal", "generative")
        assert result == ["service.name", "llm.provider", "llm.model", "team.name"]

    def test_get_required_for_vendor_timeseries(self):
        """Test getting required attributes for vendor time-series model."""
        result = get_required_attributes("vendor", "time-series")
        assert result == ["service.name", "model.type", "vendor.name", "team.name"]

    def test_get_required_for_vendor_model(self):
        """Test getting required attributes for vendor model."""
        result = get_required_attributes("vendor", "classification")
        assert result == ["service.name", "model.type", "vendor.name", "team.name"]

    def test_get_required_invalid_taxonomy_raises_error(self):
        """Test that invalid taxonomy raises ValidationError."""
        with pytest.raises(ValidationError) as exc_info:
            get_required_attributes("invalid", "regression")

        assert "Invalid taxonomy combination" in str(exc_info.value)

    def test_get_required_returns_copy(self):
        """Test that function returns a copy, not the original list."""
        result1 = get_required_attributes("internal", "regression")
        result2 = get_required_attributes("internal", "regression")

        # Modify result1
        result1.append("extra")

        # result2 should not be affected
        assert "extra" not in result2


class TestGetRecommendedAttributes:
    """Test suite for get_recommended_attributes function."""

    def test_get_recommended_attributes(self):
        """Test getting list of recommended attributes."""
        result = get_recommended_attributes()
        assert result == ["service.version", "deployment.environment"]

    def test_get_recommended_returns_copy(self):
        """Test that function returns a copy, not the original list."""
        result1 = get_recommended_attributes()
        result2 = get_recommended_attributes()

        # Modify result1
        result1.append("extra")

        # result2 should not be affected
        assert "extra" not in result2
        assert result2 == ["service.version", "deployment.environment"]


class TestModuleConstants:
    """Test suite for module-level constants."""

    def test_required_attributes_structure(self):
        """Test that REQUIRED_ATTRIBUTES has expected structure."""
        assert "model" in REQUIRED_ATTRIBUTES
        assert "genai" in REQUIRED_ATTRIBUTES
        assert "vendor" in REQUIRED_ATTRIBUTES

        # Each should have required attribute lists
        assert isinstance(REQUIRED_ATTRIBUTES["model"], list)
        assert isinstance(REQUIRED_ATTRIBUTES["genai"], list)
        assert isinstance(REQUIRED_ATTRIBUTES["vendor"], list)

    def test_required_attributes_all_include_service_name(self):
        """Test that all required attribute sets include service.name."""
        for prefix, attrs in REQUIRED_ATTRIBUTES.items():
            assert "service.name" in attrs, f"{prefix} missing service.name"

    def test_required_attributes_all_include_team_name(self):
        """Test that all required attribute sets include team.name."""
        for prefix, attrs in REQUIRED_ATTRIBUTES.items():
            assert "team.name" in attrs, f"{prefix} missing team.name"

    def test_recommended_attributes_not_empty(self):
        """Test that RECOMMENDED_ATTRIBUTES is not empty."""
        assert len(RECOMMENDED_ATTRIBUTES) > 0

    def test_recommended_attributes_includes_service_version(self):
        """Test that recommended attributes include service.version."""
        assert "service.version" in RECOMMENDED_ATTRIBUTES

    def test_recommended_attributes_includes_deployment_environment(self):
        """Test that recommended attributes include deployment.environment."""
        assert "deployment.environment" in RECOMMENDED_ATTRIBUTES
