"""
Explainable prediction engine for Sentinel Brain.

V1 produces a provisional degradation-risk estimate.
It is not a statistically calibrated probability of failure.
"""

from dataclasses import dataclass
from math import exp

from sentinel.brain.findings import Finding


@dataclass(frozen=True)
class PredictionResult:
    """Explainable prediction result."""

    risk_index: float
    probability_percent: float
    horizon_days: int | None
    confidence: float
    explanation: str


class PredictionEngine:
    """Estimate degradation risk from subsystem health and findings."""

    @staticmethod
    def _clamp(
        value: float,
        minimum: float = 0.0,
        maximum: float = 100.0,
    ) -> float:
        """Clamp a value to a defined range."""
        return max(minimum, min(maximum, value))

    @classmethod
    def _risk_from_scores(
        cls,
        thermal_score: float,
        electrical_score: float,
        cooling_score: float,
        sensor_score: float,
    ) -> float:
        """Convert subsystem health scores into a weighted risk index."""
        risk_index = (
            (100.0 - cls._clamp(thermal_score)) * 0.35
            + (100.0 - cls._clamp(electrical_score)) * 0.20
            + (100.0 - cls._clamp(cooling_score)) * 0.35
            + (100.0 - cls._clamp(sensor_score)) * 0.10
        )

        return round(cls._clamp(risk_index), 2)

    @staticmethod
    def _finding_adjustment(
        findings: list[Finding],
    ) -> float:
        """Add a conservative adjustment for active findings."""
        weights = {
            "INFO": 0.0,
            "LOW": 1.0,
            "MEDIUM": 3.0,
            "HIGH": 7.0,
            "CRITICAL": 12.0,
        }

        return min(
            20.0,
            sum(
                weights.get(finding.severity, 0.0)
                for finding in findings
            ),
        )

    @staticmethod
    def _probability_from_risk(
        risk_index: float,
    ) -> float:
        """Map risk index to a smooth provisional 30-day estimate."""
        probability = 100.0 / (
            1.0 + exp(-0.09 * (risk_index - 55.0))
        )

        return round(
            min(99.0, max(0.0, probability - 0.70)),
            1,
        )

    @staticmethod
    def _horizon_from_risk(
        risk_index: float,
    ) -> int | None:
        """Estimate a conservative intervention horizon."""
        if risk_index < 15.0:
            return None
        if risk_index < 25.0:
            return 180
        if risk_index < 40.0:
            return 90
        if risk_index < 55.0:
            return 60
        if risk_index < 70.0:
            return 30
        if risk_index < 85.0:
            return 14
        return 7

    @staticmethod
    def _confidence(
        sample_count: int,
        findings: list[Finding],
    ) -> float:
        """Estimate confidence from data volume and finding confidence."""
        data_confidence = min(
            95.0,
            45.0 + sample_count / 20.0,
        )

        if not findings:
            return round(data_confidence, 1)

        mean_finding_confidence = (
            sum(finding.confidence for finding in findings)
            / len(findings)
        )

        return round(
            min(
                95.0,
                data_confidence * 0.60
                + mean_finding_confidence * 0.40,
            ),
            1,
        )

    @classmethod
    def predict(
        cls,
        thermal_score: float,
        electrical_score: float,
        cooling_score: float,
        sensor_score: float,
        findings: list[Finding],
        sample_count: int,
    ) -> PredictionResult:
        """Produce an explainable degradation-risk estimate."""
        subsystem_risk = cls._risk_from_scores(
            thermal_score=thermal_score,
            electrical_score=electrical_score,
            cooling_score=cooling_score,
            sensor_score=sensor_score,
        )

        risk_index = round(
            cls._clamp(
                subsystem_risk
                + cls._finding_adjustment(findings)
            ),
            2,
        )

        probability = cls._probability_from_risk(risk_index)
        horizon_days = cls._horizon_from_risk(risk_index)
        confidence = cls._confidence(sample_count, findings)

        if risk_index < 15.0:
            explanation = (
                "Current subsystem health and historical findings do not "
                "support a meaningful near-term degradation warning."
            )
        elif risk_index < 40.0:
            explanation = (
                "A low-to-moderate degradation risk is present. "
                "Continued observation is recommended."
            )
        elif risk_index < 70.0:
            explanation = (
                "Multiple indicators suggest increasing equipment "
                "degradation. Preventive maintenance should be planned "
                "if the trend persists."
            )
        else:
            explanation = (
                "The current combination of subsystem degradation and "
                "technical findings indicates a high near-term risk. "
                "Prompt technical inspection is recommended."
            )

        return PredictionResult(
            risk_index=risk_index,
            probability_percent=probability,
            horizon_days=horizon_days,
            confidence=confidence,
            explanation=explanation,
        )
