"""
k-Anonymitaets-Helper fuer Branchen-Benchmarks (P6-Vorbereitung).

Vor jeder Veroeffentlichung von Benchmark-Aussagen pruefen, ob die
zugrundeliegende Stichprobe gross genug ist, dass keine Re-Identifikation
einzelner Hersteller moeglich wird.

Default k=5: jede publizierbare Aussage muss auf mindestens 5 verschiedenen
Datensubjekten (z.B. OEM-Sites) beruhen. Bei OEM-Markt mit ~30 Spielern
ist k=5 das Minimum, um "ein deutscher Premium-Hersteller mit X Modellen"
auszuschliessen.

Memory: feedback_oem_data_legal.md + project_legal_contracts_2026_07.md.

Verwendung:
  from compliance.services.benchmark_k_anonymity import (
      enforce_k_anonymity, quantize_value, KAnonymityError,
  )

  rows = [...]                          # pro Hersteller 1 Row
  safe_groups = enforce_k_anonymity(rows, group_keys=["segment", "country"])
  # safe_groups: nur Gruppen mit count >= 5 zurueck
"""

from __future__ import annotations

from collections.abc import Iterable
from typing import Any

DEFAULT_K = 5


class KAnonymityError(RuntimeError):
    """Stichprobe ist zu klein fuer eine publizierbare Aussage."""


def assert_min_sample(n: int, k: int = DEFAULT_K, context: str = "") -> None:
    """Wirft KAnonymityError wenn n < k."""
    if n < k:
        raise KAnonymityError(
            f"Stichprobe zu klein fuer Publikation: n={n} < k={k}"
            + (f" — Kontext: {context}" if context else "")
        )


def quantize_value(value: float | int, step: int = 5) -> int:
    """Quantisiere Zahlenwerte auf step-Vielfache (Generalisierung).

    quantize_value(67, 5) -> 65
    quantize_value(83, 10) -> 80

    Verhindert exakte Identifizierung ueber numerische Signale.
    """
    if step <= 0:
        return int(value)
    return int(value // step) * step


def quantize_range(value: float | int, step: int = 10) -> str:
    """Gib ein Range-Bucket zurueck als String: '60-70%', '80-90%'."""
    base = quantize_value(value, step)
    return f"{base}-{base + step}%"


def group_and_count(
    rows: Iterable[dict],
    keys: list[str],
) -> dict[tuple, int]:
    """Gruppiere Rows nach allen `keys` und zaehle pro Bucket."""
    counts: dict[tuple, int] = {}
    for r in rows:
        bucket = tuple(r.get(k, "") for k in keys)
        counts[bucket] = counts.get(bucket, 0) + 1
    return counts


def enforce_k_anonymity(
    rows: list[dict],
    group_keys: list[str],
    k: int = DEFAULT_K,
) -> list[dict]:
    """Filtere Rows so, dass jede ueberlebende Gruppe >= k Mitglieder hat.

    Returns: Rows die in ausreichend grossen Gruppen sind.
    Rows in zu kleinen Gruppen werden suppressed (entfernt).
    """
    counts = group_and_count(rows, group_keys)
    safe_buckets = {bucket for bucket, n in counts.items() if n >= k}
    return [
        r for r in rows
        if tuple(r.get(key, "") for key in group_keys) in safe_buckets
    ]


def summarize_benchmark(
    rows: list[dict],
    group_keys: list[str],
    measure_key: str,
    k: int = DEFAULT_K,
    quantize_step: int = 5,
) -> list[dict]:
    """Erzeuge publizierbare Benchmark-Aggregat-Zeilen.

    Pro Gruppe: count, mean (quantisiert), only-if count >= k.
    Liefert sortiert nach count desc.

    Beispiel:
      rows = [{"segment": "premium", "consent_score": 84}, ...]
      summarize_benchmark(rows, ["segment"], "consent_score")
      -> [{"segment": "premium", "n": 8, "mean_quantized": 80}, ...]
    """
    buckets: dict[tuple, list[float]] = {}
    for r in rows:
        bucket = tuple(r.get(k, "") for k in group_keys)
        val = r.get(measure_key)
        if val is not None:
            buckets.setdefault(bucket, []).append(float(val))

    out: list[dict] = []
    for bucket, values in buckets.items():
        n = len(values)
        if n < k:
            continue
        mean = sum(values) / n
        entry: dict[str, Any] = {key: bucket[i] for i, key in enumerate(group_keys)}
        entry["n"] = n
        entry["mean_quantized"] = quantize_value(mean, quantize_step)
        entry["mean_range"] = quantize_range(mean, quantize_step * 2)
        out.append(entry)
    out.sort(key=lambda e: e["n"], reverse=True)
    return out


def safe_to_publish(
    statement: str,
    sample_size: int,
    k: int = DEFAULT_K,
) -> tuple[bool, str]:
    """Validator fuer Marketing/Press-Statements.

    Returns (ok, message). Wenn ok=False, NICHT publishen.
    """
    if sample_size < k:
        return False, (
            f'Aussage NICHT publizierbar: "{statement[:60]}…" '
            f'(n={sample_size} < k={k}). Risiko: Re-Identifikation '
            f'einzelner Hersteller moeglich.'
        )
    return True, f"OK (n={sample_size}, k={k})"
