"""Decision engine implementing explicit signal -> feature -> risk mapping."""

from __future__ import annotations

from collections.abc import Iterable
from typing import Any

from .risk_manager import RiskManager
from .signals import Feature, Signal, format_risk_explanation, signals_to_features


class DecisionEngine:
    """Ingests signals and computes action level."""

    def __init__(self, config: dict[str, Any]) -> None:
        risk_cfg = config.get("risk", {})
        self.risk_manager = RiskManager(decay_factor=float(risk_cfg.get("decay_factor", 1.0)))
        self.thresholds = config.get("thresholds", {})
        self.signal_weight_scale = float(risk_cfg.get("signal_weight_scale", 1.0))
        self.feature_weight_scale = float(risk_cfg.get("feature_weight_scale", 0.25))
        self.feature_weights = {
            str(name): float(weight)
            for name, weight in risk_cfg.get("feature_weights", {}).items()
        }

    def add_signal(self, signal: Signal) -> float:
        """Add one signal contribution to risk."""
        return self.risk_manager.add_signal(signal, scale=self.signal_weight_scale)

    def extract_features(self, signals: Iterable[Signal]) -> list[Feature]:
        """Build features from signal stream."""
        return signals_to_features(list(signals))

    def add_features(self, features: Iterable[Feature]) -> list[tuple[str, float]]:
        """Add feature contributions to risk."""
        contributions: list[tuple[str, float]] = []
        for feature in features:
            weight = self.feature_weights.get(feature.name, 1.0)
            delta = self.risk_manager.add_feature(
                feature, scale=self.feature_weight_scale * max(0.0, weight)
            )
            contributions.append((feature.name, delta))
        return contributions

    def ingest_signals(self, signals: Iterable[Signal]) -> tuple[str, list[Feature], float, list[str]]:
        """Process signals and return (action_level, features, risk_score, explanation_lines)."""
        normalized_signals = list(signals)
        previous_risk, decayed_risk = self.risk_manager.decay()
        signal_contributions: list[tuple[str, float]] = []
        for signal in normalized_signals:
            delta = self.add_signal(signal)
            signal_contributions.append((signal.name, delta))
        features = self.extract_features(normalized_signals)
        feature_contributions = self.add_features(features)
        final_risk = self.risk_manager.score
        explanation = format_risk_explanation(
            previous_risk=previous_risk,
            decayed_risk=decayed_risk,
            signal_contributions=signal_contributions,
            feature_contributions=feature_contributions,
            final_risk=final_risk,
        )
        return self.action_level(), features, final_risk, explanation

    def action_level(self) -> str:
        """Return LOW, MEDIUM, HIGH, or CRITICAL based on thresholds."""
        score = self.risk_manager.score
        medium = float(self.thresholds.get("MEDIUM", 30.0))
        high = float(self.thresholds.get("HIGH", 60.0))
        critical = float(self.thresholds.get("CRITICAL", 85.0))
        if score >= critical:
            return "CRITICAL"
        if score >= high:
            return "HIGH"
        if score >= medium:
            return "MEDIUM"
        return "LOW"
