"""Tests for LLMEngine Task2 interfaces (issue #8).

Tests the new interfaces added for Task2 integration:
- capabilities() - Returns CapabilityDescriptor (delegated to BackendProvider)
- get_kv_cache() - Returns KV cache manager
- set_scheduler() - Injects scheduler instance

These interfaces enable Task2 (KV management) and Task3 (compression) to
integrate with the engine without modifying core code.
"""

from __future__ import annotations

import pytest

from sagellm_core import LLMEngine, LLMEngineConfig
from sagellm_protocol import CapabilityDescriptor


class TestCapabilitiesInterface:
    """Test LLMEngine.capabilities() interface."""

    def test_capabilities_without_backend_returns_unknown(self) -> None:
        """capabilities() should return device_type='unknown' when no backend."""
        config = LLMEngineConfig(
            model_path="test-model",
            backend_type="cpu",
        )
        engine = LLMEngine(config)

        # Before start(), backend is None
        cap = engine.capabilities()

        assert isinstance(cap, CapabilityDescriptor)
        assert cap.device_type == "unknown"

    @pytest.mark.slow
    @pytest.mark.asyncio
    async def test_capabilities_with_backend_delegates(self, tiny_model) -> None:
        """capabilities() should delegate to BackendProvider after start()."""
        config = LLMEngineConfig(
            model_path=tiny_model,
            backend_type="cpu",
        )
        engine = LLMEngine(config)

        await engine.start()
        try:
            cap = engine.capabilities()

            assert isinstance(cap, CapabilityDescriptor)
            # CPU backend should report 'cpu' device_type
            assert cap.device_type == "cpu"
            # Should have device_name
            assert cap.device_name is not None
        finally:
            await engine.stop()

    def test_capabilities_method_exists(self) -> None:
        """capabilities() method should exist on LLMEngine."""
        assert hasattr(LLMEngine, "capabilities")
        assert callable(getattr(LLMEngine, "capabilities"))


class TestGetKVCacheInterface:
    """Test LLMEngine.get_kv_cache() interface."""

    def test_get_kv_cache_returns_none_initially(self) -> None:
        """get_kv_cache() should return None when not initialized."""
        config = LLMEngineConfig(
            model_path="test-model",
            backend_type="cpu",
        )
        engine = LLMEngine(config)

        # Before any KV cache manager is set
        kv_cache = engine.get_kv_cache()
        assert kv_cache is None

    def test_get_kv_cache_method_exists(self) -> None:
        """get_kv_cache() method should exist on LLMEngine."""
        assert hasattr(LLMEngine, "get_kv_cache")
        assert callable(getattr(LLMEngine, "get_kv_cache"))


class TestSetSchedulerInterface:
    """Test LLMEngine.set_scheduler() interface."""

    def test_set_scheduler_injects_scheduler(self) -> None:
        """set_scheduler() should inject scheduler instance."""
        config = LLMEngineConfig(
            model_path="test-model",
            backend_type="cpu",
        )
        engine = LLMEngine(config)

        # Create a mock scheduler
        class MockScheduler:
            def schedule(self, requests):
                return requests

        scheduler = MockScheduler()
        engine.set_scheduler(scheduler)

        # Verify scheduler was set (access internal for test)
        assert engine._scheduler is scheduler

    def test_set_scheduler_replaces_existing(self) -> None:
        """set_scheduler() should replace existing scheduler."""
        config = LLMEngineConfig(
            model_path="test-model",
            backend_type="cpu",
        )
        engine = LLMEngine(config)

        class Scheduler1:
            pass

        class Scheduler2:
            pass

        engine.set_scheduler(Scheduler1())
        engine.set_scheduler(Scheduler2())

        assert isinstance(engine._scheduler, Scheduler2)

    def test_set_scheduler_method_exists(self) -> None:
        """set_scheduler() method should exist on LLMEngine."""
        assert hasattr(LLMEngine, "set_scheduler")
        assert callable(getattr(LLMEngine, "set_scheduler"))


class TestTask2IntegrationFlow:
    """Test typical Task2 integration flow using new interfaces."""

    @pytest.mark.slow
    @pytest.mark.asyncio
    async def test_task2_integration_flow(self, tiny_model) -> None:
        """Test complete Task2 integration flow."""
        config = LLMEngineConfig(
            model_path=tiny_model,
            backend_type="cpu",
        )
        engine = LLMEngine(config)

        await engine.start()
        try:
            # 1. Check capabilities for strategy selection
            cap = engine.capabilities()
            assert cap.device_type == "cpu"

            # 2. Get KV cache manager (None until Task2 implementation)
            kv_mgr = engine.get_kv_cache()
            # Currently None, Task2 will inject implementation
            assert kv_mgr is None

            # 3. Inject scheduler
            class TestScheduler:
                def __init__(self):
                    self.scheduled_count = 0

                def schedule(self, requests):
                    self.scheduled_count += len(requests)
                    return requests

            scheduler = TestScheduler()
            engine.set_scheduler(scheduler)
            assert engine._scheduler is scheduler

        finally:
            await engine.stop()
