from __future__ import annotations

import json
import logging
import os
from typing import Dict, Iterable, List, Literal, Optional

from pydantic import BaseModel

from ..utils.score_utils import get_confidence_tier

log = logging.getLogger(__name__)

ConfidenceTier = Literal["CERTAIN", "LIKELY", "POSSIBLE", "REVIEW", "UNLIKELY"]


class ConfidenceBand(BaseModel):
    tier: ConfidenceTier
    min_score: float
    max_score: float
    count: int = 0
    color: str
    action_hint: str


_DEFAULT_THRESHOLDS: Dict[str, float] = {
    "certain": 0.95,
    "likely": 0.85,
    "possible": 0.70,
    "review": 0.50,
}

_DEFAULT_COLORS: Dict[ConfidenceTier, str] = {
    "CERTAIN": "#10B981",
    "LIKELY": "#3B82F6",
    "POSSIBLE": "#F59E0B",
    "REVIEW": "#6B7280",
    "UNLIKELY": "#EF4444",
}

_DEFAULT_HINTS: Dict[ConfidenceTier, str] = {
    "CERTAIN": "Safe to auto-apply",
    "LIKELY": "Review recommended",
    "POSSIBLE": "Manual review recommended",
    "REVIEW": "Needs review",
    "UNLIKELY": "Do not merge",
}

_CACHED_THRESHOLDS: Optional[Dict[str, float]] = None


def _get_default_thresholds() -> Dict[str, float]:
    global _CACHED_THRESHOLDS
    if _CACHED_THRESHOLDS is None:
        _CACHED_THRESHOLDS = _normalize_thresholds(
            _load_thresholds_from_env() or _DEFAULT_THRESHOLDS
        )
    return dict(_CACHED_THRESHOLDS)


def _load_thresholds_from_env() -> Optional[Dict[str, float]]:
    raw = os.getenv("FM_CONFIDENCE_THRESHOLDS") or os.getenv("FM_CONFIDENCE_BANDS")
    if not raw:
        return None
    try:
        data = json.loads(raw)
    except json.JSONDecodeError:
        log.warning("Invalid confidence threshold JSON; using defaults.")
        return None
    if not isinstance(data, dict):
        log.warning("Confidence threshold JSON must be an object; using defaults.")
        return None
    thresholds: Dict[str, float] = {}
    for key in ("certain", "likely", "possible", "review"):
        if key in data:
            try:
                thresholds[key] = float(data[key])
            except (TypeError, ValueError):
                log.warning("Invalid threshold for '%s'; using defaults.", key)
                return None
    return thresholds or None


def _normalize_thresholds(thresholds: Dict[str, float]) -> Dict[str, float]:
    normalized = {}
    for key in ("certain", "likely", "possible", "review"):
        value = thresholds.get(key, _DEFAULT_THRESHOLDS[key])
        try:
            value = float(value)
        except (TypeError, ValueError):
            value = _DEFAULT_THRESHOLDS[key]
        normalized[key] = max(0.0, min(1.0, value))
    ordered = [
        normalized["certain"],
        normalized["likely"],
        normalized["possible"],
        normalized["review"],
    ]
    if ordered != sorted(ordered, reverse=True):
        log.warning("Confidence thresholds out of order; using defaults.")
        return dict(_DEFAULT_THRESHOLDS)
    return normalized


def _build_bands(thresholds: Dict[str, float]) -> List[ConfidenceBand]:
    return [
        ConfidenceBand(
            tier="CERTAIN",
            min_score=thresholds["certain"],
            max_score=1.0,
            color=_DEFAULT_COLORS["CERTAIN"],
            action_hint=_DEFAULT_HINTS["CERTAIN"],
        ),
        ConfidenceBand(
            tier="LIKELY",
            min_score=thresholds["likely"],
            max_score=thresholds["certain"],
            color=_DEFAULT_COLORS["LIKELY"],
            action_hint=_DEFAULT_HINTS["LIKELY"],
        ),
        ConfidenceBand(
            tier="POSSIBLE",
            min_score=thresholds["possible"],
            max_score=thresholds["likely"],
            color=_DEFAULT_COLORS["POSSIBLE"],
            action_hint=_DEFAULT_HINTS["POSSIBLE"],
        ),
        ConfidenceBand(
            tier="REVIEW",
            min_score=thresholds["review"],
            max_score=thresholds["possible"],
            color=_DEFAULT_COLORS["REVIEW"],
            action_hint=_DEFAULT_HINTS["REVIEW"],
        ),
        ConfidenceBand(
            tier="UNLIKELY",
            min_score=0.0,
            max_score=thresholds["review"],
            color=_DEFAULT_COLORS["UNLIKELY"],
            action_hint=_DEFAULT_HINTS["UNLIKELY"],
        ),
    ]


def get_confidence_bands_from_config() -> List[ConfidenceBand]:
    return _build_bands(_get_default_thresholds())


def band_for_score(
    score: float, thresholds: Optional[Dict[str, float]] = None
) -> ConfidenceTier:
    thresholds = _get_default_thresholds() if thresholds is None else _normalize_thresholds(thresholds)
    return get_confidence_tier(score, thresholds=thresholds)  # type: ignore[return-value]


def count_confidence_bands(
    scores: Iterable[float], thresholds: Optional[Dict[str, float]] = None
) -> List[ConfidenceBand]:
    thresholds = _get_default_thresholds() if thresholds is None else _normalize_thresholds(thresholds)
    bands = _build_bands(thresholds)
    counts = {band.tier: 0 for band in bands}
    for score in scores:
        tier = get_confidence_tier(score, thresholds=thresholds)
        if tier in counts:
            counts[tier] += 1
    for band in bands:
        band.count = counts.get(band.tier, 0)
    return bands
