"""Unit tests for structured routing logging."""

from __future__ import annotations

import logging

from llm_rotator._types import Candidate, Usage
from llm_rotator.config import ClientType
from llm_rotator.exceptions import (
    KeyDeadError,
    ModelRateLimitError,
    QuotaExceededError,
    ServerError,
)
from llm_rotator.logging import RoutingTrace


def _make_candidate(
    provider: str = "OpenAI",
    model: str = "gpt-4o",
    key_alias: str = "main_key",
    group: str = "flagship",
) -> Candidate:
    return Candidate(
        provider_name=provider,
        model=model,
        key_alias=key_alias,
        key_token="sk-test",
        model_group=group,
        base_url=None,
        client_type=ClientType.OPENAI,
    )


def _make_usage(total: int = 150) -> Usage:
    return Usage(prompt_tokens=100, completion_tokens=total - 100, total_tokens=total)


class TestRoutingTraceFormat:
    def test_routing_log_happy_path(self):
        """Single candidate success → clean log line."""
        trace = RoutingTrace(request_id="abc123")
        candidate = _make_candidate()
        trace.add_success(candidate, _make_usage(150))

        result = trace.format()
        assert "[req:abc123]" in result
        assert "[OpenAI/flagship]" in result
        assert "gpt-4o (main_key)" in result
        assert "200 OK" in result
        assert "150 tokens" in result
        assert "AllAttemptsFailed" not in result

    def test_routing_log_fallback_chain(self):
        """429 → fallback → 200 → full chain in one line."""
        trace = RoutingTrace(request_id="def456")
        c1 = _make_candidate(key_alias="key1")
        c2 = _make_candidate(key_alias="key2")

        trace.add_error(c1, ModelRateLimitError(key_alias="key1", model="gpt-4o"))
        trace.add_success(c2, _make_usage(150))

        result = trace.format()
        assert "429 RateLimit" in result
        assert "200 OK" in result
        assert "key1" in result
        assert "key2" in result

    def test_routing_log_all_failed(self):
        """Chain ends with AllAttemptsFailed."""
        trace = RoutingTrace(request_id="fail01")
        c1 = _make_candidate(key_alias="key1")
        c2 = _make_candidate(provider="Gemini", key_alias="g_key", group="default")

        trace.add_error(c1, ModelRateLimitError(key_alias="key1", model="gpt-4o"))
        trace.add_error(
            c2,
            ServerError(provider="Gemini", status_code=500),
        )
        trace.add_all_failed()

        result = trace.format()
        assert "AllAttemptsFailed" in result
        assert "429 RateLimit" in result
        assert "500 ServerError" in result

    def test_routing_log_skip(self):
        """Skipped candidates appear in the log."""
        trace = RoutingTrace(request_id="skip01")
        c1 = _make_candidate()
        c2 = _make_candidate(
            provider="Gemini",
            model="gemini-flash",
            key_alias="g_key",
            group="default",
        )

        trace.add_skip(c1, "circuit breaker")
        trace.add_success(c2, _make_usage(100))

        result = trace.format()
        assert "Skip (circuit breaker)" in result
        assert "200 OK" in result

    def test_routing_log_quota_skip(self):
        """QuotaExceeded shows usage stats."""
        trace = RoutingTrace(request_id="quota1")
        c1 = _make_candidate()
        c2 = _make_candidate(
            provider="Gemini",
            model="gemini-flash",
            key_alias="g_key",
            group="default",
        )

        trace.add_error(
            c1,
            QuotaExceededError(scope="OpenAI/flagship", current=248000, limit=250000),
        )
        trace.add_success(c2, _make_usage(100))

        result = trace.format()
        assert "QuotaExceeded (248k/250k)" in result

    def test_routing_log_key_dead(self):
        """KeyDead error is formatted correctly."""
        trace = RoutingTrace(request_id="dead01")
        c1 = _make_candidate()

        trace.add_error(c1, KeyDeadError(key_alias="main_key", status_code=401))
        trace.add_all_failed()

        result = trace.format()
        assert "401 KeyDead" in result

    def test_log_includes_request_id(self):
        """Each trace has a unique request_id."""
        t1 = RoutingTrace()
        t2 = RoutingTrace()
        assert t1.request_id != t2.request_id
        assert f"[req:{t1.request_id}]" in t1.format()


class TestRoutingTraceEmit:
    def test_emit_success_info(self, caplog):
        """Single success → INFO level."""
        trace = RoutingTrace(request_id="info01")
        trace.add_success(_make_candidate(), _make_usage(150))

        test_logger = logging.getLogger("test_emit_success")
        with caplog.at_level(logging.DEBUG, logger="test_emit_success"):
            trace.emit(test_logger)

        assert len(caplog.records) == 1
        assert caplog.records[0].levelno == logging.INFO
        assert "200 OK" in caplog.records[0].message

    def test_emit_fallback_warning(self, caplog):
        """Fallback chain (>1 step with success) → WARNING level."""
        trace = RoutingTrace(request_id="warn01")
        c1 = _make_candidate(key_alias="key1")
        c2 = _make_candidate(key_alias="key2")
        trace.add_error(c1, ModelRateLimitError(key_alias="key1", model="gpt-4o"))
        trace.add_success(c2, _make_usage(150))

        test_logger = logging.getLogger("test_emit_fallback")
        with caplog.at_level(logging.DEBUG, logger="test_emit_fallback"):
            trace.emit(test_logger)

        assert len(caplog.records) == 1
        assert caplog.records[0].levelno == logging.WARNING

    def test_emit_all_failed_warning(self, caplog):
        """All failed → WARNING level."""
        trace = RoutingTrace(request_id="fail01")
        trace.add_error(
            _make_candidate(),
            ServerError(provider="OpenAI", status_code=500),
        )
        trace.add_all_failed()

        test_logger = logging.getLogger("test_emit_allfail")
        with caplog.at_level(logging.DEBUG, logger="test_emit_allfail"):
            trace.emit(test_logger)

        assert len(caplog.records) == 1
        assert caplog.records[0].levelno == logging.WARNING

    def test_custom_logger(self, caplog):
        """Custom logger receives the routing log."""
        custom_logger = logging.getLogger("my_custom_logger")
        trace = RoutingTrace(request_id="cust01")
        trace.add_success(_make_candidate(), _make_usage(150))

        with caplog.at_level(logging.DEBUG, logger="my_custom_logger"):
            trace.emit(custom_logger)

        assert len(caplog.records) == 1
        assert caplog.records[0].name == "my_custom_logger"
        assert "200 OK" in caplog.records[0].message
