import pytest
from unittest.mock import patch


@pytest.fixture(autouse=True)
def mock_registry_client():
    with patch("mca_sdk.registry.client.RegistryClient._fetch_model_config_internal") as mock_fetch:
        # Prevent TypeError: '>=' not supported between instances of 'MagicMock' and 'int'
        from mca_sdk.registry.models import ModelConfig

        mock_fetch.return_value = ModelConfig(
            model_id="test",
            model_version="1.0",
            service_name="test-service",
            team_name="test-team",
            model_category="internal",
            model_type="regression",
        )
        yield mock_fetch


"""Configuration edge case hardening tests.

This module tests edge cases and error conditions in configuration handling
to ensure robust validation and helpful error messages.

Target coverage: Configuration validation edge cases not covered by main tests.
"""

import pytest
import tempfile
import os

from mca_sdk.config.settings import MCAConfig
from mca_sdk.utils.exceptions import ConfigurationError


class TestEmptyYAMLFile:
    """Test handling of empty YAML files."""

    def test_empty_yaml_file_missing_required_fields(self):
        """Test that empty YAML file raises ConfigurationError about YAML dictionary."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
            f.write("")
            temp_path = f.name

        try:
            with pytest.raises(ConfigurationError, match="must contain a YAML dictionary"):
                MCAConfig.from_file(temp_path)
        finally:
            os.unlink(temp_path)

    def test_yaml_with_only_comments(self):
        """Test YAML file with only comments raises ConfigurationError."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
            f.write("# This is just a comment\n# Another comment\n")
            temp_path = f.name

        try:
            with pytest.raises(ConfigurationError, match="must contain a YAML dictionary"):
                MCAConfig.from_file(temp_path)
        finally:
            os.unlink(temp_path)

    def test_yaml_with_null_values(self):
        """Test YAML file with null values raises ConfigurationError."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
            f.write("service_name: null\n")
            temp_path = f.name

        try:
            with pytest.raises(ConfigurationError, match="service_name"):
                MCAConfig.from_file(temp_path)
        finally:
            os.unlink(temp_path)


class TestMalformedYAML:
    """Test handling of malformed YAML syntax."""

    def test_unclosed_brackets(self):
        """Test YAML with unclosed brackets raises ConfigurationError with helpful message."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
            f.write("service_name: test\nlist: [item1, item2\n")
            temp_path = f.name

        try:
            with pytest.raises(ConfigurationError, match="Invalid YAML"):
                MCAConfig.from_file(temp_path)
        finally:
            os.unlink(temp_path)

    def test_wrong_indentation(self):
        """Test YAML with wrong indentation raises ConfigurationError."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
            f.write("service_name: test\n  model_id: mdl-001\n model_type: regression\n")
            temp_path = f.name

        try:
            with pytest.raises(ConfigurationError, match="Invalid YAML"):
                MCAConfig.from_file(temp_path)
        finally:
            os.unlink(temp_path)

    def test_invalid_yaml_structure(self):
        """Test YAML with invalid structure raises ConfigurationError."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
            f.write(":\n  - invalid: [yaml: {{{")
            temp_path = f.name

        try:
            with pytest.raises(ConfigurationError, match="Invalid YAML"):
                MCAConfig.from_file(temp_path)
        finally:
            os.unlink(temp_path)

    def test_duplicate_keys(self):
        """Test YAML with duplicate keys (should use last value per YAML spec)."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
            f.write("service_name: first_value\nservice_name: second_value\n")
            temp_path = f.name

        try:
            # YAML spec says last value wins for duplicate keys
            config = MCAConfig.from_file(temp_path)
            assert config.service_name == "second_value"
        finally:
            os.unlink(temp_path)


class TestInvalidDataTypes:
    """Test handling of invalid data types in configuration."""

    def test_service_name_as_int(self):
        """Test service_name as integer raises ConfigurationError."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
            f.write("service_name: 12345\n")
            temp_path = f.name

        try:
            with pytest.raises(ConfigurationError, match="must be a string"):
                MCAConfig.from_file(temp_path)
        finally:
            os.unlink(temp_path)

    def test_timeout_as_string(self):
        """Test timeout as string instead of int raises ConfigurationError."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
            f.write("service_name: test\ncollector_timeout: not_a_number\n")
            temp_path = f.name

        try:
            with pytest.raises(ConfigurationError, match="collector_timeout"):
                MCAConfig.from_file(temp_path)
        finally:
            os.unlink(temp_path)

    def test_bool_as_string(self):
        """Test bool field with invalid string value raises ConfigurationError."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
            f.write("service_name: test\nbuffering_enabled: maybe\n")
            temp_path = f.name

        try:
            with pytest.raises(ConfigurationError, match="must be a boolean"):
                MCAConfig.from_file(temp_path)
        finally:
            os.unlink(temp_path)

    def test_list_instead_of_string(self):
        """Test providing list instead of string for service_name raises ConfigurationError."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
            f.write("service_name:\n  - item1\n  - item2\n")
            temp_path = f.name

        try:
            with pytest.raises(ConfigurationError, match="must be a string"):
                MCAConfig.from_file(temp_path)
        finally:
            os.unlink(temp_path)


class TestMissingRequiredEnvVars:
    """Test handling of missing required environment variables."""

    def test_missing_service_name_env(self, monkeypatch):
        """Test loading config without MCA_SERVICE_NAME raises ConfigurationError."""
        # Clear all MCA_ env vars
        for key in list(os.environ.keys()):
            if key.startswith("MCA_"):
                monkeypatch.delenv(key, raising=False)

        with pytest.raises(ConfigurationError, match="service_name is required"):
            MCAConfig.from_env()

    def test_missing_service_name_in_load(self, monkeypatch):
        """Test MCAConfig.load() without service_name raises ConfigurationError."""
        # Clear all MCA_ env vars
        for key in list(os.environ.keys()):
            if key.startswith("MCA_"):
                monkeypatch.delenv(key, raising=False)

        with pytest.raises(ConfigurationError, match="service_name is required"):
            MCAConfig.load()


class TestEdgeCaseValues:
    """Test edge case values for configuration fields."""

    def test_empty_string_service_name(self):
        """Test empty string for service_name raises ConfigurationError."""
        with pytest.raises(ConfigurationError, match="service_name is required"):
            MCAConfig(service_name="")

    def test_whitespace_only_service_name(self):
        """Test whitespace-only service_name raises ConfigurationError."""
        with pytest.raises(ConfigurationError, match="whitespace-only"):
            MCAConfig(service_name="   ")

    def test_negative_timeout(self):
        """Test negative timeout value raises ConfigurationError."""
        with pytest.raises(ConfigurationError, match="collector_timeout must be a positive number"):
            MCAConfig(service_name="test", collector_timeout=-1)

    def test_negative_retry_attempts(self):
        """Test negative retry attempts raises ConfigurationError."""
        with pytest.raises(
            ConfigurationError, match="retry_attempts must be a non-negative integer"
        ):
            MCAConfig(service_name="test", retry_attempts=-1)

    def test_negative_max_queue_size(self):
        """Test negative max_queue_size raises ConfigurationError."""
        with pytest.raises(ConfigurationError, match="max_queue_size must be a positive integer"):
            MCAConfig(service_name="test", max_queue_size=-100)

    def test_zero_max_queue_size(self):
        """Test zero max_queue_size raises ConfigurationError."""
        with pytest.raises(ConfigurationError, match="max_queue_size must be a positive integer"):
            MCAConfig(service_name="test", max_queue_size=0)

    def test_url_without_protocol(self):
        """Test URL without protocol raises ConfigurationError."""
        with pytest.raises(ConfigurationError, match="must start with http"):
            MCAConfig(service_name="test", collector_endpoint="collector.example.com:4318")

    def test_invalid_protocol_url(self):
        """Test URL with invalid protocol raises ConfigurationError."""
        with pytest.raises(ConfigurationError):
            MCAConfig(service_name="test", collector_endpoint="ftp://collector.example.com")

    def test_http_non_localhost_registry(self):
        """Test HTTP URL for non-localhost registry raises ConfigurationError."""
        with pytest.raises(ConfigurationError, match="must use HTTPS"):
            MCAConfig(service_name="test", registry_url="http://registry.example.com")

    def test_extremely_large_queue_size(self):
        """Test extremely large queue size is accepted."""
        # Should work but might trigger memory concerns in real usage
        config = MCAConfig(service_name="test", max_queue_size=1000000)
        assert config.max_queue_size == 1000000

    def test_very_short_timeout(self):
        """Test very short timeout value is accepted."""
        config = MCAConfig(service_name="test", collector_timeout=1)
        assert config.collector_timeout == 1


class TestConfigFromDictEdgeCases:
    """Test edge cases in from_dict method."""

    def test_from_dict_with_none_values(self):
        """Test from_dict with None values uses defaults."""
        config = MCAConfig.from_dict({"service_name": "test", "model_id": None, "team_name": None})
        assert config.service_name == "test"
        assert config.model_id is None
        assert config.team_name is None

    def test_from_dict_with_extra_unknown_fields(self):
        """Test from_dict filters out unknown fields gracefully."""
        config = MCAConfig.from_dict(
            {
                "service_name": "test",
                "unknown_field_1": "value1",
                "unknown_field_2": "value2",
                "completely_invalid": {"nested": "data"},
            }
        )
        assert config.service_name == "test"
        assert not hasattr(config, "unknown_field_1")

    def test_from_dict_empty_dict(self):
        """Test from_dict with empty dict raises TypeError (missing required arg)."""
        with pytest.raises(TypeError, match="missing 1 required positional argument"):
            MCAConfig.from_dict({})


class TestURLValidationEdgeCases:
    """Test edge cases in URL validation."""

    def test_localhost_with_port(self):
        """Test localhost URL with port is accepted for HTTP."""
        config = MCAConfig(service_name="test", collector_endpoint="http://localhost:4318")
        assert config.collector_endpoint == "http://localhost:4318"

    def test_127_0_0_1_with_port(self):
        """Test 127.0.0.1 URL with port is accepted for HTTP."""
        config = MCAConfig(service_name="test", collector_endpoint="http://127.0.0.1:4318")
        assert config.collector_endpoint == "http://127.0.0.1:4318"

    def test_url_with_path(self):
        """Test URL with path component."""
        config = MCAConfig(
            service_name="test",
            collector_endpoint="https://collector.example.com/v1/traces",
        )
        assert config.collector_endpoint == "https://collector.example.com/v1/traces"

    def test_url_with_query_params(self):
        """Test URL with query parameters."""
        config = MCAConfig(
            service_name="test",
            collector_endpoint="https://collector.example.com:4318?key=value",
        )
        assert "key=value" in config.collector_endpoint


class TestModelCategoryAndTypeValidation:
    """Test edge cases in model category and type validation."""

    def test_invalid_model_category(self):
        """Test invalid model_category raises ConfigurationError with helpful message."""
        with pytest.raises(ConfigurationError, match="model_category must be one of"):
            MCAConfig(service_name="test", model_category="invalid_category")

    def test_invalid_model_type(self):
        """Test invalid model_type raises ConfigurationError with helpful message."""
        with pytest.raises(ConfigurationError, match="model_type must be one of"):
            MCAConfig(service_name="test", model_type="invalid_type")

    def test_case_sensitive_model_category(self):
        """Test model_category is case-sensitive."""
        with pytest.raises(ConfigurationError):
            MCAConfig(service_name="test", model_category="INTERNAL")

    def test_case_sensitive_model_type(self):
        """Test model_type is case-sensitive."""
        with pytest.raises(ConfigurationError):
            MCAConfig(service_name="test", model_type="REGRESSION")


class TestResilienceParametersEdgeCases:
    """Test edge cases for resilience parameters."""

    def test_base_delay_equal_to_max_delay(self):
        """Test base_delay equal to max_delay is valid."""
        config = MCAConfig(service_name="test", base_delay=5.0, max_delay=5.0)
        assert config.base_delay == 5.0
        assert config.max_delay == 5.0

    def test_backoff_multiplier_exactly_1(self):
        """Test backoff_multiplier exactly 1.0 raises ConfigurationError."""
        with pytest.raises(ConfigurationError, match="backoff_multiplier must be > 1.0"):
            MCAConfig(service_name="test", backoff_multiplier=1.0)

    def test_very_high_backoff_multiplier(self):
        """Test very high backoff_multiplier is accepted."""
        config = MCAConfig(service_name="test", backoff_multiplier=100.0)
        assert config.backoff_multiplier == 100.0

    def test_circuit_breaker_fail_max_1(self):
        """Test circuit_breaker_fail_max of 1 is valid."""
        config = MCAConfig(service_name="test", circuit_breaker_fail_max=1)
        assert config.circuit_breaker_fail_max == 1
