"""Injury criteria panel component.

Provides the criteria computation button, auto-detection of channels,
results table with color-coded ratings, and protocol scoring.
"""

from __future__ import annotations

import logging
from typing import Any

import dash_bootstrap_components as dbc
from dash import html

from impakt.channel.model import Channel, TestData
from impakt.criteria import (
    CriterionResult,
    chest_deflection,
    clip_3ms,
    femur_load,
    hic15,
    nij,
    tibia_index,
    viscous_criterion,
)
from impakt.protocol.base import Color, ProtocolResult, Rating

logger = logging.getLogger(__name__)


def build_criteria_panel() -> dbc.Card:
    """Build the injury criteria panel."""
    return dbc.Card(
        [
            dbc.CardHeader("Injury Criteria", className="fw-bold py-2"),
            dbc.CardBody(
                [
                    dbc.Button(
                        "Compute All",
                        id="compute-criteria-btn",
                        color="primary",
                        size="sm",
                        className="mb-2 w-100",
                    ),
                    dbc.Select(
                        id="protocol-select",
                        options=[
                            {"label": "Euro NCAP 2024", "value": "euro_ncap"},
                            {"label": "US NCAP 2023", "value": "us_ncap"},
                            {"label": "IIHS 2024", "value": "iihs"},
                        ],
                        value="euro_ncap",
                        size="sm",
                        className="mb-2",
                    ),
                    html.Div(id="criteria-results"),
                ],
                className="py-2",
            ),
        ],
        className="mb-2",
    )


def auto_compute_criteria(test_data: TestData) -> dict[str, CriterionResult]:
    """Auto-detect channels and compute all applicable injury criteria.

    Uses ISO channel naming to find the right channels for each criterion.
    """
    results: dict[str, CriterionResult] = {}

    # --- HIC15 ---
    head_accel = test_data.find("*HEAD0000*AC{X,Y,Z}*")
    if not head_accel:
        head_accel = test_data.find("*HEAD*AC*")
    if len(head_accel) >= 2:
        try:
            from impakt.transform.resultant import resultant_from_channels

            # Filter to only X/Y/Z components for the first test object found
            first_obj = head_accel[0].code.test_object
            comps = [
                ch
                for ch in head_accel
                if ch.code.test_object == first_obj and ch.code.is_component()
            ]
            if len(comps) >= 2:
                results["HIC15"] = hic15(resultant_from_channels(*comps))
        except Exception as e:
            logger.warning("HIC15 computation failed: %s", e)

    # --- 3ms Clip ---
    chest_accel = test_data.find("*CHST0000*AC{X,Y,Z}*")
    if not chest_accel:
        chest_accel = test_data.find("*CHST*AC*")
    if len(chest_accel) >= 2:
        try:
            from impakt.transform.resultant import resultant_from_channels

            first_obj = chest_accel[0].code.test_object
            comps = [
                ch
                for ch in chest_accel
                if ch.code.test_object == first_obj and ch.code.is_component()
            ]
            if len(comps) >= 2:
                results["3ms Clip"] = clip_3ms(resultant_from_channels(*comps))
        except Exception as e:
            logger.warning("3ms Clip computation failed: %s", e)

    # --- Nij ---
    neck_fz = test_data.find("*NECKUP*FO*Z*")
    neck_my = test_data.find("*NECKUP*MO*Y*")
    if neck_fz and neck_my:
        try:
            results["Nij"] = nij(
                fz_channel=neck_fz[0],
                my_channel=neck_my[0],
                dummy=test_data.metadata.dummy,
            )
        except Exception as e:
            logger.warning("Nij computation failed: %s", e)

    # --- Chest Deflection ---
    # Look for DC (deflection/compression) channels — these are the actual chest
    # deflection sensors (not DS which is general displacement and may be steering column)
    chest_defl = [ch for ch in test_data.find("*CHST*DC*") if ch.code.measurement == "DC"]
    # Only use DS as fallback if it looks like actual chest deflection
    # (NHTSA DS channels are often steering column displacement, not chest defl)
    if not chest_defl:
        for ch in test_data.find("*CHST*DS*"):
            if ch.code.measurement != "DS":
                continue
            # Chest deflection should be in mm range (0-100mm typical)
            # If unit is 'm' and peak > 0.1m = 100mm, it's not chest deflection
            peak_mm = ch.peak * 1000.0 if ch.unit in ("m",) else ch.peak
            if peak_mm < 150.0:
                chest_defl.append(ch)
    if chest_defl:
        try:
            results["Chest Deflection"] = chest_deflection(channel=chest_defl[0])
        except Exception as e:
            logger.warning("Chest deflection computation failed: %s", e)

    # --- Femur Load ---
    femur_left = test_data.find("*FEMRLE*FO*Z*")
    if femur_left:
        try:
            results["Femur Load Left"] = femur_load(channel=femur_left[0], side="left")
        except Exception as e:
            logger.warning("Femur left computation failed: %s", e)

    femur_right = test_data.find("*FEMRRI*FO*Z*")
    if femur_right:
        try:
            results["Femur Load Right"] = femur_load(channel=femur_right[0], side="right")
        except Exception as e:
            logger.warning("Femur right computation failed: %s", e)

    # --- Tibia Index ---
    tibia_fz = test_data.find("*TIBILI*FO*Z*")
    if not tibia_fz:
        tibia_fz = test_data.find("*TIBI*FO*Z*")
    tibia_mx = test_data.find("*TIBILI*MO*X*")
    if not tibia_mx:
        tibia_mx = test_data.find("*TIBI*MO*X*")
    tibia_my = test_data.find("*TIBILI*MO*Y*")
    if not tibia_my:
        tibia_my = test_data.find("*TIBI*MO*Y*")

    if tibia_fz:
        try:
            results["Tibia Index"] = tibia_index(
                fz_channel=tibia_fz[0],
                mx_channel=tibia_mx[0] if tibia_mx else None,
                my_channel=tibia_my[0] if tibia_my else None,
            )
        except Exception as e:
            logger.warning("Tibia index computation failed: %s", e)

    return results


def score_protocol(
    criteria: dict[str, CriterionResult],
    protocol: str = "euro_ncap",
) -> ProtocolResult | None:
    """Score criteria results against a protocol."""
    try:
        if protocol == "euro_ncap":
            from impakt.protocol.euro_ncap import evaluate

            return evaluate(criteria)
        elif protocol == "us_ncap":
            from impakt.protocol.us_ncap import evaluate

            return evaluate(criteria)
        elif protocol == "iihs":
            from impakt.protocol.iihs import evaluate

            return evaluate(criteria)
    except Exception as e:
        logger.warning("Protocol scoring failed: %s", e)
    return None


def _rating_color(rating: Rating | None, color: Color | None) -> str:
    """Map a rating or color to a CSS color."""
    if color:
        return {
            Color.GREEN: "#2ecc71",
            Color.YELLOW: "#f1c40f",
            Color.ORANGE: "#e67e22",
            Color.BROWN: "#8B4513",
            Color.RED: "#e74c3c",
        }.get(color, "#bdc3c7")
    if rating:
        return {
            Rating.GOOD: "#2ecc71",
            Rating.ACCEPTABLE: "#f1c40f",
            Rating.MARGINAL: "#e67e22",
            Rating.POOR: "#e74c3c",
        }.get(rating, "#bdc3c7")
    return "#bdc3c7"


def build_criteria_results_display(
    criteria: dict[str, CriterionResult],
    protocol_result: ProtocolResult | None = None,
) -> html.Div:
    """Build the criteria results display."""
    elements: list = []

    # Protocol summary
    if protocol_result:
        stars_str = ""
        if protocol_result.stars is not None:
            stars_str = "★" * protocol_result.stars + "☆" * (5 - protocol_result.stars)

        elements.append(
            html.Div(
                [
                    html.Div(
                        [
                            html.Span(
                                protocol_result.protocol,
                                style={"fontWeight": "bold", "fontSize": "13px"},
                            ),
                            html.Span(
                                f" {protocol_result.version}",
                                style={"fontSize": "11px", "color": "#999"},
                            ),
                        ]
                    ),
                    html.Div(
                        stars_str,
                        style={"fontSize": "20px", "color": "#f1c40f", "letterSpacing": "2px"},
                    )
                    if stars_str
                    else None,
                    html.Div(
                        protocol_result.overall_rating,
                        style={"fontSize": "12px", "fontWeight": "bold"},
                    ),
                    html.Hr(style={"margin": "6px 0"}),
                ]
            )
        )

    # Criteria table
    rows = []
    for name, result in criteria.items():
        # Find matching protocol region score for color
        bg_color = "#fff"
        if protocol_result:
            for rs in protocol_result.region_scores:
                if rs.criterion == name or name.lower() in rs.criterion.lower():
                    bg_color = _rating_color(rs.rating, rs.color)
                    break

        time_str = f"{result.time_of_peak:.4f}s" if result.time_of_peak is not None else ""

        rows.append(
            html.Tr(
                [
                    html.Td(result.criterion, style={"fontSize": "11px", "fontWeight": "500"}),
                    html.Td(
                        f"{result.value:.2f}",
                        style={
                            "fontSize": "11px",
                            "fontFamily": "monospace",
                            "fontWeight": "bold",
                            "textAlign": "right",
                        },
                    ),
                    html.Td(result.unit, style={"fontSize": "11px"}),
                    html.Td(time_str, style={"fontSize": "10px", "color": "#999"}),
                    html.Td(
                        html.Div(
                            style={
                                "width": "12px",
                                "height": "12px",
                                "borderRadius": "50%",
                                "backgroundColor": bg_color,
                                "display": "inline-block",
                            }
                        ),
                    ),
                ],
                id={"type": "criteria-row", "index": name},
                style={"cursor": "pointer"},
            )
        )

    elements.append(
        html.Table(
            [
                html.Thead(
                    html.Tr(
                        [
                            html.Th("Criterion", style={"fontSize": "10px"}),
                            html.Th("Value", style={"fontSize": "10px", "textAlign": "right"}),
                            html.Th("", style={"fontSize": "10px"}),
                            html.Th("Peak", style={"fontSize": "10px"}),
                            html.Th("", style={"fontSize": "10px"}),
                        ]
                    )
                ),
                html.Tbody(rows),
            ],
            className="table table-sm table-hover mb-0",
        )
    )

    if not criteria:
        elements.append(
            html.Div(
                "No criteria computed. Check that channels are available.",
                className="text-muted small",
            )
        )

    return html.Div(elements)
