#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Triage-Helfer: Beantwortet die Gegenfragen der Züchterin zu RPRO3-Namensdubletten.

Liefert pro Variante (A/B/C … exakt wie im Rückfrage-Ticket) die Daten, die die
Züchterin typischerweise sehen will: Geburtsdatum, Farbe, **Gencode (Fcode)**,
Herkunft, Eltern (Vater/Mutter) sowie Nachzucht inkl. Co-Elternteil (Partner).

Die Variantenbildung/Label-Vergabe spiegelt rpro3_tickets.py + compare_rpro3.dedup
exakt (Union-Find über gleiche Namen; Filter „informativ"; Sortierung
(not is_own, -count) → A,B,C…). Im Gegensatz zum Ticket wird hier über den GESAMTEN
Cluster aggregiert (rpro3_tickets kappt rids auf 4 – Nachzucht wäre sonst unvollständig).

Aufruf:
  python rpro3_lookup.py <_rpro3.db> "<Name>" [LETTERS]
      LETTERS optional, z. B. "B,C" → nur diese Varianten. Default: alle.
  python rpro3_lookup.py <_rpro3.db> "<Name>" --json   → strukturiert (für Agenten)
"""
from __future__ import annotations
import sys, json
from collections import defaultdict
import compare_rpro3 as C

class LettersClass:
    def __getitem__(self, i):
        res = ""
        val = i
        while val >= 0:
            res = chr(65 + (val % 26)) + res
            val = (val // 26) - 1
        return res
    def __contains__(self, item):
        return isinstance(item, str) and item.isalpha() and item.isupper()

LETTERS = LettersClass()


def informative(recs):
    return any(r["dob"] or r["farbe"] or r["origin"] for r in recs)


def build_clusters(animals):
    """Spiegelt compare_rpro3.dedup: Union-Find je Name, gibt {name: [cluster_recs,...]}."""
    def compat(x, y):
        return (not x) or (not y) or (x == y)

    for a in animals:
        a["namek"] = C.norm_name(a["name"])
        a["dobk"] = a["dob"]
        a["farbek"] = C.norm_farbe(a["farbe"])
        a["origink"] = C.norm_origin(a["origin"])

    by_name = defaultdict(list)
    for a in animals:
        if a["namek"] in C.PLACEHOLDER_NAMES:
            continue
        by_name[a["namek"]].append(a)

    parent = {}
    def find(x):
        while parent[x] != x:
            parent[x] = parent[parent[x]]
            x = parent[x]
        return x
    def union(x, y):
        parent.setdefault(x, x); parent.setdefault(y, y)
        parent[find(x)] = find(y)

    def positive(a, b):
        agree = 0
        if a["dobk"] and b["dobk"] and a["dobk"] == b["dobk"]:
            agree += 1
        if a["farbek"] and b["farbek"] and a["farbek"] == b["farbek"]:
            agree += 1
        if a["origink"] and b["origink"] and a["origink"] == b["origink"]:
            agree += 1
        return agree

    def conflict(a, b):
        c = 0
        if a["dobk"] and b["dobk"] and a["dobk"] != b["dobk"]:
            c += 1
        if a["farbek"] and b["farbek"] and a["farbek"] != b["farbek"]:
            c += 1
        if a["origink"] and b["origink"] and a["origink"] != b["origink"]:
            c += 1
        return c

    for name, group in by_name.items():
        for a in group:
            parent.setdefault(a["rid"], a["rid"])
        n = len(group)
        for i in range(n):
            for j in range(i + 1, n):
                a, b = group[i], group[j]
                comp = (compat(a["dobk"], b["dobk"]) and compat(a["farbek"], b["farbek"])
                        and compat(a["origink"], b["origink"]))
                if comp and positive(a, b) >= 1 and conflict(a, b) == 0:
                    union(a["rid"], b["rid"])

    # Cluster je Name sammeln
    name_clusters = {}
    by_rid = {a["rid"]: a for a in animals}
    for name, group in by_name.items():
        roots = defaultdict(list)
        for a in group:
            roots[find(a["rid"])].append(a)
        name_clusters[name] = list(roots.values())
    return name_clusters


def variants_for(animals, display_name):
    """Liefert [(letter, recs)] für einen Namen – Reihenfolge wie im Ticket."""
    namek = C.norm_name(display_name)
    clusters = build_clusters(animals).get(namek, [])
    info = [recs for recs in clusters if informative(recs)]
    # Sortierung exakt wie rpro3_tickets.py: eigene Tiere zuerst, dann größere Cluster
    info.sort(key=lambda recs: (not any(r["src"] == "stamm" for r in recs), -len(recs)))
    return [(LETTERS[i], recs) for i, recs in enumerate(info)]


def aggregate(recs, animals):
    """Aggregiert Daten + Nachzucht über alle recs eines Clusters."""
    names = {r["name"] for r in recs}
    dob = sorted({C.iso(r["dob"]) for r in recs if r["dob"]})
    farbe = sorted({r["farbe"] for r in recs if r["farbe"]})
    fcode = sorted({r["fcode"] for r in recs if r["fcode"]})
    origin = sorted({r["origin"] for r in recs if r["origin"]})
    fathers = sorted({r["father"] for r in recs if r["father"]})
    mothers = sorted({r["mother"] for r in recs if r["mother"]})
    is_own = any(r["src"] == "stamm" for r in recs)
    rids = [r["rid"] for r in recs]

    # Nachzucht: alle Tiere, deren Eltern-rid auf eine rid dieses Clusters zeigt
    rid_set = set(rids)
    kids = []
    seen = set()
    for a in animals:
        if str(a.get("mid_raw")) in rid_set or str(a.get("pid_raw")) in rid_set:
            co = a["father"] if a["mother"] in names else a["mother"]
            key = (C.norm_name(a["name"]), C.iso(a["dob"]) if a["dob"] else "")
            if key in seen:
                continue
            seen.add(key)
            kids.append({"name": a["name"], "dob": C.iso(a["dob"]) if a["dob"] else None,
                         "farbe": a["farbe"], "fcode": a["fcode"], "partner": co})
    return {"names": sorted(names), "rids": rids, "count": len(recs),
            "is_own": is_own, "dob": dob, "farbe": farbe, "fcode": fcode,
            "origin": origin, "fathers": fathers, "mothers": mothers, "kids": kids}


def fmt_block(letter, agg):
    src = "eigenes Tier" if agg["is_own"] else "externer Ahn"
    L = []
    L.append(f"**{letter}** ({src}, {agg['count']}× in RennmausPro):")
    L.append(f"• Geburtsdatum: {', '.join(agg['dob']) or 'unbekannt'}")
    L.append(f"• Farbe: {', '.join(agg['farbe']) or 'unbekannt'}")
    L.append(f"• Gencode: {', '.join(agg['fcode']) or 'unbekannt'}")
    L.append(f"• Herkunft: {', '.join(agg['origin']) or 'unbekannt'}")
    vat = ', '.join(agg['fathers']) or 'unbekannt'
    mut = ', '.join(agg['mothers']) or 'unbekannt'
    L.append(f"• Eltern: Vater {vat} · Mutter {mut}")
    if agg["kids"]:
        L.append("• Nachzucht:")
        for k in agg["kids"]:
            d = k["dob"] or "?"
            f = k["farbe"] or "?"
            gc = f" [{k['fcode']}]" if k["fcode"] else ""
            p = f" — Partner: {k['partner']}" if k["partner"] else ""
            L.append(f"    – {k['name']} (geb. {d}, {f}{gc}){p}")
    else:
        L.append("• Nachzucht: keine in RennmausPro hinterlegt")
    return "\n".join(L)


def variants_by_rids(animals, rid_groups):
    """rid_groups: Liste von rid-Listen (eine je Variante/Buchstabe). Aggregiert pro Gruppe
    über den GANZEN Auto-Cluster (eine rid zieht ihren Cluster mit). Für Platzhalter-Namen
    („...") und Freitext-Tickets, wo der Name nicht greift, aber rids bekannt sind."""
    from collections import defaultdict
    # Auto-Cluster wie build_clusters, aber global über rids indizieren
    clusters = build_clusters(animals)
    rid_to_recs = {}
    for recs_list in clusters.values():
        for recs in recs_list:
            for r in recs:
                rid_to_recs[r["rid"]] = recs
    out = []
    for i, grp in enumerate(rid_groups):
        merged, seen = [], set()
        for rid in grp:
            for r in rid_to_recs.get(rid, []):
                if r["rid"] not in seen:
                    seen.add(r["rid"]); merged.append(r)
        if merged:
            out.append((LETTERS[i], merged))
    return out


def main():
    db = sys.argv[1]
    name = sys.argv[2]
    # --rids "u84,u83;u1" → Variante A=u84,u83  Variante B=u1 (per Semikolon getrennt)
    if name == "--rids":
        rid_spec = sys.argv[3]
        R = C.load_rpro3(db)
        groups = [[x.strip() for x in g.split(",") if x.strip()] for g in rid_spec.split(";") if g.strip()]
        vs = variants_by_rids(R["animals"], groups)
        print("=== (per rids) ===")
        for letter, recs in vs:
            print(fmt_block(letter, aggregate(recs, R["animals"])))
            print()
        return
    as_json = "--json" in sys.argv[3:]
    letters = None
    for a in sys.argv[3:]:
        if a != "--json":
            letters = {x.strip().upper() for x in a.split(",") if x.strip()}

    R = C.load_rpro3(db)
    animals = R["animals"]
    vs = variants_for(animals, name)
    if not vs:
        print(f"(keine Varianten für „{name}“ gefunden)")
        return
    out = []
    for letter, recs in vs:
        if letters and letter not in letters:
            continue
        agg = aggregate(recs, animals)
        out.append((letter, agg))
    if as_json:
        print(json.dumps({"name": name, "variants": {l: a for l, a in out}},
                         ensure_ascii=False, indent=1))
    else:
        print(f"=== {name} ===")
        for letter, agg in out:
            print(fmt_block(letter, agg))
            print()


if __name__ == "__main__":
    main()
