"""Engine Interface Contract Tests

Validates that BaseEngine interface conforms to Protocol v0.1.
Tests are updated to use the new async API: start/stop/execute/stream/health_check.
"""

from __future__ import annotations

import pytest
import pytest_asyncio

from sagellm_core.engine import BaseEngine, HealthStatus, create_engine
from sagellm_core.engine import EngineConfig
from sagellm_protocol import Request, Response, StreamEventStart, StreamEventDelta, StreamEventEnd
from tests.test_engine import TestCPUEngine


class TestBaseEngineContract:
    """Test BaseEngine Protocol contract"""

    def test_protocol_has_required_methods(self) -> None:
        """Verify BaseEngine defines all required methods"""
        required_methods = [
            "start",
            "stop",
            "execute",
            "stream",
            "health_check",
            "is_available",
            "priority",
            "backend_type",
        ]

        for method in required_methods:
            assert hasattr(BaseEngine, method), f"BaseEngine missing method: {method}"

    def test_start_method_signature(self) -> None:
        """Verify start() method signature"""
        import inspect

        sig = inspect.signature(BaseEngine.start)
        params = [p for p in sig.parameters.keys() if p != "self"]

        assert len(params) == 0, "start() should not have additional parameters"

        # Check that it's async
        assert inspect.iscoroutinefunction(BaseEngine.start), (
            "start() must be async (coroutine function)"
        )

    def test_stop_method_signature(self) -> None:
        """Verify stop() method signature"""
        import inspect

        sig = inspect.signature(BaseEngine.stop)
        params = [p for p in sig.parameters.keys() if p != "self"]

        assert len(params) == 0, "stop() should not have additional parameters"

        # Check that it's async
        assert inspect.iscoroutinefunction(BaseEngine.stop), (
            "stop() must be async (coroutine function)"
        )

    def test_execute_method_signature(self) -> None:
        """Verify execute() method signature"""
        import inspect

        sig = inspect.signature(BaseEngine.execute)
        params = list(sig.parameters.keys())

        assert "request" in params, "execute() must have request parameter"

        # Check return type annotation
        return_annotation = sig.return_annotation
        assert return_annotation != inspect.Signature.empty, (
            "execute() must have return type annotation"
        )

        # Check that it's async
        assert inspect.iscoroutinefunction(BaseEngine.execute), (
            "execute() must be async (coroutine function)"
        )

    def test_stream_method_signature(self) -> None:
        """Verify stream() method signature"""
        import inspect

        sig = inspect.signature(BaseEngine.stream)
        params = list(sig.parameters.keys())

        assert "request" in params, "stream() must have request parameter"

        # Check return type annotation
        return_annotation = sig.return_annotation
        assert return_annotation != inspect.Signature.empty, (
            "stream() must have return type annotation"
        )

        # Check that it's async generator
        assert inspect.isasyncgenfunction(BaseEngine.stream), (
            "stream() must be async generator function"
        )

    def test_health_check_method_signature(self) -> None:
        """Verify health_check() method signature"""
        import inspect

        sig = inspect.signature(BaseEngine.health_check)

        # health_check() should not have extra parameters (except self)
        params = [p for p in sig.parameters.keys() if p != "self"]
        assert len(params) == 0, "health_check() should not have extra parameters"

        # Check return type annotation
        return_annotation = sig.return_annotation
        assert return_annotation != inspect.Signature.empty, (
            "health_check() must have return type annotation"
        )

        # Check that it's async
        assert inspect.iscoroutinefunction(BaseEngine.health_check), (
            "health_check() must be async (coroutine function)"
        )

    def test_engine_id_property(self) -> None:
        """Verify engine_id property exists"""
        assert hasattr(BaseEngine, "engine_id"), "BaseEngine must have engine_id property"

    def test_config_property(self) -> None:
        """Verify config property exists"""
        assert hasattr(BaseEngine, "config"), "BaseEngine must have config property"

    def test_is_running_property(self) -> None:
        """Verify is_running property exists"""
        assert hasattr(BaseEngine, "is_running"), "BaseEngine must have is_running property"

    def test_class_method_is_available(self) -> None:
        """Verify is_available() is a class method"""

        # is_available should be accessible as a class method
        assert hasattr(BaseEngine, "is_available"), "BaseEngine must have is_available class method"
        # It should return bool
        assert callable(BaseEngine.is_available), "is_available must be callable"

    def test_class_method_priority(self) -> None:
        """Verify priority() is a class method"""

        assert hasattr(BaseEngine, "priority"), "BaseEngine must have priority class method"
        assert callable(BaseEngine.priority), "priority must be callable"

    def test_class_method_backend_type(self) -> None:
        """Verify backend_type() is a class method"""

        assert hasattr(BaseEngine, "backend_type"), "BaseEngine must have backend_type class method"
        assert callable(BaseEngine.backend_type), "backend_type must be callable"


class TestHealthStatus:
    """Test HealthStatus enum"""

    def test_health_status_values(self) -> None:
        """Verify HealthStatus has correct values"""
        from enum import Enum

        assert issubclass(HealthStatus, Enum), "HealthStatus must be an Enum"

        required_values = ["HEALTHY", "DEGRADED", "UNHEALTHY"]
        actual_values = [e.name for e in HealthStatus]

        for value in required_values:
            assert value in actual_values, f"HealthStatus missing value: {value}"

    def test_health_status_usage(self) -> None:
        """Verify HealthStatus can be used normally"""
        assert HealthStatus.HEALTHY.name == "HEALTHY"
        assert HealthStatus.DEGRADED.name == "DEGRADED"
        assert HealthStatus.UNHEALTHY.name == "UNHEALTHY"


class TestCreateEngineFactory:
    """Test create_engine factory function"""

    def test_factory_exists(self) -> None:
        """Verify factory function exists"""
        assert callable(create_engine), "create_engine must be callable"

    def test_factory_signature(self) -> None:
        """Verify factory function signature"""
        import inspect

        sig = inspect.signature(create_engine)
        params = list(sig.parameters.keys())

        assert "cfg" in params, "create_engine must have cfg parameter"
        assert "backend" in params, "create_engine must have backend parameter"


class TestCPUEngineImplementation:
    """Test TestCPUEngine implementation of BaseEngine"""

    @pytest_asyncio.fixture
    async def cpu_engine(self) -> TestCPUEngine:
        """Create and start a TestCPUEngine for testing"""
        config = EngineConfig(
            engine_id="test-cpu-engine", model_path="sshleifer/tiny-gpt2", device="cpu"
        )
        engine = TestCPUEngine(config)
        await engine.start()
        yield engine
        await engine.stop()

    def test_cpu_engine_implements_all_methods(self, cpu_engine: TestCPUEngine) -> None:
        """Verify TestCPUEngine implements all BaseEngine methods"""
        required_methods = [
            "start",
            "stop",
            "execute",
            "stream",
            "health_check",
            "is_available",
            "priority",
            "backend_type",
        ]

        for method in required_methods:
            assert hasattr(cpu_engine, method), f"TestCPUEngine missing method: {method}"
            assert callable(getattr(cpu_engine, method)), f"{method} must be callable"

    @pytest.mark.asyncio
    async def test_cpu_engine_start_stop(self) -> None:
        """Verify TestCPUEngine start/stop lifecycle"""
        config = EngineConfig(
            engine_id="test-lifecycle", model_path="sshleifer/tiny-gpt2", device="cpu"
        )
        engine = TestCPUEngine(config)

        # Initially not running
        assert not engine.is_running

        # Start engine
        await engine.start()
        assert engine.is_running

        # Stop engine
        await engine.stop()
        assert not engine.is_running

    @pytest.mark.asyncio
    async def test_cpu_engine_health_check(self, cpu_engine: TestCPUEngine) -> None:
        """Verify TestCPUEngine health_check method"""
        health = await cpu_engine.health_check()

        # Should return bool
        assert isinstance(health, bool)
        assert health is True  # Running engine should be healthy

    @pytest.mark.asyncio
    async def test_cpu_engine_execute(self, cpu_engine: TestCPUEngine) -> None:
        """Verify TestCPUEngine execute method"""
        request = Request(
            request_id="test-001",
            trace_id="trace-001",
            model="sshleifer/tiny-gpt2",
            prompt="Hello",
            max_tokens=10,
            stream=False,
        )

        response = await cpu_engine.execute(request)

        # Should return Response object
        assert isinstance(response, Response)
        assert response.request_id == "test-001"
        assert response.trace_id == "trace-001"
        assert isinstance(response.output_text, str)
        assert len(response.output_text) > 0

    @pytest.mark.asyncio
    async def test_cpu_engine_stream(self, cpu_engine: TestCPUEngine) -> None:
        """Verify TestCPUEngine stream method"""
        request = Request(
            request_id="test-002",
            trace_id="trace-002",
            model="sshleifer/tiny-gpt2",
            prompt="Hello",
            max_tokens=10,
            stream=True,
        )

        events = []
        async for event in cpu_engine.stream(request):
            events.append(event)

        # Should yield events
        assert len(events) > 0

        # First event should be start
        assert isinstance(events[0], StreamEventStart)
        assert events[0].request_id == "test-002"

        # Last event should be end
        assert isinstance(events[-1], StreamEventEnd)
        assert events[-1].request_id == "test-002"

        # Should have delta events in between
        delta_events = [e for e in events if isinstance(e, StreamEventDelta)]
        assert len(delta_events) > 0

    def test_cpu_engine_class_methods(self) -> None:
        """Verify TestCPUEngine class methods"""
        # is_available should return True for CPU
        assert TestCPUEngine.is_available() is True

        # priority should return int
        priority = TestCPUEngine.priority()
        assert isinstance(priority, int)

        # backend_type should return string
        backend_type = TestCPUEngine.backend_type()
        assert isinstance(backend_type, str)
        assert backend_type == "cpu"


class TestProtocolAlignment:
    """Test Protocol v0.1 alignment"""

    def test_engine_imports_protocol_types(self) -> None:
        """Verify engine.py imports correct types from core modules"""
        import sagellm_core.engine as engine_module
        import inspect

        source = inspect.getsource(engine_module)

        # After refactoring, should import from sagellm_core.base_engine
        # (which itself uses sagellm_protocol)
        assert "from sagellm_core.engine import" in source, (
            "Must import BaseEngine from sagellm_core.base_engine"
        )

    def test_no_level3_dependencies(self) -> None:
        """Verify engine.py does not depend on Level 3 modules"""
        import sagellm_core.engine as engine_module
        import inspect

        source = inspect.getsource(engine_module)

        # Forbidden imports
        forbidden_imports = [
            "sagellm_kv",
            "sagellm_comm",
            "sagellm_compression",
            "from kv_cache import",
            "from comm import",
            "from compression import",
        ]

        for forbidden in forbidden_imports:
            assert forbidden not in source, (
                f"engine.py should not depend on Level 3 modules: {forbidden}"
            )
