"""Produce the finite certificate from the curve equation and fixed conventions.

No target Kurihara residue is an input. The eigenline file is written and
hashed before the summation starts. An existing certificate is never replaced.
"""
import argparse
import hashlib
import json
import platform
from datetime import datetime, timezone
from pathlib import Path
import numpy as np
from manin import Manin, a_ell, build_eigenline, discrete_logs


def dump(path, value):
    path.write_text(json.dumps(value, ensure_ascii=False, sort_keys=True,
                               separators=(",", ":")) + "\n", encoding="utf-8")


def sha(path):
    return hashlib.sha256(path.read_bytes()).hexdigest()


def sparse_rows(matrix):
    return [[[int(i), int(row[i])] for i in np.flatnonzero(row)] for row in matrix]


def check(condition, stage, detail):
    if not condition:
        raise ArithmeticError(f"{stage}: {detail}")


def build(output):
    output.mkdir(parents=True, exist_ok=True)
    eigen_path = output / "eigenline_certificate.json"
    if eigen_path.exists():
        raise FileExistsError("choose a fresh output directory; an existing eigenline is immutable")
    events = []

    def event(stage, **extra):
        item = {"stage": stage, "utc": datetime.now(timezone.utc).isoformat(), **extra}
        events.append(item)
        dump(output / "producer_events.json", events)
        print(json.dumps(item, sort_keys=True), flush=True)

    event("A1_MANIN_HECKE_START")
    M = Manin(389, 11)
    data = build_eigenline(M)
    K = np.array(data["manin_kernel"], dtype=np.int64)
    v = np.array(data["eigenvector"], dtype=np.int64)
    R = M.relations()
    check(data["relation_rank"] == 325 and K.shape == (390, 65), "A1_MANIN_RANK", "unexpected quotient dimension")
    check(np.all(R @ K % 11 == 0), "A1_MANIN_KERNEL", "relation residual")
    check(data["hecke_eigenspace_dimension"] == 2, "A2_HECKE", "unexpected joint eigenspace dimension")
    check(data["plus_eigenspace_dimension"] == 1, "A3_PLUS", "unexpected geometric plus dimension")
    for i in range(390):
        start, end = M.generator_endpoints(i)
        check(np.all((M.path(start, end) @ K-K[i]) % 11 == 0), "A1_PATH", f"generator {i}")
    check(np.all(M.plus_relations() @ v % 11 == 0), "A3_PLUS", "involution residual")
    qs = [2, 3, 5, 7, 13, 17, 19]
    matrices = {str(q): M.hecke(q).tolist() for q in qs}
    for q in qs:
        H = M.hecke(q)
        check(np.all((H @ v-a_ell(q)*v) % 11 == 0), "A4_HECKE_CROSSCHECK", f"q={q}")
        check(np.all((R @ H % 11) @ K % 11 == 0), "A4_HECKE_DESCENT", f"q={q}")
    H2, H3 = M.hecke(2), M.hecke(3)
    check(np.all(((H2 @ H3-H3 @ H2) % 11) @ K % 11 == 0), "A4_HECKE_COMMUTATOR", "T2,T3")
    counts = {str(q): {"a_ell": a_ell(q), "point_count": q+1-a_ell(q)}
              for q in qs+[11,397,991]}
    certificate = {
        "schema": "kurihara-finite-eigenline-v1",
        "parameters": {"level": 389, "prime": 11, "a_invariants": [0,1,1,-2,0],
                       "summation_primes": [397,991], "primitive_roots": [5,6]},
        "generator_order": [list(M.pair(i)) for i in range(390)],
        "conventions": {
            "symbol": "m(c,d)=g{0,infinity}, det(g)=1, bottom row (c,d) modulo389",
            "projective_order": "(1,0),(1,1),...,(1,388),(0,1)",
            "S": [[0,-1],[1,0]], "R": [[0,-1],[1,-1]],
            "plus": "lambda(c,d)=lambda(-c,d); geometric conjugation, real-part functional",
            "cusp_path": "{infinity,a/n}; ordinary floor continued fractions",
            "hecke_rows": "row i is T_q(m_i); H_q lambda=a_q lambda"
        },
        "linear_algebra": data,
        "relation_rows": sparse_rows(R),
        "plus_rows": sparse_rows(M.plus_relations()),
        "hecke_matrices": matrices,
        "point_counts": counts,
        "producer_algebra_checks": {"generator_paths_on_full_quotient": 390,
            "hecke_descent_primes": qs, "commuting_pair": [2,3],
            "eigen_residuals_zero": qs, "plus_residual_zero": True}
    }
    dump(eigen_path, certificate)
    eigen_hash = sha(eigen_path)
    event("A5_EIGENLINE_LOCKED_BEFORE_SUM", sha256=eigen_hash,
          normalization_index=data["normalization_index"],
          relation_rank=data["relation_rank"],
          hecke_dimension=data["hecke_eigenspace_dimension"], plus_dimension=1)

    tables = {}
    for ell, root in [(397,5),(991,6)]:
        logs = discrete_logs(ell, root, 11)
        check((ell-1) % 11 == 0 and (a_ell(ell)-ell-1) % 11 == 0,
              "B1_KOLYVAGIN_PRIME", f"ell={ell}")
        tables[str(ell)] = {"root": root, "values": logs}
    log_path = output / "log_tables.json"
    dump(log_path, {"modulus": 11, "tables": tables})
    logs397, logs991 = tables["397"]["values"], tables["991"]["values"]
    n = 397*991
    count = total = 0
    blocks = []
    csv_path = output / "kurihara_terms.csv"
    event("B2_FULL_SUM_START", eigenline_sha256=eigen_hash, n=n)
    # Every unit gets an explicit row, including zero-log and zero-symbol terms.
    with csv_path.open("w", encoding="ascii", newline="") as stream:
        stream.write("a,symbol,log_397,log_991,term\n")
        for first in range(1, n, 10000):
            last = min(first+9999, n-1)
            block_count = block_total = 0
            for a in range(first, last+1):
                if a % 397 == 0 or a % 991 == 0:
                    continue
                value = M.scalar_cusp(a, n, v)
                l397, l991 = logs397[a % 397], logs991[a % 991]
                product = value*l397*l991
                stream.write(f"{a},{value},{l397},{l991},{product % 11}\n")
                block_count += 1
                block_total += product
            blocks.append({"first_a": first, "last_a": last,
                           "unit_count": block_count, "raw_product_sum": block_total,
                           "residue": block_total % 11})
            count += block_count
            total += block_total
    check(count == (397-1)*(991-1), "B2_UNIT_ENUMERATION", str(count))
    check(sha(eigen_path) == eigen_hash, "B2_NORMALIZATION_LOCK", "eigenline changed during sum")
    result = {"schema": "kurihara-finite-sum-v1", "n": n, "modulus": 11,
              "eigenline_sha256": eigen_hash, "log_tables_sha256": sha(log_path),
              "terms_csv_sha256": sha(csv_path), "unit_count": count,
              "raw_product_sum": total, "residue": total % 11, "blocks": blocks}
    dump(output / "kurihara_certificate.json", result)
    dump(output / "environment.json", {"python": platform.python_version(),
                                      "numpy": np.__version__, "platform": platform.platform()})
    event("B3_PRODUCER_SUM_COMPLETE", unit_count=count, residue=total % 11,
          terms_csv_sha256=result["terms_csv_sha256"])
    return result


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--output", type=Path, default=Path("results"))
    args = parser.parse_args()
    try:
        build(args.output)
    except Exception as exc:
        args.output.mkdir(parents=True, exist_ok=True)
        dump(args.output / "producer_failure.json", {"type": type(exc).__name__, "message": str(exc)})
        raise
