"""Self-tuning confidence thresholds based on decision outcomes."""

from __future__ import annotations

import logging
import threading
import time
from collections import deque
from dataclasses import dataclass

from horizon.fund._decisions import ActionType, ConfidenceThresholds

logger = logging.getLogger("horizon.fund.adaptive_thresholds")


@dataclass(frozen=True)
class OutcomeRecord:
    """Records whether a decision led to a profitable outcome."""
    decision_id: str
    action: str
    confidence: float
    profitable: bool | None  # None = not yet resolved
    timestamp: float


class ThresholdTuner:
    """Self-tuning confidence thresholds based on decision precision.

    Tracks deploy/scale/retire decision outcomes and adjusts confidence
    thresholds to balance precision (avoid bad decisions) against recall
    (avoid missing opportunities).

    Learning rule per action type:
    - precision < target (0.6): raise threshold by step
    - precision > 0.8 and threshold > floor: lower threshold by step
    - Requires >= min_samples before adjusting
    """

    def __init__(
        self,
        initial: ConfidenceThresholds | None = None,
        target_precision: float = 0.6,
        step: float = 0.02,
        min_threshold: float = 0.20,
        max_threshold: float = 0.95,
        min_samples: int = 10,
    ) -> None:
        self._target_precision = target_precision
        self._step = step
        self._min_threshold = min_threshold
        self._max_threshold = max_threshold
        self._min_samples = min_samples
        self._records: deque[OutcomeRecord] = deque(maxlen=50_000)
        self._lock = threading.Lock()

        # Build mutable copy of initial thresholds
        src = initial or ConfidenceThresholds()
        self._thresholds: dict[str, float] = {
            at.value: src.threshold_for(at) for at in ActionType
        }

    def record_outcome(
        self,
        decision_id: str,
        action: str,
        confidence: float,
        profitable: bool | None = None,
    ) -> None:
        """Record a decision outcome.

        Args:
            decision_id: The decision's unique ID.
            action: ActionType value string.
            confidence: Confidence at decision time.
            profitable: True if the decision was profitable, None if unresolved.
        """
        with self._lock:
            self._records.append(OutcomeRecord(
                decision_id=decision_id,
                action=action,
                confidence=confidence,
                profitable=profitable,
                timestamp=time.time(),
            ))

    def tune(self) -> ConfidenceThresholds:
        """Adjust thresholds based on accumulated outcomes.

        Returns the updated ConfidenceThresholds.
        """
        with self._lock:
            records = list(self._records)
            thresholds = dict(self._thresholds)

        for action_type in ActionType:
            action_key = action_type.value
            resolved = [r for r in records if r.action == action_key and r.profitable is not None]

            if len(resolved) < self._min_samples:
                continue

            profitable_count = sum(1 for r in resolved if r.profitable)
            precision = profitable_count / len(resolved)

            current = thresholds.get(action_key, 0.5)

            if precision < self._target_precision:
                # Too many bad decisions: raise threshold
                new_val = min(current + self._step, self._max_threshold)
                if new_val != current:
                    logger.info(
                        "Threshold UP: %s %.2f -> %.2f (precision=%.2f < %.2f)",
                        action_key, current, new_val, precision, self._target_precision,
                    )
                thresholds[action_key] = new_val
            elif precision > 0.8 and current > self._min_threshold + self._step:
                # Very precise: can afford to lower threshold
                new_val = max(current - self._step, self._min_threshold)
                if new_val != current:
                    logger.info(
                        "Threshold DOWN: %s %.2f -> %.2f (precision=%.2f > 0.8)",
                        action_key, current, new_val, precision,
                    )
                thresholds[action_key] = new_val

        with self._lock:
            self._thresholds = thresholds

        return self._build_confidence_thresholds(thresholds)

    def get_thresholds(self) -> ConfidenceThresholds:
        """Return the current thresholds."""
        with self._lock:
            return self._build_confidence_thresholds(dict(self._thresholds))

    def stats(self) -> dict[str, dict]:
        """Return per-action statistics: precision, sample count, current threshold."""
        with self._lock:
            records = list(self._records)
            thresholds = dict(self._thresholds)

        result: dict[str, dict] = {}
        for action_type in ActionType:
            key = action_type.value
            resolved = [r for r in records if r.action == key and r.profitable is not None]
            total = len(resolved)
            profitable = sum(1 for r in resolved if r.profitable) if total > 0 else 0
            precision = profitable / total if total > 0 else 0.0

            result[key] = {
                "threshold": thresholds.get(key, 0.5),
                "precision": round(precision, 3),
                "sample_count": total,
                "profitable": profitable,
            }

        return result

    @staticmethod
    def _build_confidence_thresholds(thresholds: dict[str, float]) -> ConfidenceThresholds:
        """Build a frozen ConfidenceThresholds from a dict."""
        return ConfidenceThresholds(
            add_to_universe=thresholds.get("add_to_universe", 0.3),
            deploy_staging=thresholds.get("deploy_staging", 0.5),
            promote_live=thresholds.get("promote_live", 0.7),
            scale_up=thresholds.get("scale_up", 0.8),
            scale_down=thresholds.get("scale_down", 0.5),
            retire_strategy=thresholds.get("retire_strategy", 0.6),
            kill_switch=thresholds.get("kill_switch", 0.5),
            rebalance=thresholds.get("rebalance", 0.6),
            research_scan=thresholds.get("research_scan", 0.2),
        )
