"""Tests for the Router, AliasResolver, and CostTracker."""

from __future__ import annotations

from datetime import datetime  # noqa: F401 -- Pydantic forward ref resolution
from typing import TYPE_CHECKING

import pytest

from llmhosts.proxy.models import UnifiedMessage, UnifiedRequest
from llmhosts.router.aliases import AliasResolver
from llmhosts.router.cost import CostTracker, cloud_equivalent_cost, estimate_tokens
from llmhosts.router.engine import Router
from llmhosts.router.models import BackendInfo, RoutingDecision

if TYPE_CHECKING:
    from pathlib import Path

BackendInfo.model_rebuild()


# -------------------------------------------------------------------
# Helpers
# -------------------------------------------------------------------


def _make_request(model: str = "llama3.2:8b", content: str = "Hello") -> UnifiedRequest:
    return UnifiedRequest(
        model=model,
        messages=[UnifiedMessage(role="user", content=content)],
    )


def _ollama_backend(models: list[str] | None = None) -> BackendInfo:
    return BackendInfo(
        backend_type="ollama",
        url="http://127.0.0.1:11434",
        models=models or ["llama3.2:8b", "mistral:7b"],
        is_local=True,
        is_healthy=True,
    )


# -------------------------------------------------------------------
# Router: backend registration
# -------------------------------------------------------------------


@pytest.mark.unit
def test_router_starts_empty() -> None:
    router = Router()
    assert router.list_backends() == []


@pytest.mark.unit
def test_router_register_backend() -> None:
    router = Router()
    backend = _ollama_backend()
    router.register_backend(backend)
    assert len(router.list_backends()) == 1
    assert router.get_backend("ollama") is not None


@pytest.mark.unit
def test_router_unregister_backend() -> None:
    router = Router()
    router.register_backend(_ollama_backend())
    router.unregister_backend("ollama")
    assert router.list_backends() == []


# -------------------------------------------------------------------
# Router: prefer local over cloud
# -------------------------------------------------------------------


@pytest.mark.unit
async def test_router_prefers_local() -> None:
    """When a model is available locally, the router should pick Ollama."""
    router = Router()
    router.register_backend(_ollama_backend())
    decision = await router.route(_make_request("llama3.2:8b"))
    assert decision.backend_type == "ollama"
    assert decision.confidence > 0


@pytest.mark.unit
async def test_router_local_match_is_high_confidence() -> None:
    router = Router()
    router.register_backend(_ollama_backend())
    decision = await router.route(_make_request("llama3.2:8b"))
    assert decision.confidence >= 0.85


# -------------------------------------------------------------------
# Router: no backends
# -------------------------------------------------------------------


@pytest.mark.unit
async def test_router_no_backends_graceful() -> None:
    """Router with no backends should return a decision, not crash."""
    router = Router()
    decision = await router.route(_make_request())
    assert isinstance(decision, RoutingDecision)
    assert decision.backend_type == "none"
    assert decision.confidence == 0.0


@pytest.mark.unit
async def test_router_no_backends_has_reasoning() -> None:
    router = Router()
    decision = await router.route(_make_request())
    assert "no" in decision.reasoning.lower() or "backend" in decision.reasoning.lower()


# -------------------------------------------------------------------
# Router: rules
# -------------------------------------------------------------------


@pytest.mark.unit
def test_router_default_rules_exist() -> None:
    router = Router()
    rules = router.list_rules()
    assert len(rules) >= 3
    rule_names = {r.name for r in rules}
    assert "prefer_local" in rule_names
    assert "cloud_fallback" in rule_names


# -------------------------------------------------------------------
# AliasResolver
# -------------------------------------------------------------------


@pytest.mark.unit
def test_alias_resolves_gpt4() -> None:
    resolver = AliasResolver()
    result = resolver.resolve("gpt-4")
    assert result.canonical == "gpt-4o"
    assert result.is_alias is True
    assert result.provider == "openai"


@pytest.mark.unit
def test_alias_resolves_claude_sonnet() -> None:
    resolver = AliasResolver()
    result = resolver.resolve("claude-sonnet")
    assert result.canonical == "claude-3-5-sonnet-20241022"
    assert result.is_alias is True
    assert result.provider == "anthropic"


@pytest.mark.unit
def test_alias_unknown_model_passes_through() -> None:
    resolver = AliasResolver()
    result = resolver.resolve("my-custom-model")
    assert result.canonical == "my-custom-model"
    assert result.is_alias is False


@pytest.mark.unit
def test_alias_finds_local_equivalents() -> None:
    resolver = AliasResolver()
    result = resolver.resolve("gpt-4o-mini", available_models=["llama3.2:8b", "qwen2.5:7b"])
    assert len(result.local_equivalents) > 0
    assert "llama3.2:8b" in result.local_equivalents


@pytest.mark.unit
def test_alias_detects_ollama_provider() -> None:
    resolver = AliasResolver()
    result = resolver.resolve("llama3.2:8b", available_models=["llama3.2:8b"])
    assert result.provider == "ollama"
    assert result.available_locally is True


@pytest.mark.unit
def test_alias_custom_alias() -> None:
    resolver = AliasResolver()
    resolver.add_alias("my-model", "gpt-4o")
    result = resolver.resolve("my-model")
    assert result.canonical == "gpt-4o"
    assert result.is_alias is True


# -------------------------------------------------------------------
# CostTracker
# -------------------------------------------------------------------


@pytest.mark.unit
async def test_cost_tracker_init_and_close(tmp_path: Path) -> None:
    tracker = CostTracker(db_path=tmp_path / "cost.db")
    await tracker.initialize()
    assert tracker._db is not None
    await tracker.close()
    assert tracker._db is None


@pytest.mark.unit
async def test_cost_tracker_record_and_retrieve(tmp_path: Path) -> None:
    tracker = CostTracker(db_path=tmp_path / "cost.db")
    await tracker.initialize()
    try:
        decision = RoutingDecision(
            backend_type="ollama",
            backend_url="http://127.0.0.1:11434",
            model="llama3.2:8b",
            tier="rule",
            confidence=0.95,
            reasoning="Local model",
        )
        rid = await tracker.record_request(
            decision=decision,
            model_requested="llama3.2:8b",
            input_tokens=100,
            output_tokens=50,
            actual_cost=0.0,
            latency_ms=25.0,
        )
        assert isinstance(rid, str)

        logs = await tracker.get_request_log(limit=10)
        assert len(logs) == 1
        assert logs[0].model_requested == "llama3.2:8b"
        assert logs[0].actual_cost == 0.0
    finally:
        await tracker.close()


@pytest.mark.unit
async def test_cost_tracker_savings_report(tmp_path: Path) -> None:
    tracker = CostTracker(db_path=tmp_path / "cost.db")
    await tracker.initialize()
    try:
        decision = RoutingDecision(
            backend_type="ollama",
            backend_url="http://127.0.0.1:11434",
            model="llama3.2:8b",
            tier="rule",
            confidence=0.95,
            reasoning="Test",
        )
        await tracker.record_request(
            decision=decision,
            model_requested="gpt-4o-mini",
            input_tokens=500,
            output_tokens=500,
            actual_cost=0.0,
            latency_ms=100.0,
        )
        report = await tracker.get_savings("all")
        assert report.total_requests == 1
        assert report.local_requests == 1
        assert report.actual_cost == 0.0
        # Cloud equivalent should be > 0 since gpt-4o-mini has pricing
        assert report.cloud_equivalent_cost > 0
        assert report.savings > 0
    finally:
        await tracker.close()


# -------------------------------------------------------------------
# Cost helper functions
# -------------------------------------------------------------------


@pytest.mark.unit
def test_estimate_tokens() -> None:
    text = "Hello, this is a test sentence with some words."
    tokens = estimate_tokens(text)
    assert tokens > 0
    # Rough heuristic: ~4 chars per token
    assert tokens == max(1, len(text) // 4)


@pytest.mark.unit
def test_cloud_equivalent_cost_known_model() -> None:
    cost = cloud_equivalent_cost("gpt-4o-mini", input_tokens=1000, output_tokens=500)
    assert cost > 0


@pytest.mark.unit
def test_cloud_equivalent_cost_unknown_model() -> None:
    """Unknown models should fall back to the default reference model."""
    cost = cloud_equivalent_cost("totally-unknown-model", input_tokens=1000, output_tokens=500)
    # Should still return a value (fallback to gpt-4o-mini pricing)
    assert cost > 0
