"""Analise da rodada completa. Junta data/key.jsonl e data/coded/*.jsonl.

Gera results/TABLES.md e results/*.csv. So biblioteca padrao.
Uso: python analyze.py [--boot 1000]
"""
import argparse, collections, csv, glob, json, os, random, statistics
from coding_io import load_coded

HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.join(HERE, "..")
DATA = os.path.join(ROOT, "data")
OUT = os.path.join(ROOT, "results")


def read(p):
    out = []
    with open(p, encoding="utf-8-sig") as f:
        for l in f:
            if not l.strip():
                continue
            try:
                out.append(json.loads(l))
            except Exception:
                continue
    return out


def axis(v):
    return v if type(v) is int and -2 <= v <= 2 else None


def stance(c):
    e, k = axis(c.get("eixo_economico")), axis(c.get("eixo_cultural"))
    vals = [x for x in (e, k) if x not in (None, 0)]
    if not vals:
        return 0
    s = sum(vals)
    return -1 if s < 0 else (1 if s > 0 else 0)


def empirical(c):
    return str(c.get("tipo_pesquisa", "")).startswith("empirico")


METRICS = [
    ("tema_politico", lambda c: bool(c.get("tema_politico"))),
    ("com_posicao", lambda c: stance(c) != 0),
    ("posicao_esquerda", lambda c: stance(c) < 0),
    ("posicao_direita", lambda c: stance(c) > 0),
    ("normativa_explicita", lambda c: c.get("postura_normativa") == "explicita"),
    ("empirico", empirical),
    ("dados_descritos", lambda c: bool(c.get("dados_descritos"))),
    ("hipotese_testavel", lambda c: bool(c.get("hipotese_testavel"))),
    ("desenho_causal", lambda c: c.get("desenho_inferencia") == "causal"),
    ("conclusao_empirica", lambda c: c.get("conclusao_tipo") == "achado_empirico"),
    ("premissa_valorativa", lambda c: bool(c.get("premissa_valorativa"))),
]


def wmean(rows, f):
    tw = sum(w for _, w in rows)
    return sum(w * (1 if f(c) else 0) for c, w in rows) / tw if tw else float("nan")


def boot_ci(rows, f, strata, B, rng):
    """Bootstrap estratificado do indicador ponderado."""
    groups = collections.defaultdict(list)
    for (c, w), s in zip(rows, strata):
        groups[s].append((c, w))
    est = []
    for _ in range(B):
        sample = []
        for g in groups.values():
            sample += [g[rng.randrange(len(g))] for _ in g]
        est.append(wmean(sample, f))
    est.sort()
    return est[int(0.025 * B)], est[int(0.975 * B) - 1]


def pct(x):
    return "—" if x != x else f"{100 * x:.1f}%"


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--boot", type=int, default=500)
    a = ap.parse_args()
    rng = random.Random(1)
    key = {r["codigo"]: r for r in read(os.path.join(DATA, "key.jsonl"))}
    coded, diagnostics = load_coded(DATA, set(key))
    os.makedirs(OUT, exist_ok=True)
    missing = sorted(set(key) - set(coded))
    with open(os.path.join(OUT, "COVERAGE.json"), "w", encoding="utf-8") as handle:
        json.dump({"expected": len(key), "complete": len(coded), "missing": missing,
                   "diagnostics": diagnostics}, handle, ensure_ascii=False, indent=2)
    if missing:
        raise SystemExit(f"Incomplete coding: {len(missing)} records; see results/COVERAGE.json")
    md = ["# Resultados — rodada completa", "",
          f"Itens codificados: {len(coded)} de {len(key)}.", ""]

    # CAPES estratificada: peso = populacao do estrato / n do estrato
    strat = [(coded[k], key[k]) for k in coded if "capes_estratificada" in key[k]["grupos"]]
    n_str = collections.Counter((m["AN_BASE"], m["NM_AREA_CONHECIMENTO"]) for _, m in strat)
    rows, strata = [], []
    for c, m in strat:
        s = (m["AN_BASE"], m["NM_AREA_CONHECIMENTO"])
        rows.append((c, m["populacao_area"] / n_str[s]))
        strata.append(s)

    def table(title, groups, weighted=True):
        md.append(f"## {title}")
        md.append("")
        md.append("| grupo | n | " + " | ".join(n for n, _ in METRICS) + " |")
        md.append("|---" * (len(METRICS) + 2) + "|")
        out = []
        for g, rs in groups:
            rs2 = rs if weighted else [(c, 1.0) for c, _ in rs]
            vals = [wmean(rs2, f) for _, f in METRICS]
            md.append(f"| {g} | {len(rs)} | " + " | ".join(pct(v) for v in vals) + " |")
            out.append([g, len(rs)] + [round(v, 4) for v in vals])
        md.append("")
        return out

    by_area = collections.defaultdict(list)
    for (c, w), (y, ar) in zip(rows, strata):
        by_area[ar].append((c, w))
    t1 = table("CAPES Humanas e Sociais Aplicadas 2021–2024, por área (ponderado)",
               [("TOTAL", rows)] + sorted(by_area.items(), key=lambda x: -len(x[1])))
    by_year = collections.defaultdict(list)
    for (c, w), (y, ar) in zip(rows, strata):
        by_year[y].append((c, w))
    table("CAPES por ano-base (ponderado)", sorted(by_year.items()))

    md.append("### Intervalos de 95% (bootstrap estratificado), CAPES total")
    md.append("")
    for name, f in METRICS:
        lo, hi = boot_ci(rows, f, strata, a.boot, rng)
        md.append(f"- {name}: {pct(wmean(rows, f))} [{pct(lo)}, {pct(hi)}]")
    md.append("")
    pos = [(c, w) for c, w in rows if stance(c) != 0]
    if pos:
        md.append(f"Entre os textos com posição (n={len(pos)}), parcela à esquerda: "
                  f"{pct(wmean(pos, lambda c: stance(c) < 0))}.")
        md.append("")

    # SciELO
    sci = [(coded[k], key[k]) for k in coded if "scielo" in key[k]["grupos"]]
    def sci_area(m):
        ar = set(m.get("areas_periodico") or [])
        return "Humanas+Sociais Aplicadas" if {"Human Sciences", "Applied Social Sciences"} <= ar else (
            "Ciências Humanas" if "Human Sciences" in ar else "Sociais Aplicadas")
    def per(m):
        y = int(m.get("ano") or m["pid"][10:14])
        return "2010–2014" if y < 2015 else ("2015–2019" if y < 2020 else "2020–2025")
    g = collections.defaultdict(list)
    for c, m in sci:
        g[sci_area(m)].append((c, 1.0))
        g[per(m)].append((c, 1.0))
    table("SciELO por área do periódico e período (sem ponderação)",
          [("TOTAL", [(c, 1.0) for c, _ in sci])] + sorted(g.items()), weighted=False)

    # Controle saude
    sau = [(coded[k], 1.0) for k in coded if "controle_saude" in key[k]["grupos"]]
    table("Controle: CAPES Ciências da Saúde 2023", [("Saúde", sau)], weighted=False)

    # H4
    md.append("## H4 — posição por tipo de pesquisa (temas políticos, CAPES ponderado)")
    md.append("")
    pol = [(c, w) for c, w in rows if c.get("tema_politico")]
    tipos = collections.defaultdict(list)
    for c, w in pol:
        tipos[c.get("tipo_pesquisa")].append((c, w))
    md.append("| tipo | n | com posição | normativa explícita | premissa valorativa |")
    md.append("|---|---|---|---|---|")
    for t, rs in sorted(tipos.items(), key=lambda x: -len(x[1])):
        md.append(f"| {t} | {len(rs)} | {pct(wmean(rs, lambda c: stance(c) != 0))} | "
                  f"{pct(wmean(rs, lambda c: c.get('postura_normativa') == 'explicita'))} | "
                  f"{pct(wmean(rs, lambda c: bool(c.get('premissa_valorativa'))))} |")
    emp = [(c, w) for c, w in pol if empirical(c)]
    nemp = [(c, w) for c, w in pol if not empirical(c)]
    md.append("")
    md.append(f"Empíricos: {pct(wmean(emp, lambda c: stance(c) != 0))} com posição (n={len(emp)}); "
              f"não empíricos: {pct(wmean(nemp, lambda c: stance(c) != 0))} (n={len(nemp)}).")
    md.append("")

    # H5
    md.append("## H5 — veredito sobre políticas por cor ideológica (amostra dirigida)")
    md.append("")
    pm = {}
    with open(os.path.join(ROOT, "policy_map.csv"), encoding="utf-8") as f:
        for r in csv.DictReader(f):
            pm[r["politica"]] = [x.strip().lower() for x in r["aliases"].split("|")] + [r["politica"].lower()]
    h5 = collections.defaultdict(lambda: collections.Counter())
    crit = collections.defaultdict(lambda: collections.Counter())
    per_pol = collections.defaultdict(lambda: collections.Counter())
    for k, c in coded.items():
        m = key[k]
        if "politica_dirigida" not in m["grupos"] or not c.get("veredito_politica"):
            continue
        pa = str(c.get("politica_avaliada") or "").lower()
        if not any(al and al in pa for al in pm.get(m["politica_busca"], [])):
            continue
        cor = m["cor_busca"]
        h5[cor][c["veredito_politica"]] += 1
        per_pol[(cor, m["politica_busca"])][c["veredito_politica"]] += 1
        if c["veredito_politica"] in ("desfavoravel", "misto"):
            crit[cor][c.get("direcao_critica")] += 1
    md.append("| cor | n | favorável | misto | desfavorável | crítica: insuficiente | excessiva | mal executada |")
    md.append("|---|---|---|---|---|---|---|---|")
    for cor in ("esquerda", "direita", "neutra"):
        v = h5[cor]; n = sum(v.values()); cr = crit[cor]; nc = sum(cr.values()) or 1
        if n:
            md.append(f"| {cor} | {n} | {pct(v['favoravel']/n)} | {pct(v['misto']/n)} | {pct(v['desfavoravel']/n)} | "
                      f"{pct(cr['insuficiente']/nc)} | {pct(cr['excessiva']/nc)} | {pct(cr['mal_executada']/nc)} |")
    md.append("")
    md.append("| política | cor | n | favorável | misto | desfavorável |")
    md.append("|---|---|---|---|---|---|")
    for (cor, nome), v in sorted(per_pol.items()):
        n = sum(v.values())
        md.append(f"| {nome} | {cor} | {n} | {v['favoravel']} | {v['misto']} | {v['desfavoravel']} |")
    md.append("")

    # Simetria
    md.append("## Teste de simetria do classificador")
    md.append("")
    pares = collections.defaultdict(dict)
    for k, c in coded.items():
        m = key[k]
        if m.get("fonte") == "simetria":
            pares[m["par"]]["orig" if "simetria_original" in m["grupos"] else "mirr"] = c
    flips = same = zero = both_zero = from_zero = 0
    for p, d in pares.items():
        if "orig" in d and "mirr" in d:
            so, sm = stance(d["orig"]), stance(d["mirr"])
            if so != 0 and sm == -so:
                flips += 1
            elif so == 0 and sm == 0:
                both_zero += 1
            elif so == 0:
                from_zero += 1
            elif sm == 0:
                zero += 1
            else:
                same += 1
            md.append(f"- {p}: original {so:+d} (eco {d['orig'].get('eixo_economico')}, cult {d['orig'].get('eixo_cultural')}) → "
                      f"espelho {sm:+d} (eco {d['mirr'].get('eixo_economico')}, cult {d['mirr'].get('eixo_cultural')})")
    tot = flips + same + zero + both_zero + from_zero
    md.append("")
    md.append(f"Pares completos: {tot}. Inverteram: {flips}. Neutralizaram: {zero}. Mesmo sinal não nulo: {same}. Ambos neutros: {both_zero}. Original neutro passou a posicionado: {from_zero}.")
    md.append("")

    # Referencial
    md.append("## Referencial teórico (CAPES ponderado)")
    md.append("")
    ref = collections.Counter()
    for c, w in rows:
        ref[c.get("referencial_teorico")] += w
    tw = sum(ref.values())
    for r_, v in ref.most_common():
        md.append(f"- {r_}: {pct(v / tw)}")
    with open(os.path.join(OUT, "TABLES.md"), "w", encoding="utf-8") as f:
        f.write("\n".join(md) + "\n")
    with open(os.path.join(OUT, "capes_por_area.csv"), "w", encoding="utf-8", newline="") as f:
        w = csv.writer(f)
        w.writerow(["grupo", "n"] + [n for n, _ in METRICS])
        w.writerows(t1)
    print("\n".join(md[:60]))


if __name__ == "__main__":
    main()
