"""Mail-V2 Bulk-Recommendation Aggregator.

Collects per-item actions (cookie-level, MC-level, retention, B1)
and groups them by `aggregation_key` so the mail can show:

  🛠 Sofortmaßnahmen
  • Sitzland ergänzen für 12 Cookies: _ga, _gid, _fbp, …
  • Drittlandtransfer absichern für 5 US-Vendors: Google, Meta, …
  • Speicherdauer > 13mo bei 3 Cookies (CNIL-Cap): IDE, _gcl_au, …

This converts individual fix-recommendations into actionable
"do-this-one-thing-fixes-multiple-cookies" bullets that scale.
"""

from __future__ import annotations

from ._actions import (
    Action,
    cookie_field_missing_action,
    cookie_status_action,
    reachability_action,
    retention_action,
    action_for_finding,
)


# ── Group-label registry ─────────────────────────────────────────

GROUP_LABELS: dict[str, dict] = {
    "missing_country": {
        "label": "Sitzland ergänzen",
        "icon": "🌍",
        "norm": "Art. 13 Abs. 1 lit. a DSGVO",
    },
    "missing_duration": {
        "label": "Speicherdauer ergänzen",
        "icon": "⏱",
        "norm": "Art. 13 Abs. 2 lit. a DSGVO",
    },
    "missing_retention": {
        "label": "Löschfrist + Rechtsgrundlage angeben",
        "icon": "🗑",
        "norm": "§ 35 BDSG",
    },
    "missing_processing_company": {
        "label": "Verantwortliche Stelle nennen",
        "icon": "🏢",
        "norm": "Art. 13 Abs. 1 DSGVO",
    },
    "missing_third_country": {
        "label": "Drittlandtransfer absichern",
        "icon": "🌐",
        "norm": "Art. 44 ff. DSGVO",
    },
    "missing_category": {
        "label": "Cookie-Kategorie zuordnen",
        "icon": "🏷",
        "norm": "EDPB Cookie-Sweep",
    },
    "undoc_cookies": {
        "label": "Undeklarierte Cookies adressieren",
        "icon": "❌",
        "norm": "§ 25 Abs. 1 TDDDG",
    },
    "orphan_cookies": {
        "label": "Veraltete Cookie-Angaben entfernen",
        "icon": "👻",
        "norm": "Art. 5 Abs. 1 lit. a DSGVO (Transparenz)",
    },
    "mismatch_cookies": {
        "label": "Cookie-Werte mit Realität abgleichen",
        "icon": "🔀",
        "norm": "Art. 5 Abs. 1 lit. d DSGVO",
    },
    "dsi_too_short": {
        "label": "DSE-Speicherdauer korrigieren (zu kurz angegeben)",
        "icon": "📏",
        "norm": "Art. 13 Abs. 2 DSGVO",
    },
    "table_too_short": {
        "label": "Cookie-Tabelle-Speicherdauer korrigieren",
        "icon": "📏",
        "norm": "Art. 13 Abs. 2 DSGVO",
    },
    "dsi_table_mismatch": {
        "label": "DSE ↔ Cookie-Tabelle synchronisieren",
        "icon": "🔁",
        "norm": "Art. 5 Abs. 2 DSGVO Rechenschaftspflicht",
    },
    "safari_itp": {
        "label": "Safari-ITP-Cap in DSE dokumentieren",
        "icon": "🍎",
        "norm": "DSGVO Transparenzgebot",
    },
    "footer_reachability": {
        "label": "Footer-Reachability für Widerruf herstellen",
        "icon": "🔗",
        "norm": "Art. 7 Abs. 3 DSGVO",
    },
}


def _generic_group(key: str | None) -> dict:
    if not key:
        return {"label": "Manuelle Prüfung", "icon": "🔍", "norm": ""}
    if key.startswith("mc_"):
        kw = key[3:].replace("_", " ").title()
        return {"label": f"{kw} ergänzen", "icon": "📝",
                "norm": "MC-Prüfung"}
    return {"label": key.replace("_", " ").title(), "icon": "•", "norm": ""}


# ── Item types collected ────────────────────────────────────────

def _cookie_items(state: dict) -> list[tuple[Action, str]]:
    """Yield (action, item_label) for every cookie-level concern.

    item_label is what gets aggregated into the bullet list of names.
    """
    from ._cookie_inventory import build_cookie_inventory
    rows, _ = build_cookie_inventory(state)
    items: list[tuple[Action, str]] = []
    for r in rows:
        name = r.get("name") or "—"
        vendor = r.get("vendor") or ""
        label = f"{name}" + (f" ({vendor})" if vendor and vendor != "—" else "")
        # Status-level
        st_action = cookie_status_action(r["status_code"], name, vendor)
        if st_action:
            items.append((st_action, label))
        # Field-level
        for field, value in (
            ("country", r.get("country")),
            ("duration", r.get("duration")),
            ("retention_grounds", r.get("retention_grounds")),
            ("processing_company", r.get("processing_company")),
            ("category", r.get("category")),
        ):
            if not value or value in ("—", "❌", ""):
                fa = cookie_field_missing_action(field, name, vendor)
                if fa:
                    items.append((fa, label))
        if r.get("third_country"):
            ta = cookie_field_missing_action("third_country", name, vendor)
            if ta:
                items.append((ta, label))
    return items


def _retention_items(state: dict) -> list[tuple[Action, str]]:
    items: list[tuple[Action, str]] = []
    for f in (state.get("retention_findings") or []):
        if f.get("matches"):
            continue
        a = retention_action(f)
        if a:
            label = (f.get("cookie_name") or "—")
            vendor = f.get("vendor_name") or ""
            if vendor:
                label += f" ({vendor})"
            items.append((a, label))
    return items


def _reachability_items(state: dict) -> list[tuple[Action, str]]:
    a = reachability_action(state.get("reachability_finding") or {})
    if not a:
        return []
    return [(a, "Footer")]


def _mc_items(state: dict) -> list[tuple[Action, str]]:
    items: list[tuple[Action, str]] = []
    for r in (state.get("results") or []):
        doc = getattr(r, "label", "") or ""
        for c in getattr(r, "checks", []) or []:
            if getattr(c, "passed", True) or getattr(c, "skipped", False):
                continue
            sev = (getattr(c, "severity", "") or "").upper()
            if sev not in ("CRITICAL", "HIGH", "MEDIUM"):
                continue
            a = action_for_finding(
                getattr(c, "id", ""),
                sev,
                getattr(c, "label", ""),
                getattr(c, "hint", "") or "",
            )
            if a:
                items.append((a, doc))
    return items


def collect_actions(state: dict) -> list[dict]:
    """Top-level: collect every item-action across cookie/retention/B1/MC."""
    raw = (
        _cookie_items(state)
        + _retention_items(state)
        + _reachability_items(state)
        + _mc_items(state)
    )
    out: list[dict] = []
    for action, label in raw:
        out.append({**action.to_dict(), "item": label})
    return out


def group_by_action(state: dict) -> list[dict]:
    """Aggregate item-actions by aggregation_key.

    Returns a list of groups:
      {
        "key": "missing_country",
        "label": "Sitzland ergänzen",
        "icon": "🌍",
        "norm": "Art. 13 Abs. 1 lit. a DSGVO",
        "effort": "low",
        "count": 12,
        "items": ["_ga (Google)", "_gid (Google)", ...],
        "first_detail": "..." (first action.detail in the group),
      }
    sorted by count desc, then by group label.
    """
    actions = collect_actions(state)
    buckets: dict[str | None, dict] = {}
    for a in actions:
        key = a.get("aggregation_key")
        bucket = buckets.setdefault(key, {
            "key": key,
            "label": None, "icon": None, "norm": None,
            "effort": a.get("effort", "med"),
            "items": [], "count": 0,
            "first_detail": a.get("detail", ""),
        })
        if not bucket["label"]:
            meta = GROUP_LABELS.get(key or "") or _generic_group(key)
            bucket["label"] = meta["label"]
            bucket["icon"] = meta["icon"]
            bucket["norm"] = meta["norm"]
        item = a.get("item") or "—"
        if item not in bucket["items"]:
            bucket["items"].append(item)
        bucket["count"] = len(bucket["items"])
    groups = list(buckets.values())
    # sort: high-impact (effort=low + many items) first
    eff_rank = {"low": 0, "med": 1, "hi": 2}
    groups.sort(key=lambda g: (eff_rank.get(g["effort"], 9),
                                -g["count"], g["label"] or ""))
    return groups
