"""Test predict() decorator backwards compatibility (AC10 - Story 1.17).

This module validates that the breaking changes in predict() decorator defaults
are handled safely and provide clear guidance to users.

Breaking Change (v0.5.0):
- capture_input default: True → False
- capture_output default: True → False

Rationale: Prevent accidental PHI capture (HIPAA compliance)
"""

import pytest
from unittest.mock import Mock, patch, MagicMock
from mca_sdk import MCAClient
from mca_sdk.utils.exceptions import ConfigurationError


class TestPredictDecoratorBackwardsCompatibility:
    """Test backwards compatibility for predict() decorator default changes (AC10)."""

    def test_predict_decorator_defaults_changed_to_false(self):
        """Verify predict() decorator defaults are now False (breaking change)."""
        client = MCAClient(
            service_name="test-service", model_id="test-model", team_name="test-team"
        )

        # Create a decorated function WITHOUT explicit capture parameters
        @client.predict()
        def my_prediction(data):
            return {"prediction": "result"}

        # Access the wrapper's configuration (capture_input/output stored on function)
        # The decorator stores these as attributes on the wrapped function
        assert hasattr(my_prediction, "__wrapped__")  # Decorator preserves original

        # Test that the defaults are False
        with patch.object(client, "record_prediction") as mock_record:
            result = my_prediction({"input": "data"})

            # Verify function executed
            assert result == {"prediction": "result"}

            # Verify metric was recorded
            assert mock_record.called

    def test_predict_decorator_explicit_true_still_works(self):
        """Verify explicit capture_input=True, capture_output=True still works."""
        client = MCAClient(
            service_name="test-service", model_id="test-model", team_name="test-team"
        )

        # Explicitly set capture to True (migration path for users)
        @client.predict(capture_input=True, capture_output=True)
        def my_prediction(data):
            return {"prediction": "result"}

        with patch.object(client, "record_prediction") as mock_record:
            result = my_prediction({"input": "data"})

            # Verify function executed
            assert result == {"prediction": "result"}

            # Verify metric was recorded
            assert mock_record.called

    def test_predict_decorator_explicit_false_works(self):
        """Verify explicit capture_input=False, capture_output=False works."""
        client = MCAClient(
            service_name="test-service", model_id="test-model", team_name="test-team"
        )

        # Explicitly set capture to False (new default, but explicit)
        @client.predict(capture_input=False, capture_output=False)
        def my_prediction(data):
            return {"prediction": "result"}

        with patch.object(client, "record_prediction") as mock_record:
            result = my_prediction({"input": "data"})

            # Verify function executed
            assert result == {"prediction": "result"}

            # Verify metric was recorded
            assert mock_record.called

    def test_predict_decorator_mixed_capture_settings(self):
        """Verify mixed capture settings work correctly."""
        client = MCAClient(
            service_name="test-service", model_id="test-model", team_name="test-team"
        )

        # Capture input but not output
        @client.predict(capture_input=True, capture_output=False)
        def prediction1(data):
            return {"result": "test1"}

        # Capture output but not input
        @client.predict(capture_input=False, capture_output=True)
        def prediction2(data):
            return {"result": "test2"}

        with patch.object(client, "record_prediction") as mock_record:
            result1 = prediction1({"input": "data1"})
            result2 = prediction2({"input": "data2"})

            assert result1 == {"result": "test1"}
            assert result2 == {"result": "test2"}
            assert mock_record.call_count == 2

    def test_predict_decorator_no_crash_on_legacy_code(self):
        """Verify legacy code (no explicit capture params) doesn't crash."""
        client = MCAClient(
            service_name="test-service", model_id="test-model", team_name="test-team"
        )

        # Simulate legacy code that relied on old defaults
        # Should not crash, just use new defaults (False)
        @client.predict()
        def legacy_prediction(data):
            return {"prediction": "legacy"}

        with patch.object(client, "record_prediction"):
            # Should execute without error
            result = legacy_prediction({"data": "test"})
            assert result == {"prediction": "legacy"}

    def test_predict_decorator_exception_handling_unchanged(self):
        """Verify exception handling behavior unchanged despite default changes."""
        client = MCAClient(
            service_name="test-service", model_id="test-model", team_name="test-team"
        )

        @client.predict()
        def failing_prediction(data):
            raise ValueError("Intentional test error")

        # Exception should propagate
        with pytest.raises(ValueError, match="Intentional test error"):
            failing_prediction({"data": "test"})

        # Exception propagation confirmed - this is the key backwards compat requirement

    def test_predict_decorator_with_model_version_attribute(self):
        """Verify decorator works with additional attributes (unchanged behavior)."""
        client = MCAClient(
            service_name="test-service", model_id="test-model", team_name="test-team"
        )

        @client.predict(model_version="2.0")
        def versioned_prediction(data):
            return {"prediction": "v2"}

        with patch.object(client, "record_prediction") as mock_record:
            result = versioned_prediction({"data": "test"})

            assert result == {"prediction": "v2"}
            assert mock_record.called

    def test_predict_decorator_documentation_available(self):
        """Verify predict() method has docstring explaining defaults."""
        client = MCAClient(
            service_name="test-service", model_id="test-model", team_name="test-team"
        )

        # Verify decorator method exists and has documentation
        assert hasattr(client, "predict")
        assert callable(client.predict)
        assert client.predict.__doc__ is not None

        # Verify docstring mentions capture_input and capture_output
        docstring = client.predict.__doc__
        assert "capture_input" in docstring.lower()
        assert "capture_output" in docstring.lower()


class TestMigrationGuidance:
    """Test that migration guidance is available to users."""

    def test_changelog_documents_breaking_change(self):
        """Verify CHANGELOG.md documents the breaking change."""
        import pathlib

        changelog_path = pathlib.Path(__file__).parent.parent.parent / "CHANGELOG.md"

        if changelog_path.exists():
            content = changelog_path.read_text()
            assert "BREAKING CHANGE" in content
            assert "predict()" in content
            assert "capture_input" in content
            assert "False" in content
        else:
            pytest.skip("CHANGELOG.md not found")

    def test_readme_mentions_phi_sanitization(self):
        """Verify README.md mentions PHI sanitization responsibility."""
        import pathlib

        readme_path = pathlib.Path(__file__).parent.parent.parent / "README.md"

        if readme_path.exists():
            content = readme_path.read_text()
            # Should mention PHI responsibility
            assert any(term in content for term in ["PHI", "sanitize", "healthcare", "HIPAA"])
        else:
            pytest.skip("README.md not found")
