"""
Tests for ContractValidationMiddleware.

Following Django styleguide pattern: test__<context>__<action>__<expected_outcome>
"""

from unittest.mock import Mock, patch

from django.http import HttpResponse, JsonResponse
from django.test import RequestFactory, TestCase, override_settings

from apps.api.v1.middleware import (
    ContractValidationMiddleware,
    ContractValidationSettings,
)


class TestContractValidationMiddlewareInit(TestCase):
    """Tests for ContractValidationMiddleware initialization."""

    @override_settings(DEBUG=True)
    def test__init__debug_true__enables_middleware(self):
        """Test middleware is enabled when DEBUG=True."""
        get_response = Mock()
        middleware = ContractValidationMiddleware(get_response)

        assert middleware.enabled is True

    @override_settings(DEBUG=False)
    def test__init__debug_false__disables_middleware(self):
        """Test middleware is disabled when DEBUG=False."""
        get_response = Mock()
        middleware = ContractValidationMiddleware(get_response)

        assert middleware.enabled is False


class TestContractValidationMiddlewareProcessResponse(TestCase):
    """Tests for ContractValidationMiddleware.process_response."""

    def setUp(self):
        self.factory = RequestFactory()
        self.get_response = Mock()
        self.middleware = ContractValidationMiddleware(self.get_response)
        self.middleware.enabled = True

    def test__process_response__non_api_path__skips_validation(self):
        """Test process_response skips non-API paths."""
        request = self.factory.get("/some/path/")
        response = JsonResponse({"data": "value"})

        result = self.middleware.process_response(request, response)

        assert result == response

    def test__process_response__non_json_response__skips_validation(self):
        """Test process_response skips non-JSON responses."""
        request = self.factory.get("/api/v1/parks/")
        response = HttpResponse("HTML content")

        result = self.middleware.process_response(request, response)

        assert result == response

    def test__process_response__error_status_code__skips_validation(self):
        """Test process_response skips error responses."""
        request = self.factory.get("/api/v1/parks/")
        response = JsonResponse({"error": "Not found"}, status=404)

        result = self.middleware.process_response(request, response)

        assert result == response

    @override_settings(DEBUG=False)
    def test__process_response__middleware_disabled__skips_validation(self):
        """Test process_response skips when middleware is disabled."""
        self.middleware.enabled = False
        request = self.factory.get("/api/v1/parks/")
        response = JsonResponse({"data": "value"})

        result = self.middleware.process_response(request, response)

        assert result == response


class TestContractValidationMiddlewareFilterValidation(TestCase):
    """Tests for filter metadata validation."""

    def setUp(self):
        self.factory = RequestFactory()
        self.get_response = Mock()
        self.middleware = ContractValidationMiddleware(self.get_response)
        self.middleware.enabled = True

    @patch.object(ContractValidationMiddleware, "_log_contract_violation")
    def test__validate_filter_metadata__valid_categorical_filters__no_violation(self, mock_log):
        """Test valid categorical filter format doesn't log violation."""
        request = self.factory.get("/api/v1/parks/filter-options/")
        valid_data = {
            "categorical": {
                "status": [
                    {"value": "OPERATING", "label": "Operating", "count": 10},
                    {"value": "CLOSED", "label": "Closed", "count": 5},
                ]
            }
        }
        response = JsonResponse(valid_data)

        self.middleware.process_response(request, response)

        # Should not log CATEGORICAL_OPTION_IS_STRING
        for call in mock_log.call_args_list:
            assert "CATEGORICAL_OPTION_IS_STRING" not in str(call)

    @patch.object(ContractValidationMiddleware, "_log_contract_violation")
    def test__validate_filter_metadata__string_options__logs_violation(self, mock_log):
        """Test string filter options logs contract violation."""
        request = self.factory.get("/api/v1/parks/filter-options/")
        invalid_data = {"categorical": {"status": ["OPERATING", "CLOSED"]}}  # Strings instead of objects
        response = JsonResponse(invalid_data)

        self.middleware.process_response(request, response)

        # Should log CATEGORICAL_OPTION_IS_STRING violation
        mock_log.assert_called()
        call_args = [str(call) for call in mock_log.call_args_list]
        assert any("CATEGORICAL_OPTION_IS_STRING" in arg for arg in call_args)

    @patch.object(ContractValidationMiddleware, "_log_contract_violation")
    def test__validate_filter_metadata__missing_value_property__logs_violation(self, mock_log):
        """Test filter option missing 'value' property logs violation."""
        request = self.factory.get("/api/v1/parks/filter-options/")
        invalid_data = {"categorical": {"status": [{"label": "Operating", "count": 10}]}}  # Missing 'value'
        response = JsonResponse(invalid_data)

        self.middleware.process_response(request, response)

        mock_log.assert_called()
        call_args = [str(call) for call in mock_log.call_args_list]
        assert any("MISSING_VALUE_PROPERTY" in arg for arg in call_args)

    @patch.object(ContractValidationMiddleware, "_log_contract_violation")
    def test__validate_filter_metadata__missing_label_property__logs_violation(self, mock_log):
        """Test filter option missing 'label' property logs violation."""
        request = self.factory.get("/api/v1/parks/filter-options/")
        invalid_data = {"categorical": {"status": [{"value": "OPERATING", "count": 10}]}}  # Missing 'label'
        response = JsonResponse(invalid_data)

        self.middleware.process_response(request, response)

        mock_log.assert_called()
        call_args = [str(call) for call in mock_log.call_args_list]
        assert any("MISSING_LABEL_PROPERTY" in arg for arg in call_args)


class TestContractValidationMiddlewareRangeValidation(TestCase):
    """Tests for range filter validation."""

    def setUp(self):
        self.factory = RequestFactory()
        self.get_response = Mock()
        self.middleware = ContractValidationMiddleware(self.get_response)
        self.middleware.enabled = True

    @patch.object(ContractValidationMiddleware, "_log_contract_violation")
    def test__validate_range_filter__valid_range__no_violation(self, mock_log):
        """Test valid range filter format doesn't log violation."""
        request = self.factory.get("/api/v1/rides/filter-options/")
        valid_data = {"ranges": {"height": {"min": 0, "max": 500, "step": 10, "unit": "ft"}}}
        response = JsonResponse(valid_data)

        self.middleware.process_response(request, response)

        # Should not log RANGE_FILTER_NOT_OBJECT
        for call in mock_log.call_args_list:
            assert "RANGE_FILTER_NOT_OBJECT" not in str(call)

    @patch.object(ContractValidationMiddleware, "_log_contract_violation")
    def test__validate_range_filter__missing_min_max__logs_violation(self, mock_log):
        """Test range filter missing min/max logs violation."""
        request = self.factory.get("/api/v1/rides/filter-options/")
        invalid_data = {"ranges": {"height": {"step": 10}}}  # Missing 'min' and 'max'
        response = JsonResponse(invalid_data)

        self.middleware.process_response(request, response)

        mock_log.assert_called()
        call_args = [str(call) for call in mock_log.call_args_list]
        assert any("MISSING_RANGE_PROPERTY" in arg for arg in call_args)


class TestContractValidationMiddlewareHybridValidation(TestCase):
    """Tests for hybrid response validation."""

    def setUp(self):
        self.factory = RequestFactory()
        self.get_response = Mock()
        self.middleware = ContractValidationMiddleware(self.get_response)
        self.middleware.enabled = True

    @patch.object(ContractValidationMiddleware, "_log_contract_violation")
    def test__validate_hybrid_response__valid_strategy__no_violation(self, mock_log):
        """Test valid hybrid response strategy doesn't log violation."""
        request = self.factory.get("/api/v1/parks/hybrid/")
        valid_data = {"strategy": "client_side", "data": [], "filter_metadata": {}}
        response = JsonResponse(valid_data)

        self.middleware.process_response(request, response)

        # Should not log INVALID_STRATEGY_VALUE
        for call in mock_log.call_args_list:
            assert "INVALID_STRATEGY_VALUE" not in str(call)

    @patch.object(ContractValidationMiddleware, "_log_contract_violation")
    def test__validate_hybrid_response__invalid_strategy__logs_violation(self, mock_log):
        """Test invalid hybrid strategy logs violation."""
        request = self.factory.get("/api/v1/parks/hybrid/")
        invalid_data = {"strategy": "invalid_strategy", "data": []}  # Not 'client_side' or 'server_side'
        response = JsonResponse(invalid_data)

        self.middleware.process_response(request, response)

        mock_log.assert_called()
        call_args = [str(call) for call in mock_log.call_args_list]
        assert any("INVALID_STRATEGY_VALUE" in arg for arg in call_args)


class TestContractValidationMiddlewarePaginationValidation(TestCase):
    """Tests for pagination response validation."""

    def setUp(self):
        self.factory = RequestFactory()
        self.get_response = Mock()
        self.middleware = ContractValidationMiddleware(self.get_response)
        self.middleware.enabled = True

    @patch.object(ContractValidationMiddleware, "_log_contract_violation")
    def test__validate_pagination__valid_response__no_violation(self, mock_log):
        """Test valid pagination response doesn't log violation."""
        request = self.factory.get("/api/v1/parks/")
        valid_data = {"count": 10, "next": None, "previous": None, "results": [{"id": 1}, {"id": 2}]}
        response = JsonResponse(valid_data)

        self.middleware.process_response(request, response)

        # Should not log MISSING_PAGINATION_FIELD or RESULTS_NOT_ARRAY
        for call in mock_log.call_args_list:
            assert "MISSING_PAGINATION_FIELD" not in str(call)
            assert "RESULTS_NOT_ARRAY" not in str(call)

    @patch.object(ContractValidationMiddleware, "_log_contract_violation")
    def test__validate_pagination__results_not_array__logs_violation(self, mock_log):
        """Test pagination with non-array results logs violation."""
        request = self.factory.get("/api/v1/parks/")
        invalid_data = {"count": 10, "results": "not an array"}  # Should be array
        response = JsonResponse(invalid_data)

        self.middleware.process_response(request, response)

        mock_log.assert_called()
        call_args = [str(call) for call in mock_log.call_args_list]
        assert any("RESULTS_NOT_ARRAY" in arg for arg in call_args)


class TestContractValidationSettings(TestCase):
    """Tests for ContractValidationSettings."""

    def test__should_validate_path__regular_api_path__returns_true(self):
        """Test should_validate_path returns True for regular API paths."""
        result = ContractValidationSettings.should_validate_path("/api/v1/parks/")

        assert result is True

    def test__should_validate_path__docs_path__returns_false(self):
        """Test should_validate_path returns False for docs paths."""
        result = ContractValidationSettings.should_validate_path("/api/docs/")

        assert result is False

    def test__should_validate_path__schema_path__returns_false(self):
        """Test should_validate_path returns False for schema paths."""
        result = ContractValidationSettings.should_validate_path("/api/schema/")

        assert result is False

    def test__should_validate_path__auth_path__returns_false(self):
        """Test should_validate_path returns False for auth paths."""
        result = ContractValidationSettings.should_validate_path("/api/v1/auth/login/")

        assert result is False


class TestContractValidationMiddlewareViolationSuggestions(TestCase):
    """Tests for violation suggestion messages."""

    def setUp(self):
        self.get_response = Mock()
        self.middleware = ContractValidationMiddleware(self.get_response)

    def test__get_violation_suggestion__categorical_string__returns_suggestion(self):
        """Test get_violation_suggestion returns suggestion for CATEGORICAL_OPTION_IS_STRING."""
        suggestion = self.middleware._get_violation_suggestion("CATEGORICAL_OPTION_IS_STRING")

        assert "ensure_filter_option_format" in suggestion
        assert "object arrays" in suggestion

    def test__get_violation_suggestion__missing_value__returns_suggestion(self):
        """Test get_violation_suggestion returns suggestion for MISSING_VALUE_PROPERTY."""
        suggestion = self.middleware._get_violation_suggestion("MISSING_VALUE_PROPERTY")

        assert "value" in suggestion
        assert "FilterOptionSerializer" in suggestion

    def test__get_violation_suggestion__unknown_violation__returns_default(self):
        """Test get_violation_suggestion returns default for unknown violation."""
        suggestion = self.middleware._get_violation_suggestion("UNKNOWN_VIOLATION_TYPE")

        assert "TypeScript interfaces" in suggestion
