"""Comprehensive coverage tests for Router engine (llmhosts/router/engine.py).

Targets all uncovered lines: update_backend_health (EMA), add_rule/remove_rule,
unknown rule condition, model_prefix, prefer_local, cost_optimize,
provider_preference, cloud_fallback, _add_cloud_alternative, _cloud_cost_options,
_infer_cloud_provider, _classify_privacy, _force_local_routing, _no_backend_decision.
"""

from __future__ import annotations

from llmhosts.privacy.models import PrivacyClassification, PrivacySettings, PrivacyTier
from llmhosts.proxy.models import UnifiedMessage, UnifiedRequest
from llmhosts.router.aliases import ResolvedModel
from llmhosts.router.engine import Router
from llmhosts.router.models import AlternativeRoute, BackendInfo, CostEstimate, RoutingDecision, RoutingRule

# Pydantic forward refs
BackendInfo.model_rebuild()


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


def _make_request(model: str = "llama3.2", content: str = "Hello world") -> 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=["llama3.2"] if models is None else models,
        is_local=True,
        is_healthy=True,
    )


def _cloud_backend(backend_type: str, models: list[str] | None = None) -> BackendInfo:
    return BackendInfo(
        backend_type=backend_type,
        url=f"https://api.{backend_type}.com/v1",
        models=models or [],
        is_local=False,
        is_healthy=True,
    )


def _resolved(
    canonical: str = "llama3.2",
    provider: str = "ollama",
    local_equivalents: list[str] | None = None,
) -> ResolvedModel:
    return ResolvedModel(
        original=canonical,
        canonical=canonical,
        provider=provider,
        is_alias=False,
        local_equivalents=local_equivalents or [],
        available_locally=provider == "ollama",
    )


def _rule(
    name: str, condition: str, priority: int = 50, parameters: dict | None = None, enabled: bool = True
) -> RoutingRule:
    return RoutingRule(
        name=name,
        priority=priority,
        condition=condition,
        parameters=parameters or {},
        enabled=enabled,
    )


# ===========================================================================
# TestUpdateBackendHealth — lines 94-103
# ===========================================================================


class TestUpdateBackendHealth:
    """Tests for update_backend_health EMA latency calculation."""

    def test_update_healthy_sets_flag(self) -> None:
        router = Router()
        router.register_backend(_ollama_backend())
        router.update_backend_health("ollama", healthy=False)
        backend = router.get_backend("ollama")
        assert backend is not None
        assert backend.is_healthy is False

    def test_update_latency_first_measurement(self) -> None:
        """First latency measurement sets avg_latency_ms directly (line 102)."""
        router = Router()
        router.register_backend(_ollama_backend())
        router.update_backend_health("ollama", healthy=True, latency_ms=100.0)
        backend = router.get_backend("ollama")
        assert backend is not None
        assert backend.avg_latency_ms == 100.0

    def test_update_latency_ema_calculation(self) -> None:
        """Subsequent latency updates use EMA (lines 99-100)."""
        router = Router()
        router.register_backend(_ollama_backend())
        # First measurement
        router.update_backend_health("ollama", healthy=True, latency_ms=100.0)
        # Second measurement: EMA = 100 * 0.7 + 200 * 0.3 = 70 + 60 = 130
        router.update_backend_health("ollama", healthy=True, latency_ms=200.0)
        backend = router.get_backend("ollama")
        assert backend is not None
        assert abs(backend.avg_latency_ms - 130.0) < 0.01

    def test_update_sets_last_check(self) -> None:
        """last_check is updated on every call (line 103)."""
        router = Router()
        router.register_backend(_ollama_backend())
        router.update_backend_health("ollama", healthy=True, latency_ms=50.0)
        backend = router.get_backend("ollama")
        assert backend is not None
        assert backend.last_check is not None

    def test_update_nonexistent_backend_is_noop(self) -> None:
        """Updating a non-existent backend does nothing (line 94-95)."""
        router = Router()
        router.update_backend_health("nonexistent", healthy=True, latency_ms=50.0)
        assert router.get_backend("nonexistent") is None

    def test_zero_latency_skips_ema(self) -> None:
        """latency_ms=0 does not change avg_latency_ms (line 97)."""
        router = Router()
        router.register_backend(_ollama_backend())
        router.update_backend_health("ollama", healthy=True, latency_ms=100.0)
        router.update_backend_health("ollama", healthy=True, latency_ms=0.0)
        backend = router.get_backend("ollama")
        assert backend is not None
        assert backend.avg_latency_ms == 100.0


# ===========================================================================
# TestRuleManagement — lines 119-120, 124-126
# ===========================================================================


class TestRuleManagement:
    """Tests for add_rule and remove_rule."""

    def test_add_rule_appends_and_sorts(self) -> None:
        """add_rule appends and re-sorts by priority (lines 119-120)."""
        router = Router()
        initial_count = len(router.list_rules())
        new_rule = _rule("custom_rule", "model_exact", priority=1)
        router.add_rule(new_rule)
        rules = router.list_rules()
        assert len(rules) == initial_count + 1
        assert rules[0].name == "custom_rule"  # priority 1 is first

    def test_add_rule_maintains_sort_order(self) -> None:
        router = Router()
        router.add_rule(_rule("z_low_priority", "model_exact", priority=500))
        router.add_rule(_rule("a_high_priority", "model_exact", priority=1))
        rules = router.list_rules()
        assert rules[0].name == "a_high_priority"
        assert rules[-1].name == "z_low_priority"

    def test_remove_rule_existing(self) -> None:
        """remove_rule returns True when rule found (lines 124-126)."""
        router = Router()
        count_before = len(router.list_rules())
        result = router.remove_rule("exact_model_match")
        assert result is True
        assert len(router.list_rules()) == count_before - 1

    def test_remove_rule_nonexistent(self) -> None:
        """remove_rule returns False when rule not found."""
        router = Router()
        result = router.remove_rule("nonexistent_rule")
        assert result is False


# ===========================================================================
# TestEvaluateRuleUnknown — lines 172, 203-204
# ===========================================================================


class TestEvaluateRuleUnknown:
    """Tests for unknown rule condition and disabled rule skip."""

    def test_unknown_condition_logs_warning_returns_none(self) -> None:
        """Unknown rule condition returns None with a warning (lines 203-204)."""
        router = Router()
        rule = _rule("bad_rule", "nonexistent_condition")
        result = router._evaluate_rule(
            rule,
            _make_request(),
            _resolved(),
            [],
        )
        assert result is None

    async def test_disabled_rule_is_skipped(self) -> None:
        """Disabled rules are skipped during routing (line 172)."""
        router = Router()
        router.register_backend(_ollama_backend(["llama3.2"]))
        # Disable all default rules
        for rule in router.list_rules():
            rule.enabled = False
        # Route should fall through to _no_backend_decision
        decision = await router.route(_make_request("llama3.2"))
        # With all rules disabled but backend healthy, should get fallback
        assert decision.backend_type in ("ollama", "none")


# ===========================================================================
# TestRuleModelPrefix — lines 242-254
# ===========================================================================


class TestRuleModelPrefix:
    """Tests for _rule_model_prefix."""

    def test_prefix_match_routes_to_target(self) -> None:
        """Prefix match on canonical name routes to configured backend (lines 242-254)."""
        router = Router()
        router.register_backend(_cloud_backend("openai", ["gpt-4o"]))
        rule = _rule("gpt_prefix", "model_prefix", parameters={"prefix": "gpt-", "backend": "openai"})
        result = router._rule_model_prefix(
            rule,
            _make_request("gpt-4o"),
            _resolved("gpt-4o", "openai"),
            [],
        )
        assert result is not None
        assert result.backend_type == "openai"
        assert result.confidence == 0.95

    def test_prefix_match_on_request_model(self) -> None:
        """Prefix match falls back to request.model when canonical doesn't match."""
        router = Router()
        router.register_backend(_cloud_backend("openai", []))
        rule = _rule("gpt_prefix", "model_prefix", parameters={"prefix": "gpt-", "backend": "openai"})
        # canonical = "custom-model" doesn't match, but request.model = "gpt-4o" does
        result = router._rule_model_prefix(
            rule,
            _make_request("gpt-4o"),
            _resolved("custom-model", "openai"),
            [],
        )
        assert result is not None

    def test_no_prefix_param_returns_none(self) -> None:
        """Missing prefix parameter returns None (line 244)."""
        router = Router()
        rule = _rule("bad_prefix", "model_prefix", parameters={})
        result = router._rule_model_prefix(rule, _make_request(), _resolved(), [])
        assert result is None

    def test_no_backend_param_returns_none(self) -> None:
        """Missing backend parameter returns None (line 244)."""
        router = Router()
        rule = _rule("bad_prefix", "model_prefix", parameters={"prefix": "gpt-"})
        result = router._rule_model_prefix(rule, _make_request("gpt-4o"), _resolved("gpt-4o", "openai"), [])
        assert result is None

    def test_no_prefix_match_returns_none(self) -> None:
        """Non-matching prefix returns None (line 248)."""
        router = Router()
        router.register_backend(_cloud_backend("openai"))
        rule = _rule("gpt_prefix", "model_prefix", parameters={"prefix": "gpt-", "backend": "openai"})
        result = router._rule_model_prefix(
            rule,
            _make_request("claude-3-opus"),
            _resolved("claude-3-opus", "anthropic"),
            [],
        )
        assert result is None

    def test_unhealthy_backend_returns_none(self) -> None:
        """Unhealthy target backend returns None (line 252)."""
        router = Router()
        backend = _cloud_backend("openai")
        backend.is_healthy = False
        router.register_backend(backend)
        rule = _rule("gpt_prefix", "model_prefix", parameters={"prefix": "gpt-", "backend": "openai"})
        result = router._rule_model_prefix(rule, _make_request("gpt-4o"), _resolved("gpt-4o", "openai"), [])
        assert result is None

    def test_unregistered_backend_returns_none(self) -> None:
        """Non-registered target backend returns None (line 251)."""
        router = Router()
        rule = _rule("gpt_prefix", "model_prefix", parameters={"prefix": "gpt-", "backend": "openai"})
        result = router._rule_model_prefix(rule, _make_request("gpt-4o"), _resolved("gpt-4o", "openai"), [])
        assert result is None


# ===========================================================================
# TestRulePreferLocal — lines 278-334
# ===========================================================================


class TestRulePreferLocal:
    """Tests for _rule_prefer_local."""

    def test_exact_model_on_ollama(self) -> None:
        """Direct model match on Ollama (lines 281-295)."""
        router = Router()
        router.register_backend(_ollama_backend(["llama3.2"]))
        rule = _rule("prefer_local", "prefer_local")
        result = router._rule_prefer_local(
            rule,
            _make_request("llama3.2"),
            _resolved("llama3.2", "ollama"),
            [],
        )
        assert result is not None
        assert result.backend_type == "ollama"
        assert result.model == "llama3.2"
        assert result.factors["match_type"] == "direct_local"

    def test_case_insensitive_match(self) -> None:
        """Case insensitive matching (line 278-283)."""
        router = Router()
        router.register_backend(_ollama_backend(["Llama3.2"]))
        rule = _rule("prefer_local", "prefer_local")
        result = router._rule_prefer_local(
            rule,
            _make_request("llama3.2"),
            _resolved("llama3.2", "ollama"),
            [],
        )
        assert result is not None
        assert result.model == "Llama3.2"

    def test_base_name_match_without_tag(self) -> None:
        """Base name match without tag (lines 298-310)."""
        router = Router()
        router.register_backend(_ollama_backend(["llama3.2:8b"]))
        rule = _rule("prefer_local", "prefer_local")
        result = router._rule_prefer_local(
            rule,
            _make_request("llama3.2"),
            _resolved("llama3.2", "ollama"),
            [],
        )
        assert result is not None
        assert result.model == "llama3.2:8b"
        assert result.factors["match_type"] == "base_name_local"

    def test_quality_equivalent_fallback(self) -> None:
        """Local equivalent model used when no direct match (lines 312-332)."""
        router = Router()
        router.register_backend(_ollama_backend(["llama3.2:8b"]))
        rule = _rule("prefer_local", "prefer_local")
        result = router._rule_prefer_local(
            rule,
            _make_request("gpt-4o-mini"),
            _resolved("gpt-4o-mini", "openai", local_equivalents=["llama3.2:8b"]),
            [],
        )
        assert result is not None
        assert result.model == "llama3.2:8b"
        assert result.confidence == 0.75
        assert result.factors["match_type"] == "quality_equivalent"

    def test_ollama_not_registered_returns_none(self) -> None:
        """No Ollama backend returns None (line 275)."""
        router = Router()
        rule = _rule("prefer_local", "prefer_local")
        result = router._rule_prefer_local(rule, _make_request(), _resolved(), [])
        assert result is None

    def test_ollama_unhealthy_returns_none(self) -> None:
        """Unhealthy Ollama returns None (line 276)."""
        router = Router()
        backend = _ollama_backend()
        backend.is_healthy = False
        router.register_backend(backend)
        rule = _rule("prefer_local", "prefer_local")
        result = router._rule_prefer_local(rule, _make_request(), _resolved(), [])
        assert result is None

    def test_no_match_no_equivalent_returns_none(self) -> None:
        """No local match and no equivalents returns None (line 334)."""
        router = Router()
        router.register_backend(_ollama_backend(["mistral:7b"]))
        rule = _rule("prefer_local", "prefer_local")
        result = router._rule_prefer_local(
            rule,
            _make_request("gpt-4o"),
            _resolved("gpt-4o", "openai"),
            [],
        )
        assert result is None

    def test_cloud_alternative_added(self) -> None:
        """Cloud alternative is appended when local preferred (line 286)."""
        router = Router()
        router.register_backend(_ollama_backend(["llama3.2"]))
        rule = _rule("prefer_local", "prefer_local")
        alts: list[AlternativeRoute] = []
        router._rule_prefer_local(
            rule,
            _make_request("gpt-4o-mini"),
            _resolved("gpt-4o-mini", "openai"),
            alts,
        )
        # No direct match, no equivalents — alt should not be added
        assert len(alts) == 0


# ===========================================================================
# TestRuleCostOptimize — lines 349-365, 383-397
# ===========================================================================


class TestRuleCostOptimize:
    """Tests for _rule_cost_optimize."""

    def test_local_model_wins_free(self) -> None:
        """Local model always wins at $0 (lines 349-360)."""
        router = Router()
        router.register_backend(_ollama_backend(["llama3.2"]))
        rule = _rule("cost", "cost_optimize")
        result = router._rule_cost_optimize(
            rule,
            _make_request("llama3.2"),
            _resolved("llama3.2", "ollama"),
            [],
        )
        assert result is not None
        assert result.backend_type == "ollama"
        assert result.factors["estimated_cost"] == 0.0

    def test_local_equivalent_wins_over_cloud(self) -> None:
        """Local equivalent model is free vs cloud (lines 362-377)."""
        router = Router()
        router.register_backend(_ollama_backend(["llama3.2:8b"]))
        rule = _rule("cost", "cost_optimize")
        result = router._rule_cost_optimize(
            rule,
            _make_request("gpt-4o-mini"),
            _resolved("gpt-4o-mini", "openai", local_equivalents=["llama3.2:8b"]),
            [],
        )
        assert result is not None
        assert result.backend_type == "ollama"
        assert result.model == "llama3.2:8b"

    def test_cheapest_cloud_selected(self) -> None:
        """Among cloud options, the cheapest is selected (lines 383-408)."""
        router = Router()
        router.register_backend(_cloud_backend("openai", ["gpt-4o-mini"]))
        router.register_backend(_cloud_backend("openrouter", ["gpt-4o-mini"]))
        rule = _rule("cost", "cost_optimize")
        alts: list[AlternativeRoute] = []
        result = router._rule_cost_optimize(
            rule,
            _make_request("gpt-4o-mini", "Hello"),
            _resolved("gpt-4o-mini", "openai"),
            alts,
        )
        assert result is not None
        assert result.backend_type in ("openai", "openrouter")
        assert result.factors["estimated_cost"] > 0

    def test_cloud_alternatives_recorded(self) -> None:
        """More expensive cloud options are recorded as alternatives (lines 386-394)."""
        router = Router()
        router.register_backend(_cloud_backend("openai"))
        router.register_backend(_cloud_backend("openrouter"))
        rule = _rule("cost", "cost_optimize")
        alts: list[AlternativeRoute] = []
        router._rule_cost_optimize(
            rule,
            _make_request("gpt-4o-mini", "Hello"),
            _resolved("gpt-4o-mini", "openai"),
            alts,
        )
        # At least one option should be selected; if 2 backends match, 1 becomes an alternative
        if len(alts) > 0:
            assert "More expensive" in alts[0].reason_not_chosen

    def test_no_backends_returns_none(self) -> None:
        """No backends at all returns None."""
        router = Router()
        rule = _rule("cost", "cost_optimize")
        result = router._rule_cost_optimize(rule, _make_request(), _resolved("gpt-4o-mini", "openai"), [])
        assert result is None

    def test_no_cloud_pricing_returns_none(self) -> None:
        """Model with no cloud pricing returns None when no local (line 596-597)."""
        router = Router()
        router.register_backend(_cloud_backend("openai"))
        rule = _rule("cost", "cost_optimize")
        result = router._rule_cost_optimize(
            rule,
            _make_request("some-unknown-model"),
            _resolved("some-unknown-model", "openai"),
            [],
        )
        assert result is None


# ===========================================================================
# TestRuleProviderPreference — lines 422-442
# ===========================================================================


class TestRuleProviderPreference:
    """Tests for _rule_provider_preference."""

    def test_preferred_provider_with_model(self) -> None:
        """Route to preferred provider when model is available (lines 431-439)."""
        router = Router()
        router.register_backend(_cloud_backend("openai", ["gpt-4o"]))
        rule = _rule("pref", "provider_preference", parameters={"provider": "openai"})
        result = router._rule_provider_preference(
            rule,
            _make_request("gpt-4o"),
            _resolved("gpt-4o", "openai"),
            [],
        )
        assert result is not None
        assert result.backend_type == "openai"
        assert result.confidence == 0.85

    def test_preferred_provider_matching_resolved_provider(self) -> None:
        """Route when resolved.provider matches preferred (line 431)."""
        router = Router()
        router.register_backend(_cloud_backend("anthropic"))
        rule = _rule("pref", "provider_preference", parameters={"provider": "anthropic"})
        result = router._rule_provider_preference(
            rule,
            _make_request("claude-3-5-sonnet-20241022"),
            _resolved("claude-3-5-sonnet-20241022", "anthropic"),
            [],
        )
        assert result is not None
        assert result.backend_type == "anthropic"

    def test_no_provider_param_returns_none(self) -> None:
        """Missing provider parameter returns None (line 423)."""
        router = Router()
        rule = _rule("pref", "provider_preference", parameters={})
        result = router._rule_provider_preference(rule, _make_request(), _resolved(), [])
        assert result is None

    def test_unregistered_provider_returns_none(self) -> None:
        """Unregistered provider returns None (line 427)."""
        router = Router()
        rule = _rule("pref", "provider_preference", parameters={"provider": "openai"})
        result = router._rule_provider_preference(rule, _make_request("gpt-4o"), _resolved("gpt-4o", "openai"), [])
        assert result is None

    def test_unhealthy_provider_returns_none(self) -> None:
        """Unhealthy preferred provider returns None (line 427)."""
        router = Router()
        backend = _cloud_backend("openai", ["gpt-4o"])
        backend.is_healthy = False
        router.register_backend(backend)
        rule = _rule("pref", "provider_preference", parameters={"provider": "openai"})
        result = router._rule_provider_preference(rule, _make_request("gpt-4o"), _resolved("gpt-4o", "openai"), [])
        assert result is None

    def test_model_not_on_provider_returns_none(self) -> None:
        """Model not available and resolved.provider doesn't match returns None (line 442)."""
        router = Router()
        router.register_backend(_cloud_backend("openai"))
        rule = _rule("pref", "provider_preference", parameters={"provider": "openai"})
        result = router._rule_provider_preference(
            rule,
            _make_request("claude-opus"),
            _resolved("claude-3-opus-20240229", "anthropic"),
            [],
        )
        assert result is None


# ===========================================================================
# TestRuleCloudFallback — lines 462-489
# ===========================================================================


class TestRuleCloudFallback:
    """Tests for _rule_cloud_fallback."""

    def test_cloud_provider_direct(self) -> None:
        """Direct cloud provider routing (lines 464-474)."""
        router = Router()
        router.register_backend(_cloud_backend("openai"))
        rule = _rule("fallback", "cloud_fallback")
        result = router._rule_cloud_fallback(
            rule,
            _make_request("gpt-4o"),
            _resolved("gpt-4o", "openai"),
            [],
        )
        assert result is not None
        assert result.backend_type == "openai"
        assert result.confidence == 0.90

    def test_inferred_provider_from_unknown(self) -> None:
        """Infer provider when resolved.provider is 'unknown' (lines 459-462)."""
        router = Router()
        router.register_backend(_cloud_backend("openai"))
        rule = _rule("fallback", "cloud_fallback")
        result = router._rule_cloud_fallback(
            rule,
            _make_request("gpt-4o"),
            _resolved("gpt-4o", "unknown"),
            [],
        )
        assert result is not None
        assert result.backend_type == "openai"

    def test_openrouter_fallback(self) -> None:
        """OpenRouter universal fallback when provider not registered (lines 477-487)."""
        router = Router()
        router.register_backend(_cloud_backend("openrouter"))
        rule = _rule("fallback", "cloud_fallback")
        result = router._rule_cloud_fallback(
            rule,
            _make_request("gpt-4o"),
            _resolved("gpt-4o", "openai"),
            [],
        )
        assert result is not None
        assert result.backend_type == "openrouter"
        assert result.confidence == 0.70

    def test_ollama_provider_returns_none(self) -> None:
        """Ollama provider skips cloud fallback (line 456)."""
        router = Router()
        rule = _rule("fallback", "cloud_fallback")
        result = router._rule_cloud_fallback(
            rule,
            _make_request("llama3.2"),
            _resolved("llama3.2", "ollama"),
            [],
        )
        assert result is None

    def test_unknown_provider_no_inference_returns_none(self) -> None:
        """Unknown provider that can't be inferred returns None (line 461)."""
        router = Router()
        rule = _rule("fallback", "cloud_fallback")
        result = router._rule_cloud_fallback(
            rule,
            _make_request("some-custom-model"),
            _resolved("some-custom-model", "unknown"),
            [],
        )
        assert result is None

    def test_no_cloud_backends_returns_none(self) -> None:
        """No matching cloud or openrouter backend returns None (line 489)."""
        router = Router()
        rule = _rule("fallback", "cloud_fallback")
        result = router._rule_cloud_fallback(
            rule,
            _make_request("gpt-4o"),
            _resolved("gpt-4o", "openai"),
            [],
        )
        assert result is None


# ===========================================================================
# TestAddCloudAlternative — lines 568-575
# ===========================================================================


class TestAddCloudAlternative:
    """Tests for _add_cloud_alternative."""

    def test_adds_alternative_for_known_cloud_model(self) -> None:
        """Adds alternative with cost estimate for a priced cloud model (lines 568-575)."""
        router = Router()
        alts: list[AlternativeRoute] = []
        resolved = _resolved("gpt-4o-mini", "openai")
        router._add_cloud_alternative(resolved, alts, "Local preferred")
        assert len(alts) == 1
        assert alts[0].backend_type == "openai"
        assert "est. $" in alts[0].reason_not_chosen

    def test_adds_alternative_for_unpriced_model(self) -> None:
        """Adds alternative without cost note when pricing is unknown."""
        router = Router()
        alts: list[AlternativeRoute] = []
        resolved = _resolved("some-model", "anthropic")
        router._add_cloud_alternative(resolved, alts, "Local preferred")
        assert len(alts) == 1
        assert "est. $" not in alts[0].reason_not_chosen

    def test_skips_ollama_provider(self) -> None:
        """Does not add alternative for ollama provider (line 568)."""
        router = Router()
        alts: list[AlternativeRoute] = []
        resolved = _resolved("llama3.2", "ollama")
        router._add_cloud_alternative(resolved, alts, "Reason")
        assert len(alts) == 0

    def test_skips_unknown_provider(self) -> None:
        """Does not add alternative for unknown provider (line 568)."""
        router = Router()
        alts: list[AlternativeRoute] = []
        resolved = _resolved("custom-model", "unknown")
        router._add_cloud_alternative(resolved, alts, "Reason")
        assert len(alts) == 0


# ===========================================================================
# TestCloudCostOptions — lines 600-620
# ===========================================================================


class TestCloudCostOptions:
    """Tests for _cloud_cost_options."""

    def test_returns_options_for_priced_model(self) -> None:
        """Returns cost options for a model with known pricing (lines 600-619)."""
        router = Router()
        router.register_backend(_cloud_backend("openai"))
        request = _make_request("gpt-4o-mini", "Hello world, this is a test message")
        resolved = _resolved("gpt-4o-mini", "openai")
        options = router._cloud_cost_options(resolved, request)
        assert len(options) >= 1
        assert options[0]["backend_type"] == "openai"
        assert isinstance(options[0]["cost"], CostEstimate)
        assert options[0]["cost"].total_estimated_cost > 0

    def test_openrouter_included_as_universal(self) -> None:
        """OpenRouter is included as universal backend (lines 607-608)."""
        router = Router()
        router.register_backend(_cloud_backend("openrouter"))
        request = _make_request("gpt-4o-mini", "Hello")
        resolved = _resolved("gpt-4o-mini", "openai")
        options = router._cloud_cost_options(resolved, request)
        assert any(o["backend_type"] == "openrouter" for o in options)

    def test_local_backends_excluded(self) -> None:
        """Local backends are excluded from cloud cost options (line 604-605)."""
        router = Router()
        router.register_backend(_ollama_backend())
        request = _make_request("gpt-4o-mini", "Hello")
        resolved = _resolved("gpt-4o-mini", "openai")
        options = router._cloud_cost_options(resolved, request)
        assert all(o["backend_type"] != "ollama" for o in options)

    def test_no_pricing_returns_empty(self) -> None:
        """Model without pricing returns empty list (line 596-597)."""
        router = Router()
        router.register_backend(_cloud_backend("openai"))
        request = _make_request("unknown-model")
        resolved = _resolved("unknown-model", "openai")
        options = router._cloud_cost_options(resolved, request)
        assert options == []

    def test_mismatched_provider_excluded(self) -> None:
        """Backend that doesn't match model provider is excluded (line 608)."""
        router = Router()
        router.register_backend(_cloud_backend("anthropic"))
        request = _make_request("gpt-4o-mini", "Hello")
        resolved = _resolved("gpt-4o-mini", "openai")
        options = router._cloud_cost_options(resolved, request)
        assert all(o["backend_type"] != "anthropic" for o in options)


# ===========================================================================
# TestInferCloudProvider — lines 627, 629, 631, 633, 635
# ===========================================================================


class TestInferCloudProvider:
    """Tests for _infer_cloud_provider."""

    def test_gpt_prefix_returns_openai(self) -> None:
        assert Router._infer_cloud_provider("gpt-4o") == "openai"

    def test_o1_prefix_returns_openai(self) -> None:
        assert Router._infer_cloud_provider("o1-mini") == "openai"

    def test_o3_prefix_returns_openai(self) -> None:
        assert Router._infer_cloud_provider("o3-mini") == "openai"

    def test_claude_prefix_returns_anthropic(self) -> None:
        assert Router._infer_cloud_provider("claude-3-5-sonnet-20241022") == "anthropic"

    def test_gemini_prefix_returns_google(self) -> None:
        assert Router._infer_cloud_provider("gemini-1.5-pro") == "google"

    def test_mistral_prefix_returns_mistral(self) -> None:
        assert Router._infer_cloud_provider("mistral-large-latest") == "mistral"

    def test_command_prefix_returns_cohere(self) -> None:
        assert Router._infer_cloud_provider("command-r-plus") == "cohere"

    def test_unknown_model_returns_none(self) -> None:
        assert Router._infer_cloud_provider("llama3.2") is None

    def test_case_insensitive(self) -> None:
        assert Router._infer_cloud_provider("GPT-4o") == "openai"
        assert Router._infer_cloud_provider("Claude-3-opus") == "anthropic"


# ===========================================================================
# TestPrivacyClassification — lines 649, 656
# ===========================================================================


class TestPrivacyClassification:
    """Tests for _classify_privacy — override and None content."""

    def test_none_content_uses_empty_string(self) -> None:
        """None content is replaced with empty string (line 649)."""
        router = Router()
        request = UnifiedRequest(
            model="llama3.2",
            messages=[UnifiedMessage(role="user", content=None)],
        )
        result = router._classify_privacy(request)
        assert result.tier == PrivacyTier.PUBLIC

    def test_strict_mode_does_not_override_public_string_comparison(self) -> None:
        """Strict mode 'private' does NOT override PUBLIC due to string comparison ('private' < 'public')."""
        settings = PrivacySettings(privacy_mode="strict")
        router = Router(privacy_settings=settings)
        request = _make_request("llama3.2", "Hello, how are you?")
        result = router._classify_privacy(request)
        # "private" < "public" in string ordering, so override condition (line 655) is False
        assert result.tier == PrivacyTier.PUBLIC

    def test_paranoid_mode_overrides_to_sensitive(self) -> None:
        """Privacy mode 'paranoid' overrides tier to SENSITIVE (line 656)."""
        settings = PrivacySettings(privacy_mode="paranoid")
        router = Router(privacy_settings=settings)
        request = _make_request("llama3.2", "Just a normal message")
        result = router._classify_privacy(request)
        assert result.tier == PrivacyTier.SENSITIVE

    def test_paranoid_overrides_private_to_sensitive(self) -> None:
        """Paranoid mode overrides PRIVATE (email PII) to SENSITIVE (line 655-656)."""
        settings = PrivacySettings(privacy_mode="paranoid")
        router = Router(privacy_settings=settings)
        # Email triggers PRIVATE, but paranoid ("sensitive") > "private" in string comparison
        request = _make_request("llama3.2", "my email is test@example.com")
        result = router._classify_privacy(request)
        assert result.tier == PrivacyTier.SENSITIVE
        assert "paranoid" in result.reasoning

    def test_standard_mode_no_override(self) -> None:
        """Standard mode does not override (override is None)."""
        router = Router()
        request = _make_request("llama3.2", "Hello")
        result = router._classify_privacy(request)
        assert result.tier == PrivacyTier.PUBLIC

    def test_override_only_when_higher_than_detected(self) -> None:
        """Override only applies when override > detected tier (line 655)."""
        settings = PrivacySettings(privacy_mode="strict")  # override = PRIVATE
        router = Router(privacy_settings=settings)
        # Content with SSN triggers SENSITIVE which is already > PRIVATE override
        request = _make_request("llama3.2", "my SSN is 123-45-6789")
        result = router._classify_privacy(request)
        # SENSITIVE > PRIVATE, so override should NOT apply
        assert result.tier == PrivacyTier.SENSITIVE


# ===========================================================================
# TestForceLocalRouting — lines 738-739, 762-763
# ===========================================================================


class TestForceLocalRouting:
    """Tests for _force_local_routing."""

    def test_exact_model_reroute(self) -> None:
        """Exact model match on Ollama for privacy reroute (lines 738-739)."""
        router = Router()
        router.register_backend(_ollama_backend(["gpt-4o"]))
        original = RoutingDecision(
            backend_type="openai",
            backend_url="https://api.openai.com/v1",
            model="gpt-4o",
            tier="rule",
            confidence=0.9,
            reasoning="test",
            latency_ms=5.0,
        )
        privacy = PrivacyClassification(
            tier=PrivacyTier.PRIVATE,
            pii_types_found=["email"],
            pii_count=1,
            reasoning="email found",
        )
        result = router._force_local_routing(original, _resolved("gpt-4o", "openai"), privacy)
        assert result is not None
        assert result.backend_type == "ollama"
        assert result.model == "gpt-4o"
        assert result.factors["privacy_enforced"] is True

    def test_local_equivalent_reroute(self) -> None:
        """Local equivalent used for privacy reroute (lines 762-763)."""
        router = Router()
        router.register_backend(_ollama_backend(["llama3.2:8b"]))
        original = RoutingDecision(
            backend_type="openai",
            backend_url="https://api.openai.com/v1",
            model="gpt-4o-mini",
            tier="rule",
            confidence=0.9,
            reasoning="test",
            latency_ms=3.0,
        )
        privacy = PrivacyClassification(
            tier=PrivacyTier.PRIVATE,
            pii_types_found=["email"],
            pii_count=1,
            reasoning="email found",
        )
        resolved = _resolved("gpt-4o-mini", "openai", local_equivalents=["llama3.2:8b"])
        result = router._force_local_routing(original, resolved, privacy)
        assert result is not None
        assert result.backend_type == "ollama"
        assert result.model == "llama3.2:8b"
        assert result.factors["equivalent_model"] == "llama3.2:8b"

    def test_fallback_to_any_local_model(self) -> None:
        """Falls back to any available local model (lines 786-808)."""
        router = Router()
        router.register_backend(_ollama_backend(["mistral:7b"]))
        original = RoutingDecision(
            backend_type="openai",
            backend_url="https://api.openai.com/v1",
            model="gpt-4o",
            tier="rule",
            confidence=0.9,
            reasoning="test",
        )
        privacy = PrivacyClassification(
            tier=PrivacyTier.SENSITIVE,
            pii_types_found=["ssn"],
            pii_count=1,
            reasoning="SSN found",
        )
        result = router._force_local_routing(original, _resolved("gpt-4o", "openai"), privacy)
        assert result is not None
        assert result.model == "mistral:7b"
        assert result.confidence == 0.3
        assert result.factors["fallback"] is True

    def test_no_ollama_returns_none(self) -> None:
        """No Ollama backend returns None (line 728-729)."""
        router = Router()
        original = RoutingDecision(
            backend_type="openai",
            backend_url="",
            model="gpt-4o",
            tier="rule",
            confidence=0.9,
            reasoning="test",
        )
        privacy = PrivacyClassification(tier=PrivacyTier.PRIVATE, pii_types_found=["email"])
        result = router._force_local_routing(original, _resolved("gpt-4o", "openai"), privacy)
        assert result is None

    def test_ollama_no_models_returns_none(self) -> None:
        """Ollama with empty models list returns None (line 810)."""
        router = Router()
        router.register_backend(_ollama_backend([]))
        original = RoutingDecision(
            backend_type="openai",
            backend_url="",
            model="gpt-4o",
            tier="rule",
            confidence=0.9,
            reasoning="test",
        )
        privacy = PrivacyClassification(tier=PrivacyTier.PRIVATE, pii_types_found=["email"])
        result = router._force_local_routing(original, _resolved("gpt-4o", "openai"), privacy)
        assert result is None


# ===========================================================================
# TestNoBackendDecision — lines 810, 823-824
# ===========================================================================


class TestNoBackendDecision:
    """Tests for _no_backend_decision."""

    def test_healthy_backend_fallback(self) -> None:
        """Falls back to first healthy backend (lines 822-834)."""
        router = Router()
        router.register_backend(_ollama_backend(["llama3.2"]))
        request = _make_request("unknown-model")
        resolved = _resolved("unknown-model", "unknown")
        decision = router._no_backend_decision(request, resolved, [], 1.5)
        assert decision.backend_type == "ollama"
        assert decision.confidence == 0.30
        assert decision.factors["fallback"] is True

    def test_no_healthy_backends(self) -> None:
        """No healthy backends returns 'none' (lines 837-847)."""
        router = Router()
        backend = _ollama_backend()
        backend.is_healthy = False
        router.register_backend(backend)
        request = _make_request("unknown-model")
        resolved = _resolved("unknown-model", "unknown")
        decision = router._no_backend_decision(request, resolved, [], 2.0)
        assert decision.backend_type == "none"
        assert decision.confidence == 0.0
        assert "No healthy backends" in decision.reasoning

    def test_completely_empty(self) -> None:
        """No backends registered at all."""
        router = Router()
        request = _make_request("anything")
        resolved = _resolved("anything", "unknown")
        decision = router._no_backend_decision(request, resolved, [], 0.5)
        assert decision.backend_type == "none"
        assert decision.latency_ms == 0.5

    def test_local_preferred_over_cloud_in_fallback(self) -> None:
        """Local backends are preferred in _healthy_backends sort (lines 823-824)."""
        router = Router()
        router.register_backend(_cloud_backend("openai"))
        router.register_backend(_ollama_backend(["llama3.2"]))
        request = _make_request("unknown-model")
        resolved = _resolved("unknown-model", "unknown")
        decision = router._no_backend_decision(request, resolved, [], 1.0)
        assert decision.backend_type == "ollama"


# ===========================================================================
# Integration-level async tests (route method, end-to-end)
# ===========================================================================


class TestRouteIntegration:
    """Integration tests exercising the full route() pipeline for uncovered paths."""

    async def test_route_with_model_prefix_rule(self) -> None:
        """End-to-end test: model_prefix rule fires correctly."""
        router = Router()
        router.register_backend(_cloud_backend("anthropic", ["claude-3-5-sonnet-20241022"]))
        router.add_rule(
            _rule(
                "anthropic_prefix",
                "model_prefix",
                priority=1,
                parameters={"prefix": "claude-", "backend": "anthropic"},
            )
        )
        decision = await router.route(_make_request("claude-3-5-sonnet-20241022"))
        # Should match either model_exact (prio 5) or our prefix (prio 1)
        assert decision.backend_type == "anthropic"

    async def test_route_with_provider_preference_rule(self) -> None:
        """End-to-end test: provider_preference rule fires."""
        router = Router()
        router.register_backend(_cloud_backend("openai", ["gpt-4o"]))
        router.add_rule(
            _rule(
                "prefer_openai",
                "provider_preference",
                priority=3,
                parameters={"provider": "openai"},
            )
        )
        decision = await router.route(_make_request("gpt-4o"))
        assert decision.backend_type == "openai"

    async def test_route_cloud_fallback_via_openrouter(self) -> None:
        """End-to-end test: cloud_fallback to OpenRouter."""
        router = Router()
        router.register_backend(_cloud_backend("openrouter"))
        decision = await router.route(_make_request("gpt-4o"))
        assert decision.backend_type == "openrouter"

    async def test_route_privacy_blocks_cloud_no_local(self) -> None:
        """Privacy enforcement blocks cloud when no local backend available."""
        settings = PrivacySettings(privacy_mode="strict", allow_cloud_routing=False)
        router = Router(privacy_settings=settings)
        router.register_backend(_cloud_backend("openai", ["gpt-4o"]))
        decision = await router.route(_make_request("gpt-4o", "my email is test@foo.com"))
        # Should be blocked since no local backend and privacy requires local
        assert decision.backend_type == "none" or decision.factors.get("privacy_enforced")
