"""Zero-dep tests for genotype.py GEN-3b normalization.

Run: python test_genotype.py   (exit 0 = all pass)
Covers: Uw/uw -> G/g alias, Sls/WP second spotting locus, dea/Dea/taub
hearing-deaf flag, WFNZ/RV/GV/DP provenance tags. Per hive/agents/god/GENETIK-notation.md.
"""
import sys
import genotype as g


def check(name, cond):
    if not cond:
        print(f"FAIL: {name}")
        check.failed += 1
    else:
        print(f"ok:   {name}")
check.failed = 0


# --- Uw/uw == G/g (same locus) ---
r = g.parse("aa Cc Dd Ee Uwuw Pp spsp rere")
check("Uw->G: G locus mapped", r["mapped8locus"].get("G") == ["G", "g"])
check("Uw->G: nothing left in unmapped", r["unmappedTokens"] == [])

r = g.parse("UwUw")
check("UwUw -> GG", r["mapped8locus"].get("G") == ["G", "G"])
r = g.parse("uwuw")
check("uwuw -> gg", r["mapped8locus"].get("G") == ["g", "g"])
r = g.parse("uw[d]uw[d]")
check("uw[d]uw[d] -> gg (dense underwhite)", r["mapped8locus"].get("G") == ["g", "g"])
# GEN-5 (ticket 5151ab20 / Vance): the het 'Uwuw[d]' (one Underwhite, one dense
# underwhite) must parse to the G locus as Gg — never crash on the [d] modifier.
r = g.parse("aa Cc[chm] D- ee Uwuw[d] PP spsp")
check("Uwuw[d] -> Gg (Vance, het dense underwhite)", r["mapped8locus"].get("G") == ["G", "g"])
check("Uwuw[d] full string: nothing unmapped", r["unmappedTokens"] == [])

# Gg and Uwuw must produce the SAME mapped locus (so they stop being a conflict)
check("Gg identical to Uwuw at G locus",
      g.parse("Gg")["mapped8locus"]["G"] == g.parse("Uwuw")["mapped8locus"]["G"])

# --- Sls / WP second spotting locus ---
check("WP -> Sls het", g.parse("WP")["mapped8locus"].get("Sls") == ["Sl", "sl"])
check("[WP] (bracketed) -> Sls het", g.parse("[WP]")["mapped8locus"].get("Sls") == ["Sl", "sl"])
check("Sls token -> Sls het", g.parse("Sls")["mapped8locus"].get("Sls") == ["Sl", "sl"])
check("sls -> Sls wild", g.parse("sls")["mapped8locus"].get("Sls") == ["sl", "sl"])
check("S(l)s(l) -> Sl,sl", g.parse("S(l)s(l)")["mapped8locus"].get("Sls") == ["Sl", "sl"])
# Sp and Sls are TWO distinct loci on the same animal (Superschecke)
r = g.parse("spsp WP")
check("Sp + Sls coexist (two spotting loci)",
      r["mapped8locus"].get("Sp") == ["sp", "sp"] and r["mapped8locus"].get("Sls") == ["Sl", "sl"])

# --- deafness flag (after spsp), case-sensitive ---
check("dea (lower) -> deaf True", g.parse("spsp dea")["deaf"] is True)
check("taub -> deaf True", g.parse("taub")["deaf"] is True)
check("Dea (upper) -> hearing False", g.parse("spsp Dea")["deaf"] is False)
check("(hörend) -> hearing False", g.parse("(hörend)")["deaf"] is False)
check("no deaf token -> None", g.parse("aa Cc")["deaf"] is None)
# deafness is NOT a genotype locus and must not pollute mapped/unmapped silently
check("deaf flag not in unmapped", "dea" not in g.parse("spsp dea")["unmappedTokens"])

# --- provenance / breeding tags (never genotype, never conflict) ---
check("WFNZ -> tag", g.parse("aa WFNZ")["tags"] == ["WFNZ"])
check("RV -> tag", g.parse("(RV)")["tags"] == ["RV"])
check("GV -> tag", g.parse("(GV)")["tags"] == ["GV"])
check("DP -> tag", g.parse("[DP]")["tags"] == ["DP"])
check("tag not in genotype loci", g.parse("WFNZ")["mapped8locus"] == {})
check("tag not in unmapped", g.parse("aa WFNZ")["unmappedTokens"] == [])

# --- #32/#34: dense-underwhite [d] annotation stripped, maps to G locus (never uw) ---
r = g.parse("Uwuw[d]")
check("Uwuw[d] -> Gg (dense underwhite, mixed case)", r["mapped8locus"].get("G") == ["G", "g"])
check("Uwuw[d] leaves nothing unmapped (no stray uw token)", r["unmappedTokens"] == [])
r = g.parse("aa Cc[chm] D- ee Uwuw[d] PP spsp")
check("Vance genotype: G locus = Gg (display via G, not uw)", r["mapped8locus"].get("G") == ["G", "g"])
check("Vance genotype: E locus = ee (Fuchs)", r["mapped8locus"].get("E") == ["e", "e"])
check("Vance genotype: no unmapped tokens", r["unmappedTokens"] == [])

# --- #42: E-locus e-dash semantics ---
# ee[-] = visible Fuchs -> ee (recessive phenotype implies homozygosity)
check("ee[-] -> ee (visible Fuchs)", g.parse("ee[-]")["mapped8locus"].get("E") == ["e", "e"])
check("E- stays [E, ?] (valid unknown partner)", g.parse("E-")["mapped8locus"].get("E") == ["E", "?"])
# bare e- / e[-] is genetically impossible -> invalid (not mapped, surfaced as unmapped)
check("e- invalid -> not mapped at E", g.parse("e-")["mapped8locus"].get("E") is None)
check("e- invalid -> in unmappedTokens", "e-" in g.parse("e-")["unmappedTokens"])
check("e[-] invalid -> not mapped at E", g.parse("e[-]")["mapped8locus"].get("E") is None)
r = g.parse("Aa CC D- ee[-] Gg Pp spsp")
check("Algierfuchs genotype: E = ee (Fuchs)", r["mapped8locus"].get("E") == ["e", "e"])
check("Algierfuchs genotype: no unmapped tokens", r["unmappedTokens"] == [])

# --- looks_like_genotype recognizes Uw-bearing cells ---
check("looks_like_genotype sees Uw as G",
      g.looks_like_genotype("aa Cc Uwuw") is True)


# ── genotype_to_farbschlag — Python mirror of catalog.ts genotypeToFarbschlag ──
# Genetik/Farbschlag ist korrektheitskritisch: jeder Ticket-Fall bekommt einen
# Regressionstest (Konvention). The base name is returned WITHOUT the Schecke/Rex
# modifier (the import carries those via the Sp/Re loci, not the variety id).
def fs(s):
    return g.genotype_to_farbschlag(g.parse(s)["mapped8locus"])

# Ticket 3f5942a2 — Goldfuchs (ee) NOT Gold (EE): a fox genotype must resolve to
# a Fuchs variety, never the substring-shorter „Gold".
check("3f5942a2: ee Fuchs -> Goldfuchs (not Gold)", fs("AA CC DD ee GG pp spsp") == "Goldfuchs")
check("3f5942a2: ee[f] Fuchsschimmel -> Goldfuchsschimmel",
      fs("Aa C- D- ee[f] G- pp Spsp") == "Goldfuchsschimmel")
# Ticket 1aac054f — namenloses Weibchen *13.08.2025: Kohlfuchsschimmel (not Gold).
check("1aac054f: aa ee[f] -> Kohlfuchsschimmel",
      fs("aa Cc[chm] D- ee[f] Gg Pp Spsp") == "Kohlfuchsschimmel")
# Ticket 998087e2 — dd must NOT be ignored: Dilute Agouti (not Agouti).
check("998087e2: AA dd EE -> Dilute Agouti", fs("AA CC dd EE GG PP spsp") == "Dilute Agouti")
check("998087e2 counter: AA DD EE -> Agouti (no dilute)", fs("AA CC DD EE GG PP spsp") == "Agouti")
# Ticket 06217eb3 — dd Anthrazit: Dilute Anthrazit (not Anthrazit).
check("06217eb3: aa dd gg -> Dilute Anthrazit", fs("aa CC dd Ee gg P- spsp") == "Dilute Anthrazit")
# Ticket e22764aa — ee[-] = Fuchs (Schimmel-Modifier unbekannt) -> Blaufuchs,
# NEVER Blaufuchsschimmel (the „(schimmel)" parenthetical is „möglich", not definitiv).
check("e22764aa: aa ee[-] gg -> Blaufuchs (not …schimmel)",
      fs("aa C- D- ee[-] gg P- spsp") == "Blaufuchs")
# Ticket cc9ea3fe / 1a508c04 — Mamta Mini Ee resolves to Agouti (AA, E_).
check("Mamta Mini: AA Ee -> Agouti", fs("AA CC D- Ee Gg PP spsp") == "Agouti")
# E-locus phenotype rules (breeder): ef/ef = Schimmel family, ef/e = Fuchsschimmel.
check("efef agouti base -> Orangeschimmel", fs("AA CC DD e[f]e[f] GG PP spsp") == "Orangeschimmel")
check("ef/e het -> Fuchsschimmel family (Kohlfuchsschimmel)",
      fs("aa CC DD ee[f] GG PP spsp") == "Kohlfuchsschimmel")
# Unknown allele = copy of the visible partner (GEN-5): A? -> AA, D? -> DD.
check("unknown copies known: AA C? DD ee GG pp -> Goldfuchs", fs("AA C- DD ee GG pp spsp") == "Goldfuchs")
# An incomplete parse must NOT throw and must not be invented as a real colour.
check("empty mapping -> Unbekannt", g.genotype_to_farbschlag({}) == g.UNKNOWN_FARBSCHLAG)

if check.failed:
    print(f"\n{check.failed} test(s) FAILED")
    sys.exit(1)
print("\nALL PASS")
