"""Outcome recorder — bridges routing decisions to persistent storage.

Creates OutcomeRecord instances from routing decision dicts and response
metadata, then persists them via OutcomeDB. Provides both sync and async
interfaces, plus a fire-and-forget ``arecord_safe`` for non-blocking use
in request handlers.
"""

from __future__ import annotations

import structlog

from robot_resources.tracking.calculator import CostCalculator
from robot_resources.tracking.db import OutcomeDB
from robot_resources.tracking.models import OutcomeRecord

logger = structlog.get_logger()


class OutcomeRecorder:
    """Records routing outcomes to the tracking database.

    Combines CostCalculator (pricing) with OutcomeDB (persistence)
    to provide a single entry point for outcome tracking.

    Usage::

        recorder = OutcomeRecorder(db=outcome_db, calculator=calc)
        record = recorder.build_record(
            routing_decision=decision,
            input_tokens=usage.prompt_tokens,
            output_tokens=usage.completion_tokens,
            latency_ms=elapsed_ms,
        )
        await recorder.arecord(record)
    """

    def __init__(self, db: OutcomeDB, calculator: CostCalculator) -> None:
        self._db = db
        self._calc = calculator

    # ------------------------------------------------------------------
    # Record building
    # ------------------------------------------------------------------

    def build_record(
        self,
        *,
        routing_decision: dict,
        input_tokens: int,
        output_tokens: int,
        latency_ms: int,
        request_id: str | None = None,
        success: bool | None = None,
    ) -> OutcomeRecord:
        """Build an OutcomeRecord from a routing decision and response data.

        The routing_decision dict is the output of ``route_prompt()``.
        Cost fields are computed from actual token counts via CostCalculator,
        not copied from the routing decision (which only has per-1K rates).
        """
        model_used = routing_decision["selected_model"]
        model_baseline = routing_decision["baseline_model"]

        costs = self._calc.calculate_request_costs(
            model_used=model_used,
            model_baseline=model_baseline,
            input_tokens=input_tokens,
            output_tokens=output_tokens,
        )

        kwargs: dict = {
            "task_type": routing_decision["task_type"],
            "model_used": model_used,
            "model_baseline": model_baseline,
            "provider": routing_decision["provider"],
            "input_tokens": input_tokens,
            "output_tokens": output_tokens,
            "cost_actual": costs.cost_actual,
            "cost_baseline": costs.cost_baseline,
            "cost_saved": costs.cost_saved,
            "latency_ms": latency_ms,
            "detection_confidence": routing_decision.get("detection_confidence"),
            "capability_score": routing_decision.get("capability_score"),
            "savings_percent": routing_decision.get("savings_percent"),
            "success": success,
        }

        if request_id is not None:
            kwargs["request_id"] = request_id

        return OutcomeRecord(**kwargs)

    def build_record_direct(
        self,
        *,
        model: str,
        provider: str,
        input_tokens: int,
        output_tokens: int,
        latency_ms: int,
        request_id: str | None = None,
        success: bool | None = None,
    ) -> OutcomeRecord:
        """Build an OutcomeRecord for a non-routed (direct model) request.

        When the user specifies an explicit model, there's no routing
        decision. Model is both "used" and "baseline", so cost_saved = 0.
        """
        cost = self._calc.calculate_cost(
            model, input_tokens=input_tokens, output_tokens=output_tokens
        )

        kwargs: dict = {
            "task_type": "direct",
            "model_used": model,
            "model_baseline": model,
            "provider": provider,
            "input_tokens": input_tokens,
            "output_tokens": output_tokens,
            "cost_actual": cost,
            "cost_baseline": cost,
            "cost_saved": 0.0,
            "latency_ms": latency_ms,
            "success": success,
        }

        if request_id is not None:
            kwargs["request_id"] = request_id

        return OutcomeRecord(**kwargs)

    # ------------------------------------------------------------------
    # Persistence
    # ------------------------------------------------------------------

    def record(self, outcome: OutcomeRecord) -> None:
        """Synchronously persist an outcome record."""
        self._db.record(outcome)

    async def arecord(self, outcome: OutcomeRecord) -> None:
        """Async persist an outcome record (runs in thread pool)."""
        await self._db.arecord(outcome)

    async def arecord_safe(self, outcome: OutcomeRecord) -> None:
        """Fire-and-forget async recording that never raises.

        Intended for use in request handlers where tracking must not
        interfere with response delivery. Errors are logged and swallowed.
        """
        try:
            await self._db.arecord(outcome)
        except Exception:
            logger.warning(
                "outcome_recording_failed",
                request_id=outcome.request_id,
                exc_info=True,
            )
