"""Euro NCAP scoring engine.

Implements the Euro NCAP adult occupant frontal impact scoring methodology.
Criteria are mapped to body-region color codes (Green through Red) using
sliding-scale performance limits. Points are then derived from colors.

Threshold values are versioned — this module supports multiple protocol years.
"""

from __future__ import annotations

from typing import Any

from impakt.criteria.base import CriterionResult
from impakt.protocol.base import BodyRegionScore, Color, ProtocolResult


def _color_from_value(
    value: float,
    higher_is_worse: bool,
    green_limit: float,
    yellow_limit: float,
    orange_limit: float,
    brown_limit: float,
    red_limit: float,
) -> Color:
    """Map a criterion value to a Euro NCAP color code.

    Uses linear interpolation between limits (sliding scale).
    """
    if higher_is_worse:
        if value <= green_limit:
            return Color.GREEN
        elif value <= yellow_limit:
            return Color.YELLOW
        elif value <= orange_limit:
            return Color.ORANGE
        elif value <= brown_limit:
            return Color.BROWN
        else:
            return Color.RED
    else:
        if value >= green_limit:
            return Color.GREEN
        elif value >= yellow_limit:
            return Color.YELLOW
        elif value >= orange_limit:
            return Color.ORANGE
        elif value >= brown_limit:
            return Color.BROWN
        else:
            return Color.RED


def _points_from_color(color: Color, max_points: float) -> float:
    """Map a color to points (linear scale)."""
    color_fractions = {
        Color.GREEN: 1.0,
        Color.YELLOW: 0.75,
        Color.ORANGE: 0.5,
        Color.BROWN: 0.25,
        Color.RED: 0.0,
    }
    return max_points * color_fractions[color]


# Threshold sets by year
# Format: {criterion: (green, yellow, orange, brown, red, higher_is_worse, max_points)}
THRESHOLDS_2024: dict[str, tuple[float, float, float, float, float, bool, float]] = {
    "HIC15": (500.0, 620.0, 700.0, 850.0, 1000.0, True, 4.0),
    "3ms Clip": (42.0, 48.0, 54.0, 57.0, 60.0, True, 4.0),
    "Chest Deflection": (22.0, 34.0, 42.0, 50.0, 63.0, True, 4.0),
    "Nij": (0.5, 0.65, 0.8, 0.9, 1.0, True, 2.0),
    "Femur Load Left": (3.8, 5.4, 7.0, 8.5, 10.0, True, 2.0),
    "Femur Load Right": (3.8, 5.4, 7.0, 8.5, 10.0, True, 2.0),
    "Tibia Index": (0.4, 0.7, 1.0, 1.15, 1.3, True, 2.0),
    "Viscous Criterion": (0.32, 0.56, 0.8, 0.9, 1.0, True, 2.0),
}

THRESHOLDS: dict[str, dict[str, tuple[float, float, float, float, float, bool, float]]] = {
    "2024": THRESHOLDS_2024,
}


class EuroNCAP:
    """Euro NCAP scorer."""

    def __init__(self, version: str = "2024") -> None:
        self._version = version
        if version not in THRESHOLDS:
            raise ValueError(
                f"Unknown Euro NCAP version: {version}. Available: {list(THRESHOLDS.keys())}"
            )
        self._thresholds = THRESHOLDS[version]

    @property
    def protocol_name(self) -> str:
        return "Euro NCAP"

    @property
    def version(self) -> str:
        return self._version

    def evaluate(
        self,
        criteria: dict[str, CriterionResult],
    ) -> ProtocolResult:
        """Score criteria results against Euro NCAP thresholds."""
        region_scores: list[BodyRegionScore] = []
        total_points = 0.0
        max_points = 0.0

        for criterion_name, thresholds in self._thresholds.items():
            green, yellow, orange, brown, red, higher_is_worse, max_pts = thresholds

            # Find the matching criterion result
            result = criteria.get(criterion_name)
            if result is None:
                # Try partial match
                for key, res in criteria.items():
                    if (
                        criterion_name.lower() in key.lower()
                        or key.lower() in criterion_name.lower()
                    ):
                        result = res
                        break

            if result is None:
                continue

            color = _color_from_value(
                result.value,
                higher_is_worse,
                green,
                yellow,
                orange,
                brown,
                red,
            )
            points = _points_from_color(color, max_pts)

            region_scores.append(
                BodyRegionScore(
                    region=result.body_region or criterion_name,
                    criterion=criterion_name,
                    value=result.value,
                    unit=result.unit,
                    color=color,
                    points=points,
                    max_points=max_pts,
                )
            )

            total_points += points
            max_points += max_pts

        percentage = (total_points / max_points * 100.0) if max_points > 0 else 0.0
        stars = self._percentage_to_stars(percentage)

        return ProtocolResult(
            protocol=self.protocol_name,
            version=self._version,
            overall_rating=f"{stars} stars",
            stars=stars,
            total_points=total_points,
            max_points=max_points,
            percentage=percentage,
            region_scores=region_scores,
        )

    @staticmethod
    def _percentage_to_stars(pct: float) -> int:
        """Convert percentage to star rating."""
        if pct >= 80:
            return 5
        elif pct >= 70:
            return 4
        elif pct >= 60:
            return 3
        elif pct >= 40:
            return 2
        elif pct >= 20:
            return 1
        else:
            return 0


def evaluate(
    criteria: dict[str, CriterionResult],
    version: str = "2024",
) -> ProtocolResult:
    """Convenience function for Euro NCAP evaluation."""
    return EuroNCAP(version=version).evaluate(criteria)
