# core/price_compare.py
from __future__ import annotations
from typing import Dict, Any, List, Tuple, Optional
import math
import statistics

def _is_num(x) -> bool:
    return isinstance(x, (int, float)) and (not (isinstance(x, float) and (math.isnan(x) or math.isinf(x))))

def _npk_sort_key(pos: str) -> tuple:
    parts = [int(p) if p.isdigit() else 0 for p in str(pos).split(".")]
    return tuple(parts + [0] * (4 - len(parts)))

def build_preisspiegel(data: Dict[str, Any]) -> Dict[str, Any]:
    """
    Akzeptiert entweder:
      - Vollstruktur: {vendor: {"positions": {...}, "summary": {...}}}
      - Nur Positionen: {vendor: {pos: {...}}}
    Nutzt bevorzugt summary.total_inkl_mwst; Fallback Summe aus Positionen (qty*EP bzw. GP, falls vorhanden).
    """
    summaries = []
    for vendor, block in (data or {}).items():
        positions = {}
        summary = {}
        if isinstance(block, dict) and "positions" in block:
            positions = block.get("positions", {}) or {}
            summary   = block.get("summary", {}) or {}
        else:
            positions = block or {}

        # bevorzugt total_inkl_mwst
        total = summary.get("total_inkl_mwst")
        if not _is_num(total):
            # Fallback: Summe über Positionen – GP wenn vorhanden, sonst qty*EP, sonst 0
            s = 0.0
            for p in positions.values():
                gp = p.get("GP")
                ep = p.get("EP")
                qty = p.get("qty")
                if _is_num(gp):
                    s += float(gp)
                elif _is_num(ep) and _is_num(qty):
                    s += float(ep) * float(qty)
            total = s

        summaries.append({
            "vendor": vendor,
            "brutto": summary.get("brutto"),
            "rabatt_pct": summary.get("rabatt_pct"),
            "skonto_pct": summary.get("skonto_pct"),
            "netto_exkl_mwst": summary.get("netto_exkl_mwst"),
            "mwst_pct": summary.get("mwst_pct"),
            "total_inkl_mwst": summary.get("total_inkl_mwst"),
            "total": total,
        })

    ranking = sorted([(s["vendor"], s["total"]) for s in summaries], key=lambda t: (t[1] if _is_num(t[1]) else math.inf))
    return {"ranking": ranking, "summaries": summaries}


def build_detailvergleich(
    offer_positions: Dict[str, Dict[str, Dict[str, Any]]],
    lv_qty: Dict[str, Dict[str, Any]],
) -> List[Dict[str, Any]]:
    """
    Baut die Matrix für den Detailvergleich:
    - Zeilen: alle NPK-Positionen, die im LV eine Menge haben (lv_qty)
    - Pro Unternehmer zwei Spalten: 'EP {U}', 'Total {U}'
    - WICHTIG: Es wird NICHT pro Zeile aus EP*qty ein GP abgeleitet und NICHT aus GP/qty ein EP! (Wunsch des Users)
      → Wenn nur EP vorhanden: 'EP {U}' befüllt, 'Total {U}' bleibt leer.
      → Wenn nur GP vorhanden: 'Total {U}' befüllt, 'EP {U}' bleibt leer.
    - Statistikspalten (Median, Min/Max) basieren auf verfügbaren EPs.
    """
    vendors = list(offer_positions.keys())
    all_positions = sorted(lv_qty.keys(), key=_npk_sort_key)

    rows: List[Dict[str, Any]] = []
    for pos in all_positions:
        base = lv_qty.get(pos, {}) or {}
        row: Dict[str, Any] = {
            "Pos": pos,
            "Text": base.get("text") or "",
            "Menge": base.get("qty"),
            "Einheit": base.get("unit"),
        }

        # EPs einsammeln für Median/Min/Max
        ep_values: List[Tuple[str, float]] = []
        for v in vendors:
            entry = (offer_positions.get(v, {}) or {}).get(pos, {}) or {}
            ep = entry.get("EP")
            gp = entry.get("GP")

            # Sichtbare Werte:
            row[f"EP {v}"] = float(ep) if _is_num(ep) else None
            row[f"Total {v}"] = float(gp) if _is_num(gp) else None

            if _is_num(ep):
                ep_values.append((v, float(ep)))

        # Statistik basierend auf EP
        if ep_values:
            only_ep = [ep for (_, ep) in ep_values]
            try:
                median = statistics.median(only_ep)
            except Exception:
                median = None

            # Min/Max nach EP
            min_vendor, min_ep = min(ep_values, key=lambda t: t[1])
            max_vendor, max_ep = max(ep_values, key=lambda t: t[1])

            row["Median"] = median
            row["Min Vendor"] = min_vendor
            row["Max Vendor"] = max_vendor

            # einfache Outlier-Flag (IQR)
            try:
                q1 = statistics.quantiles(only_ep, n=4)[0]
                q3 = statistics.quantiles(only_ep, n=4)[2]
                iqr = q3 - q1
                low = q1 - 1.5 * iqr
                high = q3 + 1.5 * iqr
                outl = [v for (v, ep) in ep_values if ep < low or ep > high]
                row["outliers"] = outl
            except Exception:
                row["outliers"] = []
        else:
            row["Median"] = None
            row["Min Vendor"] = None
            row["Max Vendor"] = None
            row["outliers"] = []

        rows.append(row)

    return rows
