//! Validation of a single dimension-4 `(2,2,2,2)`-isogeny step against the
//! oracle.
//!
//! Ground truth comes from `tests/isogeny_step_vectors.json` (generated by
//! `sqisignhd-harness/extract_isogeny_step.py`): for individual *plain*
//! `IsogenyDim4` steps captured from real Level-1 verifications it records the
//! kernel `K_8`, the codomain theta null point, and `(input, image)` pairs.
//! Each captured codomain is additionally cross-checked against the Phase 0
//! `test_vectors_l1.json` per-step codomain for the same chain/step.

mod hd_common;
use hd_common::{load, parse_coords, parse_node, Pt, PHASE0_VECTORS};

use serde_json::Value;
use sqisign_verify::hd::IsogenyDim4;

const ISOGENY_STEP_VECTORS: &str = concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/tests/isogeny_step_vectors.json"
);

/// Parse the four kernel 8-torsion theta points.
fn parse_k8(v: &Value) -> [Pt; 4] {
    let arr = v.as_array().expect("K_8 array");
    assert_eq!(arr.len(), 4, "K_8 must have 4 points");
    core::array::from_fn(|k| parse_coords(&arr[k]))
}

#[test]
fn isogeny_step_codomain_matches_oracle() {
    let doc = load(ISOGENY_STEP_VECTORS);
    let main = load(PHASE0_VECTORS);
    let cases = doc["cases"].as_array().expect("cases");
    assert!(!cases.is_empty(), "no isogeny-step cases");

    let mut checked = 0usize;
    let mut from_f1 = 0usize;
    let mut from_f2 = 0usize;
    for case in cases {
        let k8 = parse_k8(&case["K_8"]);
        let iso = IsogenyDim4::from_kernel(&k8).expect("codomain must be computable");

        // The zero-dual classification must agree with the reference.
        assert_eq!(
            iso.has_zero_dual_null(),
            case["has_zero_dual"].as_bool().unwrap(),
            "has_zero_dual mismatch"
        );

        // Codomain null point matches the per-step oracle (projectively).
        let expected = parse_coords(&case["codomain_null"]);
        assert!(
            iso.codomain_null().projective_eq(&expected),
            "codomain mismatch vs per-step oracle"
        );

        // ... and the Phase 0 ground truth chain_codomains[chain][plain_step]:
        // chain_codomains index 0 is the gluing step, so plain step s sits at
        // index s.
        let vi = case["vector"].as_u64().unwrap() as usize;
        let chain = case["chain"].as_str().unwrap();
        let step = case["plain_step"].as_u64().unwrap() as usize;
        let node = &main["test_vectors"][vi]["stage4_compute_hd"][chain]["chain_codomains"][step];
        let from_main = parse_node(node);
        assert!(
            iso.codomain_null().projective_eq(&from_main),
            "codomain mismatch vs test_vectors_l1.json {chain}[{step}] (vector {vi})"
        );

        match chain {
            "F1" => from_f1 += 1,
            "F2_dual" => from_f2 += 1,
            _ => {}
        }
        checked += 1;
    }

    // The step must work for both half-chains (Phase 0 builds F1 and F2_dual).
    assert!(
        from_f1 > 0 && from_f2 > 0,
        "need steps from both half-chains"
    );
    assert!(checked >= 5, "expected at least 5 validated steps");
    println!(
        "validated {checked} isogeny-step codomains ({from_f1} F1, {from_f2} F2_dual), \
         each also matching test_vectors_l1.json"
    );
}

#[test]
fn isogeny_step_image_matches_oracle() {
    let doc = load(ISOGENY_STEP_VECTORS);
    let cases = doc["cases"].as_array().unwrap();

    let mut n_pairs = 0usize;
    for case in cases {
        if case["has_zero_dual"].as_bool().unwrap() {
            continue; // generic image formula does not apply; special_image (Phase 4)
        }
        let k8 = parse_k8(&case["K_8"]);
        let iso = IsogenyDim4::from_kernel(&k8).unwrap();
        for pair in case["image_pairs"].as_array().unwrap() {
            let p = parse_coords(&pair["in"]);
            let expected = parse_coords(&pair["out"]);
            let img = iso.image(&p).expect("generic image must be available");
            assert!(img.projective_eq(&expected), "isogeny image mismatch");
            n_pairs += 1;
        }
    }
    assert!(n_pairs > 0, "no image pairs validated");
    println!("validated {n_pairs} isogeny image evaluations");
}

#[test]
fn from_kernel_is_deterministic() {
    // The codomain is determined by the kernel alone (no domain null needed);
    // recomputing from the same K_8 yields the same projective codomain.
    let doc = load(ISOGENY_STEP_VECTORS);
    let case = &doc["cases"][0];
    let k8 = parse_k8(&case["K_8"]);
    let a = IsogenyDim4::from_kernel(&k8).unwrap();
    let b = IsogenyDim4::from_kernel(&k8).unwrap();
    assert!(a.codomain_null().projective_eq(b.codomain_null()));
}
