"""Rollup-Dedup für Recommendations.

Wenn 3 Findings alle "AVV mit Anbieter X abschließen" sagen, werden sie
zu einer Recommendation gebündelt mit Liste der ursächlichen Findings.
Spart Kundenzeit beim Abarbeiten.

KEIN LLM — deterministische Normalisierung + Hashing.
"""

from __future__ import annotations

import re

from ._base import Finding, Recommendation, Severity, stable_recommendation_id


_SEVERITY_ORDER = {
    Severity.HIGH: 3,
    Severity.MEDIUM: 2,
    Severity.LOW: 1,
    Severity.INFO: 0,
}


def _canonical(text: str) -> str:
    """Normalisiert: lowercase, Whitespace zusammenfalten, Punkte raus.
    Liefert eine kanonische Form für Dedup-Hashing."""
    t = (text or "").lower().strip()
    t = re.sub(r"\s+", " ", t)
    t = re.sub(r"[.,;:!?]+$", "", t)
    return t


def rollup(findings: list[Finding]) -> list[Recommendation]:
    """Bündelt actions zu Recommendations. Eine Recommendation enthält
    alle Findings deren action denselben canonical_text hat."""
    buckets: dict[str, list[Finding]] = {}
    for f in findings:
        action = f.action or ""
        if not action:
            continue
        key = _canonical(action)
        if not key:
            continue
        buckets.setdefault(key, []).append(f)
    out: list[Recommendation] = []
    for key, group in buckets.items():
        max_sev_finding = max(
            group, key=lambda x: _SEVERITY_ORDER.get(_sev(x), 0),
        )
        # title aus dem ersten Finding (am ehesten lesbar)
        first = group[0]
        body = first.action
        # wenn mehrere distincte titles: liste sie
        related_titles = sorted({f.title for f in group if f.title})
        if len(related_titles) > 1:
            body = (
                body + "\n\nGilt für: "
                + "; ".join(related_titles[:8])
            )
        rid = stable_recommendation_id(key)
        out.append(Recommendation(
            recommendation_id=rid,
            title=first.action[:120] or "(ohne Titel)",
            body=body,
            severity=_sev(max_sev_finding),
            related_finding_ids=[f.check_id for f in group],
            estimated_effort_hours=_estimate_effort(group),
        ))
    return sorted(
        out,
        key=lambda r: _SEVERITY_ORDER.get(Severity(r.severity), 0),
        reverse=True,
    )


def _sev(f: Finding) -> Severity:
    """Helper: handle Enum vs str (because use_enum_values=True)."""
    v = f.severity
    if isinstance(v, Severity):
        return v
    try:
        return Severity(str(v).upper())
    except Exception:
        return Severity.INFO


def _estimate_effort(group: list[Finding]) -> float:
    """Heuristik: pro Finding 0.5h Basis, +0.5h pro HIGH, max 8h."""
    base = 0.5 * len(group)
    extra = 0.5 * sum(1 for f in group if _sev(f) == Severity.HIGH)
    return min(8.0, base + extra)
