//! Cross-validation test: assert the Rust Level 1 precomputed constants
//! produce byte-identical output to the reference implementation.
//!
//! The expected hex bytes below were generated by the external harness
//! under `tools/c-validate/precomp_validate.c`.

use hex_literal::hex;
use sqisign_verify::ec::point::{ec_mul, xdbl_a24};
use sqisign_verify::ec::{EcCurve, EcPoint};
use sqisign_verify::fp::{Fp2, FpBackend};
use sqisign_verify::params::Level1;
use sqisign_verify::precomp::level1::*;

fn fp2_encode<L: FpBackend>(a: &Fp2<L>) -> [u8; 64] {
    let arr = a.encode();
    let mut out = [0u8; 64];
    out.copy_from_slice(arr.as_ref());
    out
}

fn hex_str(bytes: &[u8]) -> String {
    let mut s = String::with_capacity(bytes.len() * 2);
    for b in bytes {
        s.push_str(&format!("{:02x}", b));
    }
    s
}

fn assert_fp2(label: &str, got: &[u8; 64], expected: &[u8; 64]) {
    assert_eq!(
        &got[..],
        &expected[..],
        "{label}:\n  got      {}\n  expected {}",
        hex_str(got),
        hex_str(expected),
    );
}

fn assert_point(label: &str, p: &EcPoint<Level1>, exp_x: &[u8; 64], exp_z: &[u8; 64]) {
    let gx = fp2_encode(&p.x);
    let gz = fp2_encode(&p.z);
    assert_fp2(&format!("{label}.x"), &gx, exp_x);
    assert_fp2(&format!("{label}.z"), &gz, exp_z);
}

#[test]
fn scalar_constants() {
    assert_eq!(TORSION_EVEN_POWER, 248);
    assert_eq!(P_COFACTOR_FOR_2F_BITLENGTH, 3);
    assert_eq!(P_COFACTOR_FOR_2F, &[5u64]);
}

#[test]
fn basis_encode_decode_roundtrip() {
    let px = Fp2::<Level1>::decode(&BASIS_E0_PX_BYTES).expect("PX decode failed");
    let qx = Fp2::<Level1>::decode(&BASIS_E0_QX_BYTES).expect("QX decode failed");

    let px_rt = fp2_encode(&px);
    let qx_rt = fp2_encode(&qx);

    assert_eq!(px_rt, BASIS_E0_PX_BYTES, "PX round-trip mismatch");
    assert_eq!(qx_rt, BASIS_E0_QX_BYTES, "QX round-trip mismatch");
}

#[test]
fn basis_bytes_match_c_reference() {
    assert_fp2(
        "BASIS_E0_PX",
        &BASIS_E0_PX_BYTES,
        &hex!("7800b4ae5ed919218ba7bf591a99be44c41662a6c304cc8324b182ca7f879b0175d2f9c33d13048e74924251aeddcfb22fe96798aa0a155242e0ea49db2a4404"),
    );
    assert_fp2(
        "BASIS_E0_QX",
        &BASIS_E0_QX_BYTES,
        &hex!("1feb93552a25167cf3e14ba5f77886871d040d051727df9f710b5c7d47fd5f04eeaacda0b728e2fdaea58e6f4b05ff399ab37636fba86544dc7318dfe9d48704"),
    );
}

#[test]
fn e0_j_invariant() {
    let e0 = EcCurve::<Level1>::default();
    let j = e0.j_inv();
    let j_bytes = fp2_encode(&j);
    assert_fp2(
        "j(E0)",
        &j_bytes,
        &hex!("c0060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
    );
}

#[test]
fn basis_points_on_e0() {
    let px = Fp2::<Level1>::decode(&BASIS_E0_PX_BYTES).unwrap();
    let qx = Fp2::<Level1>::decode(&BASIS_E0_QX_BYTES).unwrap();

    // E0: y^2 = x^3 + x (A=0, C=1)
    // Check x^3 + x is a square in Fp2
    let px_ysq = px.sqr().mul(&px).add(&px);
    assert!(bool::from(px_ysq.is_square()), "PX not on E0");

    let qx_ysq = qx.sqr().mul(&qx).add(&qx);
    assert!(bool::from(qx_ysq.is_square()), "QX not on E0");
}

#[test]
fn cofactor_clears_to_torsion() {
    let mut e0 = EcCurve::<Level1>::default();
    e0.normalize_a24();

    let px = Fp2::<Level1>::decode(&BASIS_E0_PX_BYTES).unwrap();
    let p = EcPoint::new(px, Fp2::one());

    // Multiply by cofactor
    let cof_p = ec_mul(
        &p,
        P_COFACTOR_FOR_2F,
        P_COFACTOR_FOR_2F_BITLENGTH as usize,
        &mut e0,
    );

    // Cross-validate cofactor multiplication result
    assert_point(
        "cof*PX",
        &cof_p,
        &hex!("03686335cc6ce31aef8d8dea3f3a283c7cfd5b547bceacb2cc96dd0cd3ec83048b99871bf38aa2be66523949df4afabb0d46d055dff64ae4139833c884dab503"),
        &hex!("b2e2c6efd7b292180a3ef2f2682a174a880f0ad743de32ea0b58316de5e1d103466e040e91cd0e01ee63a0b143df7fa9c81a015138b1e142726f5a23c8a84904"),
    );

    // Double TORSION_EVEN_POWER times -> identity
    let mut test = cof_p;
    for _ in 0..TORSION_EVEN_POWER {
        test = xdbl_a24(&test, &e0.a24, true);
    }
    assert!(
        bool::from(test.z.ct_is_zero()),
        "cof*P doubled {} times should be identity",
        TORSION_EVEN_POWER
    );
}
