import pytest
from mca_sdk.config import MCAConfig
from mca_sdk.validation import AttributeValidator, ValidationError, ValidationResult


class TestAttributeValidator:
    def test_validator_initialization(self):
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="regression",
            model_id="model-123",
            model_version="1.0.0",
            team_name="test-team",
        )
        validator = AttributeValidator(config, strict=True)
        assert validator.config == config
        assert validator.strict is True

    def test_validate_internal_model_success(self):
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="regression",
            model_id="model-123",
            model_version="1.0.0",
            team_name="test-team",
        )
        validator = AttributeValidator(config)
        result = validator.validate()
        assert result.is_valid is True
        assert len(result.missing_fields) == 0
        assert str(result) == "Validation passed"

    def test_validate_internal_model_failure(self):
        # Missing model_id
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="regression",
            model_id=None,
            model_version="1.0.0",
            team_name="test-team",
        )
        validator = AttributeValidator(config, strict=True)

        with pytest.raises(ValidationError) as excinfo:
            validator.validate()

        assert "Missing required attribute: model.id" in str(excinfo.value)
        assert "model.id" in excinfo.value.missing_fields

    def test_validate_generative_model_success(self):
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="generative",
            llm_provider="openai",
            llm_model="gpt-4",
            team_name="test-team",
        )
        validator = AttributeValidator(config)
        result = validator.validate()
        assert result.is_valid is True

    def test_validate_generative_model_failure(self):
        # Missing llm_provider
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="generative",
            llm_provider=None,
            llm_model="gpt-4",
            team_name="test-team",
        )
        validator = AttributeValidator(config, strict=True)

        with pytest.raises(ValidationError) as excinfo:
            validator.validate()

        assert "Missing required attribute: llm.provider" in str(excinfo.value)

    def test_validate_vendor_model_success(self):
        config = MCAConfig(
            service_name="test-service",
            model_category="vendor",
            model_type="classification",
            vendor_name="epic",
            team_name="test-team",
        )
        validator = AttributeValidator(config)
        result = validator.validate()
        assert result.is_valid is True

    def test_validate_vendor_model_failure(self):
        # Missing vendor_name
        config = MCAConfig(
            service_name="test-service",
            model_category="vendor",
            model_type="classification",
            vendor_name=None,
            team_name="test-team",
        )
        validator = AttributeValidator(config, strict=True)

        with pytest.raises(ValidationError) as excinfo:
            validator.validate()

        assert "Missing required attribute: vendor.name" in str(excinfo.value)

    def test_permissive_mode(self):
        # Missing model_id but strict=False
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="regression",
            model_id=None,
            model_version="1.0.0",
            team_name="test-team",
        )
        validator = AttributeValidator(config, strict=False)
        result = validator.validate()

        assert result.is_valid is False
        assert "model.id" in result.missing_fields
        assert "Validation failed" in str(result)
        # Should not raise exception

    def test_attribute_value_validation(self):
        # Invalid model_id (special chars)
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="regression",
            model_id="model@123",  # Invalid char @
            model_version="1.0.0",
            team_name="test-team",
        )
        validator = AttributeValidator(config, strict=True)

        with pytest.raises(ValidationError) as excinfo:
            validator.validate()

        assert "Invalid value for model.id" in str(excinfo.value)

    def test_attribute_length_validation(self):
        # Value too long
        long_string = "a" * 1025
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="regression",
            model_id="model-123",
            model_version="1.0.0",
            team_name=long_string,  # Too long
        )
        validator = AttributeValidator(config, strict=True)

        with pytest.raises(ValidationError) as excinfo:
            validator.validate()

        assert "Invalid value for team.name" in str(excinfo.value)

    # Additional Internal Model Tests
    def test_internal_missing_model_version(self):
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="regression",
            model_id="model-123",
            model_version=None,  # Missing
            team_name="test-team",
        )
        validator = AttributeValidator(config, strict=True)

        with pytest.raises(ValidationError) as excinfo:
            validator.validate()

        assert "Missing required attribute: model.version" in str(excinfo.value)
        assert "model.version" in excinfo.value.missing_fields

    def test_internal_missing_team_name(self):
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="regression",
            model_id="model-123",
            model_version="1.0.0",
            team_name=None,  # Missing
        )
        validator = AttributeValidator(config, strict=True)

        with pytest.raises(ValidationError) as excinfo:
            validator.validate()

        assert "Missing required attribute: team.name" in str(excinfo.value)
        assert "team.name" in excinfo.value.missing_fields

    def test_internal_multiple_missing_fields(self):
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="regression",
            model_id=None,  # Missing
            model_version=None,  # Missing
            team_name="test-team",
        )
        validator = AttributeValidator(config, strict=True)

        with pytest.raises(ValidationError) as excinfo:
            validator.validate()

        assert "model.id" in excinfo.value.missing_fields
        assert "model.version" in excinfo.value.missing_fields
        assert len(excinfo.value.missing_fields) == 2

    def test_internal_empty_string_model_id(self):
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="regression",
            model_id="",  # Empty string
            model_version="1.0.0",
            team_name="test-team",
        )
        validator = AttributeValidator(config, strict=True)

        with pytest.raises(ValidationError) as excinfo:
            validator.validate()

        assert "Missing required attribute: model.id" in str(excinfo.value)

    # Additional Generative Model Tests
    def test_generative_missing_llm_model(self):
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="generative",
            llm_provider="openai",
            llm_model=None,  # Missing
            team_name="test-team",
        )
        validator = AttributeValidator(config, strict=True)

        with pytest.raises(ValidationError) as excinfo:
            validator.validate()

        assert "Missing required attribute: llm.model" in str(excinfo.value)
        assert "llm.model" in excinfo.value.missing_fields

    def test_generative_missing_team_name(self):
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="generative",
            llm_provider="openai",
            llm_model="gpt-4",
            team_name=None,  # Missing
        )
        validator = AttributeValidator(config, strict=True)

        with pytest.raises(ValidationError) as excinfo:
            validator.validate()

        assert "Missing required attribute: team.name" in str(excinfo.value)
        assert "team.name" in excinfo.value.missing_fields

    def test_generative_multiple_missing_fields(self):
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="generative",
            llm_provider=None,  # Missing
            llm_model=None,  # Missing
            team_name="test-team",
        )
        validator = AttributeValidator(config, strict=True)

        with pytest.raises(ValidationError) as excinfo:
            validator.validate()

        assert "llm.provider" in excinfo.value.missing_fields
        assert "llm.model" in excinfo.value.missing_fields
        assert len(excinfo.value.missing_fields) == 2

    def test_generative_model_id_optional(self):
        # model.id is optional for generative models
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="generative",
            model_id=None,  # Optional - should pass
            llm_provider="openai",
            llm_model="gpt-4",
            team_name="test-team",
        )
        validator = AttributeValidator(config)
        result = validator.validate()
        assert result.is_valid is True

    # Additional Vendor Model Tests
    def test_vendor_missing_team_name(self):
        config = MCAConfig(
            service_name="test-service",
            model_category="vendor",
            model_type="classification",
            vendor_name="epic",
            team_name=None,  # Missing
        )
        validator = AttributeValidator(config, strict=True)

        with pytest.raises(ValidationError) as excinfo:
            validator.validate()

        assert "Missing required attribute: team.name" in str(excinfo.value)
        assert "team.name" in excinfo.value.missing_fields

    def test_vendor_model_id_optional(self):
        # model.id is optional for vendor models
        config = MCAConfig(
            service_name="test-service",
            model_category="vendor",
            model_type="classification",
            model_id=None,  # Optional - should pass
            vendor_name="epic",
            team_name="test-team",
        )
        validator = AttributeValidator(config)
        result = validator.validate()
        assert result.is_valid is True

    # Additional Strict Mode Tests
    def test_strict_mode_raises_on_invalid_value(self):
        # Invalid model_id value + strict mode
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="regression",
            model_id="model@invalid!",  # Invalid characters
            model_version="1.0.0",
            team_name="test-team",
        )
        validator = AttributeValidator(config, strict=True)

        with pytest.raises(ValidationError) as excinfo:
            validator.validate()

        assert "Invalid value for model.id" in str(excinfo.value)

    def test_strict_mode_passes_on_valid(self):
        # All valid + strict mode = no exception
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="regression",
            model_id="model-123",
            model_version="1.0.0",
            team_name="test-team",
        )
        validator = AttributeValidator(config, strict=True)
        result = validator.validate()

        assert result.is_valid is True
        assert len(result.errors) == 0

    # Additional Permissive Mode Tests
    def test_permissive_mode_invalid_value(self):
        # Invalid value + permissive mode = no exception, but result shows invalid
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="regression",
            model_id="model@invalid!",  # Invalid
            model_version="1.0.0",
            team_name="test-team",
        )
        validator = AttributeValidator(config, strict=False)
        result = validator.validate()

        assert result.is_valid is False
        assert any("Invalid value" in err for err in result.errors)
        # Should not raise exception

    def test_permissive_mode_all_valid(self):
        # All valid + permissive mode = valid result
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="regression",
            model_id="model-123",
            model_version="1.0.0",
            team_name="test-team",
        )
        validator = AttributeValidator(config, strict=False)
        result = validator.validate()

        assert result.is_valid is True
        assert len(result.errors) == 0

    # Additional Attribute Value Tests
    def test_attribute_whitespace_only(self):
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="regression",
            model_id="model-123",
            model_version="   ",  # Whitespace only
            team_name="test-team",
        )
        validator = AttributeValidator(config, strict=True)

        with pytest.raises(ValidationError) as excinfo:
            validator.validate()

        assert "Missing required attribute: model.version" in str(excinfo.value)

    def test_attribute_empty_string(self):
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="regression",
            model_id="model-123",
            model_version="",  # Empty string
            team_name="test-team",
        )
        validator = AttributeValidator(config, strict=True)

        with pytest.raises(ValidationError) as excinfo:
            validator.validate()

        assert "Missing required attribute: model.version" in str(excinfo.value)

    def test_model_id_valid_alphanumeric(self):
        # Valid model_id with alphanumeric, dash, underscore
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="regression",
            model_id="model-123_v2",  # Valid
            model_version="1.0.0",
            team_name="test-team",
        )
        validator = AttributeValidator(config, strict=True)
        result = validator.validate()

        assert result.is_valid is True

    def test_model_id_allows_dots(self):
        # Verify dots are allowed in model_id per regex pattern
        config = MCAConfig(
            service_name="test-service",
            model_category="internal",
            model_type="regression",
            model_id="model.v2.123",  # Dots should be allowed
            model_version="1.0.0",
            team_name="test-team",
        )
        validator = AttributeValidator(config, strict=True)
        result = validator.validate()

        assert result.is_valid is True

    # Parametrized tests for additional model types
    @pytest.mark.parametrize("model_type", ["time-series", "classification", "agentic"])
    def test_validate_additional_model_types_success(self, model_type):
        """Test validation for time-series, classification, and agentic models."""
        if model_type in ("time-series", "classification"):
            # Traditional ML models - same requirements as regression
            config = MCAConfig(
                service_name="test-service",
                model_category="internal",
                model_type=model_type,
                model_id="mdl-001",
                model_version="1.0.0",
                team_name="test-team",
            )
        else:  # agentic
            # GenAI models - same requirements as generative
            config = MCAConfig(
                service_name="test-service",
                model_category="internal",
                model_type=model_type,
                llm_provider="openai",
                llm_model="gpt-4",
                team_name="test-team",
            )

        validator = AttributeValidator(config, strict=True)
        result = validator.validate()
        assert result.is_valid is True

    @pytest.mark.parametrize(
        "model_type,missing_field",
        [
            ("time-series", "model_id"),
            ("classification", "model_version"),
            ("agentic", "llm_provider"),
        ],
    )
    def test_validate_additional_model_types_failure(self, model_type, missing_field):
        """Test validation failures for time-series, classification, and agentic models."""
        if model_type in ("time-series", "classification"):
            config = MCAConfig(
                service_name="test-service",
                model_category="internal",
                model_type=model_type,
                model_id=None if missing_field == "model_id" else "mdl-001",
                model_version=None if missing_field == "model_version" else "1.0.0",
                team_name="test-team",
            )
        else:  # agentic
            config = MCAConfig(
                service_name="test-service",
                model_category="internal",
                model_type=model_type,
                llm_provider=None if missing_field == "llm_provider" else "openai",
                llm_model="gpt-4",
                team_name="test-team",
            )

        validator = AttributeValidator(config, strict=True)
        with pytest.raises(ValidationError):
            validator.validate()

    # ValidationResult Tests
    def test_validation_result_valid_str(self):
        result = ValidationResult(is_valid=True, missing_fields=[], errors=[])
        assert str(result) == "Validation passed"

    def test_validation_result_invalid_str(self):
        errors = ["Missing required attribute: model.id", "Invalid value for team.name"]
        result = ValidationResult(is_valid=False, missing_fields=["model.id"], errors=errors)

        assert "Validation failed" in str(result)
        assert "Missing required attribute: model.id" in str(result)

    def test_unknown_model_type_raises_value_error(self):
        # Test that get_validation_rules() raises ValueError for unknown combinations
        from mca_sdk.validation.rules import get_validation_rules

        with pytest.raises(ValueError) as excinfo:
            get_validation_rules("internal", "unknown-type")

        assert "No validation rules for model_category=internal, model_type=unknown-type" in str(
            excinfo.value
        )
