"""Tests for the model router."""

import pytest

from backend.config import EcateConfig, ModelConfig, ProviderConfig, RoutingConfig
from backend.proxy.router import Router


def _make_config() -> EcateConfig:
    return EcateConfig(
        routing=RoutingConfig(
            vectors_path="nonexistent.json",  # No vectors → fallback mode
        ),
        providers=[
            ProviderConfig(
                id="cheap",
                type="openai-compatible",
                base_url="http://localhost:11434/v1",
                models=[
                    ModelConfig(
                        id="small-model",
                        input_cost_per_mtok=0.1,
                        output_cost_per_mtok=0.2,
                        max_context=32000,
                        supports_tools=False,
                        supports_vision=False,
                    ),
                ],
            ),
            ProviderConfig(
                id="expensive",
                type="openai",
                base_url="https://api.openai.com/v1",
                models=[
                    ModelConfig(
                        id="big-model",
                        input_cost_per_mtok=10.0,
                        output_cost_per_mtok=30.0,
                        max_context=200000,
                        supports_tools=True,
                        supports_vision=True,
                    ),
                ],
            ),
        ],
    )


@pytest.fixture
def router():
    config = _make_config()
    return Router(config)


class TestRouter:
    """Tests for the embedding-based router.
    
    Note: Without vectors loaded, the router uses fallback mode
    which returns the first matching model.
    """
    
    def test_route_returns_decision(self, router):
        """Basic routing should return a decision."""
        decision = router.route(prompt="Hello world")
        assert decision is not None
        assert decision.model is not None
        assert decision.provider is not None

    def test_no_vectors_uses_fallback(self, router):
        """Without vectors, router should use fallback strategy."""
        assert not router.has_vectors()
        decision = router.route(prompt="Test prompt")
        assert decision is not None
        assert decision.strategy == "fallback"

    def test_tools_filter(self, router):
        """Filter should only return models supporting tools."""
        decision = router.route(prompt="Use a tool", needs_tools=True)
        assert decision is not None
        assert decision.model.supports_tools is True
        assert decision.model.id == "big-model"

    def test_vision_filter(self, router):
        """Filter should only return models supporting vision."""
        decision = router.route(prompt="Analyze this image", needs_vision=True)
        assert decision is not None
        assert decision.model.supports_vision is True

    def test_context_filter(self, router):
        """Filter should respect max_context limits."""
        decision = router.route(prompt="Long context", token_estimate=100000)
        assert decision is not None
        # Only big-model has 200k context
        assert decision.model.id == "big-model"

    def test_allowed_models_filter(self, router):
        """Filter should respect allowed_models whitelist."""
        decision = router.route(
            prompt="Test",
            allowed_models=["cheap/small-model"],
        )
        assert decision is not None
        assert decision.model.id == "small-model"

    def test_no_suitable_model(self, router):
        """Should return None when no model matches constraints."""
        decision = router.route(
            prompt="Test",
            needs_tools=True,
            needs_vision=True,
            token_estimate=500000,  # Exceeds all models
        )
        assert decision is None

    def test_exclude_models(self, router):
        """Test that exclude_models filters out specified models."""
        # First call should return first model
        decision1 = router.route(prompt="Test")
        assert decision1 is not None
        first_model = decision1.model.id

        # Excluding first model should return the other one
        decision2 = router.route(
            prompt="Test",
            exclude_models={f"{decision1.provider.id}/{first_model}"},
        )
        assert decision2 is not None
        assert decision2.model.id != first_model

        # Excluding all models should return None
        decision3 = router.route(
            prompt="Test",
            exclude_models={"cheap/small-model", "expensive/big-model"},
        )
        assert decision3 is None

    def test_decision_has_metadata(self, router):
        """RouteDecision should have required fields."""
        decision = router.route(prompt="Test")
        assert decision is not None
        assert hasattr(decision, 'score')
        assert hasattr(decision, 'adjusted_score')
        assert hasattr(decision, 'cost_per_mtok')
        assert hasattr(decision, 'strategy')
        assert isinstance(decision.score, float)
        assert isinstance(decision.cost_per_mtok, float)
    
    def test_get_calibrated_models_empty(self, router):
        """Without vectors, calibrated models list should be empty."""
        assert router.get_calibrated_models() == []
