"""Scoring engine — чистий Python, нуль Streamlit/DB імпортів."""
from __future__ import annotations

from dataclasses import dataclass, field


@dataclass
class ScoringResult:
    class_indices: dict[int, float]         # class_id → [0.0, 1.0]
    overall_index: float
    certification_level: int | None         # 1/2/3 або None
    has_critical: bool
    nonconformity_req_ids: list[int]
    req_met: dict[int, bool] = field(default_factory=dict)


def dispatch_operator(operator: str, value: object, threshold: object) -> bool:
    """§С.3.7: порівняння значення з порогом за оператором."""
    match operator:
        case ">=":
            return float(value) >= float(threshold)
        case "<=":
            return float(value) <= float(threshold)
        case "=":
            return str(value) == str(threshold)
        case "yes-no":
            return str(value).lower() == str(threshold).lower()  # регістр ігнорується
        case _:
            raise ValueError(f"Unknown operator: {operator!r}")


def compute_class_indices(
    requirements: list,
    results_by_req: dict[int, str],
) -> dict[int, float]:
    """§С.3.8: невагований індекс відповідності по кожному класу."""
    totals: dict[int, int] = {}
    mets: dict[int, int] = {}

    for req in requirements:
        cid = req.class_id
        totals[cid] = totals.get(cid, 0) + 1
        raw = results_by_req.get(req.id)
        if raw is not None:
            try:
                met = dispatch_operator(req.operator, raw, req.threshold)
            except (ValueError, TypeError):
                met = False
        else:
            met = False  # без відповіді → не виконана (найгірший сценарій)
        mets[cid] = mets.get(cid, 0) + (1 if met else 0)

    return {
        cid: mets.get(cid, 0) / total
        for cid, total in totals.items()
        if total > 0
    }


def _scope_class_ids(class_scope: str, class_id_by_code: dict[str, int]) -> set[int]:
    if class_scope.strip().upper() == "ALL":
        return set(class_id_by_code.values())
    codes = [c.strip() for c in class_scope.split(",")]
    return {class_id_by_code[c] for c in codes if c in class_id_by_code}


def select_certification_level(
    class_indices: dict[int, float],
    overall_index: float,
    has_critical: bool,
    levels: list,
    class_id_by_code: dict[str, int],
) -> int | None:
    """§С.4.6: двовимірний вибір рівня; критична НВ жорстко обмежує до рівня 1."""
    if has_critical:
        return 1  # §С.5.4: критична НВ блокує рівень вище 1

    for lvl in sorted(levels, key=lambda lv: lv.level, reverse=True):
        scope_ids = _scope_class_ids(lvl.class_scope, class_id_by_code)
        class_ok = all(
            class_indices.get(cid, 0.0) >= lvl.class_min_index
            for cid in scope_ids
        )
        overall_ok = overall_index >= lvl.overall_min_index
        if class_ok and overall_ok:
            return lvl.level

    return None


def evaluate_assessment(
    requirements: list,
    results_by_req: dict[int, str],
    levels: list,
    class_id_by_code: dict[str, int],
) -> ScoringResult:
    """Повний цикл розрахунку: §С.3.7 → §С.3.8 → §С.4.6 → §С.5."""
    # Крок 1: перевіряємо кожну вимогу
    req_met: dict[int, bool] = {}
    for req in requirements:
        raw = results_by_req.get(req.id)
        if raw is not None:
            try:
                met = dispatch_operator(req.operator, raw, req.threshold)
            except (ValueError, TypeError):
                met = False
        else:
            met = False
        req_met[req.id] = met

    # Крок 2–3: індекси по класах і загальний індекс
    class_indices = compute_class_indices(requirements, results_by_req)
    total = len(requirements)
    overall_index = sum(req_met.values()) / total if total > 0 else 0.0

    # Крок 4: поріг критичності з конфігурації Рівня 1
    critical_threshold = 3
    if levels:
        lvl1 = next((lv for lv in levels if lv.level == 1), levels[0])
        critical_threshold = lvl1.critical_threshold

    # Крок 5–6: невідповідності та перевірка критичності
    nonconformity_req_ids: list[int] = []
    has_critical = False
    for req in requirements:
        if req.mandatory and not req_met[req.id]:
            nonconformity_req_ids.append(req.id)
            if req.criticality >= critical_threshold:
                has_critical = True  # §С.5.4

    # Крок 7: рівень сертифікації
    cert_level = select_certification_level(
        class_indices, overall_index, has_critical, levels, class_id_by_code
    )

    return ScoringResult(
        class_indices=class_indices,
        overall_index=overall_index,
        certification_level=cert_level,
        has_critical=has_critical,
        nonconformity_req_ids=nonconformity_req_ids,
        req_met=req_met,
    )
