//! Cross-validation test: assert the Rust basis generation layer produces
//! byte-identical output to the reference implementation.
//!
//! Expected hex bytes generated by `tools/c-validate/basis_validate.c`.

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

fn fp2_small<L: FpBackend>(re: u64, im: u64) -> Fp2<L> {
    Fp2 {
        re: sqisign_verify::fp::Fp::<L>::from_small(re),
        im: sqisign_verify::fp::Fp::<L>::from_small(im),
    }
}

fn fp2_encode_arr<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_arr(&p.x);
    let gz = fp2_encode_arr(&p.z);
    assert_fp2(&format!("{label}.x"), &gx, exp_x);
    assert_fp2(&format!("{label}.z"), &gz, exp_z);
}

// ---- Test 1: ec_basis_E0_2f (A=0, f=248) ----
#[test]
fn test_basis_e0_full_order() {
    let mut e0 = EcCurve::<Level1>::default();
    e0.normalize_a24();

    let (basis, hint) = ec_curve_to_basis_2f_to_hint(
        &mut e0,
        TORSION_EVEN_POWER,
        &BASIS_E0_PX_BYTES,
        &BASIS_E0_QX_BYTES,
        P_COFACTOR_FOR_2F,
        P_COFACTOR_FOR_2F_BITLENGTH as usize,
        TORSION_EVEN_POWER,
    )
    .unwrap();

    assert_eq!(hint, 0);
    assert_point(
        "P",
        &basis.p,
        &hex!("7800b4ae5ed919218ba7bf591a99be44c41662a6c304cc8324b182ca7f879b0175d2f9c33d13048e74924251aeddcfb22fe96798aa0a155242e0ea49db2a4404"),
        &hex!("01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
    );
    assert_point(
        "Q",
        &basis.q,
        &hex!("1feb93552a25167cf3e14ba5f77886871d040d051727df9f710b5c7d47fd5f04eeaacda0b728e2fdaea58e6f4b05ff399ab37636fba86544dc7318dfe9d48704"),
        &hex!("01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
    );
    assert_point(
        "PmQ",
        &basis.pmq,
        &hex!("6524ec13804c8cc41bc0f9d6e22497c6849ad0f757748ae546dc8065c0262e006f4466a16d641715cd8f291ad93c9e657d1b6a069e14b8d554e061f2df930f03"),
        &hex!("808e7dedc956375ce55289e7b27005e30c12174e425864e2347cf144a5d378033eba36cdd8ef78b96e023e60327ef9a69df8b72a3356fb53f822a2e06e319d01"),
    );
}

// ---- Test 2: ec_basis_E0_2f (A=0, f=128) ----
#[test]
fn test_basis_e0_partial_order() {
    let mut e0 = EcCurve::<Level1>::default();
    e0.normalize_a24();

    let (basis, hint) = ec_curve_to_basis_2f_to_hint(
        &mut e0,
        128,
        &BASIS_E0_PX_BYTES,
        &BASIS_E0_QX_BYTES,
        P_COFACTOR_FOR_2F,
        P_COFACTOR_FOR_2F_BITLENGTH as usize,
        TORSION_EVEN_POWER,
    )
    .unwrap();

    assert_eq!(hint, 0);
    assert_point(
        "P128",
        &basis.p,
        &hex!("71e42203219fc29b45e50231cd90518e05a74dd48c5fb436dc1bf3014650b60316bc1b3d24d11d2a0329c457c50a6ecaf4f1b5bd68626880f23baed734c8f803"),
        &hex!("066813168199ecdfd61db2c65f044a0a047210e979f21a85d7da851da2ac1a0316ca443c068ce1cb1a146d36d0c61928efb8ee9a3bc85b875b28cb408d5ced04"),
    );
    assert_point(
        "Q128",
        &basis.q,
        &hex!("1ab5d5386e7e457e19f5ee14da81b4f8a8e3120d14d50d1ee92cae4ee05f1101ee10ac0da8c8fb31c4ab2f77207df41215a11da46471e09079ced55572101704"),
        &hex!("4c03eb3f5a8e1627cc4448372d79d78d16c341a1a9b18842232e3f799970d4021cfc7da55d3e141f45343588d861e553dbd718bf16e9fa99423550ace596aa00"),
    );
    assert_point(
        "PmQ128",
        &basis.pmq,
        &hex!("15315a5889d8f0f7422eb1806d705919087c64c377ce27717ed058888b6bf303357a7452ff52f269e7b7278aff98929f278b56ca5367b18445429366ec2e9204"),
        &hex!("0b6833bf13b8fba5a2976be3db1e02a50e9a5cbf79f6ccd9c3fa3eea77aa300472d9689bf82b16fffa23f58c7097bf4ce24d268edb868055b2d0f75c52325f04"),
    );
}

// ---- Test 3: ec_recover_y ----
#[test]
fn test_ec_recover_y() {
    let e0 = EcCurve::<Level1>::default();

    // (3+i) on E0 (A=0)
    let px = fp2_small::<Level1>(3, 1);
    let (y, ret) = ec_recover_y(&px, &e0);
    // C returns 0 for (3+i), not on curve
    assert!(!bool::from(ret));
    let y_bytes = fp2_encode_arr(&y);
    assert_fp2(
        "y(3+i)",
        &y_bytes,
        &hex!("1a525a47e979b9087de8c6bfca64b97b2ec2f418078ade99d674c65ef504da007051b5eaf2283c002a87ff577479368285a1745138f544a5b3dfc3479734a803"),
    );

    // (7+2i) on E1 (A=6)
    let e1 = EcCurve::<Level1> {
        a: fp2_small(6, 0),
        c: Fp2::one(),
        ..EcCurve::default()
    };
    let px = fp2_small::<Level1>(7, 2);
    let (y, ret) = ec_recover_y(&px, &e1);
    assert!(!bool::from(ret));
    let y_bytes = fp2_encode_arr(&y);
    assert_fp2(
        "y_E1(7+2i)",
        &y_bytes,
        &hex!("344eccf7c763046e29837198360e47bbe48f9ac806db85fda261573da3075401d390383bb497b742874e6a0057520233ef41535a96bb4890a04554d57f7a4800"),
    );
}

// ---- Test 4: basis_to_hint (A=6, f=248) ----
#[test]
fn test_basis_to_hint_e1() {
    let mut e1 = EcCurve::<Level1> {
        a: fp2_small(6, 0),
        c: Fp2::one(),
        ..EcCurve::default()
    };
    e1.normalize_a24();

    let (basis, hint) = ec_curve_to_basis_2f_to_hint(
        &mut e1,
        TORSION_EVEN_POWER,
        &BASIS_E0_PX_BYTES,
        &BASIS_E0_QX_BYTES,
        P_COFACTOR_FOR_2F,
        P_COFACTOR_FOR_2F_BITLENGTH as usize,
        TORSION_EVEN_POWER,
    )
    .unwrap();

    assert_eq!(hint, 11);
    assert_point(
        "P_E1",
        &basis.p,
        &hex!("25dad4c0426d5f261a605624ef9b7639507aa71759069d02fc4a1082b1144601f655d5239d5383a4e92418b0a59c18968d1947cbd472d29ca2bcd634d3a2dc04"),
        &hex!("1a0ef7567fc6788fa4b8446b41a1d4570eba3881057e5fb68be3dc9be217af04f6a0d4a60219abcf89ebdf0b8dc27d877a3403401e69de174a5b53d24937b203"),
    );
    assert_point(
        "Q_E1",
        &basis.q,
        &hex!("91d96d61b2b83c48d61216ef66fc29cdd9fd95d889119a5c9a881a5a6e5bef00cc8c51f9f17216fa3f1694163bf0829636e5336b9da23d46b63d547107d4cb04"),
        &hex!("d62dc10f6b8ae8c1752e5e5bd566f13b0a515b6413bfa3b66e24e09ee39b300285eb55c00b4b1d7547caccae256bf730a70ef06d4d40fa2393eb0e57d8178301"),
    );
    assert_point(
        "PmQ_E1",
        &basis.pmq,
        &hex!("18a93a94403d33a9d587e1ea57f108939cbd7a4a490a3713fa999c6d3033f202250ea97d8cbfffd9c6f90c2f3430e5ad259f071a0c0ed7e8859fd2d441282b02"),
        &hex!("4133f7a6327deb53f2ddef166832a4e387d98345bdf62c57875426ce02cde403bee27fbdf1a01ad8339a8f2a1f0e57d0478d309fd9616fcf9dd60d7cb738b403"),
    );
}

// ---- Test 5: basis_from_hint (A=6, f=248, hint=11) ----
#[test]
fn test_basis_from_hint_e1() {
    let mut e1 = EcCurve::<Level1> {
        a: fp2_small(6, 0),
        c: Fp2::one(),
        ..EcCurve::default()
    };
    e1.normalize_a24();

    let (basis, ok) = ec_curve_to_basis_2f_from_hint(
        &mut e1,
        TORSION_EVEN_POWER,
        11,
        &BASIS_E0_PX_BYTES,
        &BASIS_E0_QX_BYTES,
        P_COFACTOR_FOR_2F,
        P_COFACTOR_FOR_2F_BITLENGTH as usize,
        TORSION_EVEN_POWER,
    )
    .unwrap();

    assert_eq!(ok, 1);
    assert_point(
        "P_hint",
        &basis.p,
        &hex!("25dad4c0426d5f261a605624ef9b7639507aa71759069d02fc4a1082b1144601f655d5239d5383a4e92418b0a59c18968d1947cbd472d29ca2bcd634d3a2dc04"),
        &hex!("1a0ef7567fc6788fa4b8446b41a1d4570eba3881057e5fb68be3dc9be217af04f6a0d4a60219abcf89ebdf0b8dc27d877a3403401e69de174a5b53d24937b203"),
    );
    assert_point(
        "Q_hint",
        &basis.q,
        &hex!("91d96d61b2b83c48d61216ef66fc29cdd9fd95d889119a5c9a881a5a6e5bef00cc8c51f9f17216fa3f1694163bf0829636e5336b9da23d46b63d547107d4cb04"),
        &hex!("d62dc10f6b8ae8c1752e5e5bd566f13b0a515b6413bfa3b66e24e09ee39b300285eb55c00b4b1d7547caccae256bf730a70ef06d4d40fa2393eb0e57d8178301"),
    );
    assert_point(
        "PmQ_hint",
        &basis.pmq,
        &hex!("18a93a94403d33a9d587e1ea57f108939cbd7a4a490a3713fa999c6d3033f202250ea97d8cbfffd9c6f90c2f3430e5ad259f071a0c0ed7e8859fd2d441282b02"),
        &hex!("4133f7a6327deb53f2ddef166832a4e387d98345bdf62c57875426ce02cde403bee27fbdf1a01ad8339a8f2a1f0e57d0478d309fd9616fcf9dd60d7cb738b403"),
    );
}

// ---- Test 6: is_on_curve (via ec_recover_y) ----
#[test]
fn test_is_on_curve() {
    let e0 = EcCurve::<Level1>::default();

    // (0+i) should be on E0: y^2 = i^3 + i = -i + i = 0
    let px = fp2_small::<Level1>(0, 1);
    let (_, ret) = ec_recover_y(&px, &e0);
    assert!(bool::from(ret), "(0+i) should be on E0");

    // (3+i) should NOT be on E0
    let px = fp2_small::<Level1>(3, 1);
    let (_, ret) = ec_recover_y(&px, &e0);
    assert!(!bool::from(ret), "(3+i) should not be on E0");
}

// ---- Test 7: lift_basis ----
#[test]
fn test_lift_basis() {
    let mut e1 = EcCurve::<Level1> {
        a: fp2_small(6, 0),
        c: Fp2::one(),
        ..EcCurve::default()
    };
    e1.normalize_a24();

    let (mut basis, _hint) = ec_curve_to_basis_2f_to_hint(
        &mut e1,
        TORSION_EVEN_POWER,
        &BASIS_E0_PX_BYTES,
        &BASIS_E0_QX_BYTES,
        P_COFACTOR_FOR_2F,
        P_COFACTOR_FOR_2F_BITLENGTH as usize,
        TORSION_EVEN_POWER,
    )
    .unwrap();

    let (jp, jq, ret) = lift_basis(&mut basis, &mut e1);
    assert!(bool::from(ret));

    let jpx = fp2_encode_arr(&jp.x);
    let jpy = fp2_encode_arr(&jp.y);
    let jpz = fp2_encode_arr(&jp.z);
    assert_fp2(
        "jP.x",
        &jpx,
        &hex!("d39cbed45bbcbfe58077ef553b674c0bd8a2c2704a55c8ff4111a9fc5e53960495bed3f3bc0d082f5c46ca895bc66f629d12a63891549e4e3251b091d1977302"),
    );
    assert_fp2(
        "jP.y",
        &jpy,
        &hex!("f2ebc8da4c1e13816b4af8aca0eb0884e0cda8e3045443e4fcc38c8625cf3b03702ae26b1de150013a2676084b91789adf28474ec80fbf6be048cae385321501"),
    );
    assert_fp2(
        "jP.z",
        &jpz,
        &hex!("01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
    );

    let jqx = fp2_encode_arr(&jq.x);
    let jqy = fp2_encode_arr(&jq.y);
    let jqz = fp2_encode_arr(&jq.z);
    assert_fp2(
        "jQ.x",
        &jqx,
        &hex!("c61c5c41d83b3f4dbb8be4e197367653759a78dcc35e7288615fcd4366ab0b0337553c4e9ee921beb40acd813a65cc533511a58c50f243a1b28cf08e2388d702"),
    );
    assert_fp2(
        "jQ.y",
        &jqy,
        &hex!("cab5a0b75f010c9b237fb7bdf6586a73c818d9310ccaef9540b3e4ace034e7014b00fd00d98fbe9b01dd6d42a143fb9a066c967e65d8b3abd861688886568202"),
    );
    assert_fp2(
        "jQ.z",
        &jqz,
        &hex!("9820de2968353c3379f1ec949520cda2dba4eb4b1191cd9d59d7b8d9fa6d0504db681f68457b355f7db2c6ab4a50c16d8cfde3507cae8f292c31352fa9c0e804"),
    );
}

// ---- Ported C tests: basis generation quality checks ----
fn inner_test_generated_basis(basis: &EcBasis<Level1>, curve: &EcCurve<Level1>, n: u32) -> bool {
    let mut p = basis.p.clone();
    let mut q = basis.q.clone();

    for _ in 0..(n - 1) {
        p = xdbl_a24(&p, &curve.a24, curve.is_a24_computed_and_normalized);
        q = xdbl_a24(&q, &curve.a24, curve.is_a24_computed_and_normalized);
    }

    // P and Q should have full order (not zero yet)
    assert!(
        !bool::from(p.is_zero()),
        "P should not be zero after n-1 doublings"
    );
    assert!(
        !bool::from(q.is_zero()),
        "Q should not be zero after n-1 doublings"
    );

    // P and Q should be different
    assert!(
        !bool::from(p.ct_equal(&q)),
        "P and Q should be linearly independent"
    );

    // Q should be above the Montgomery point: [2^(n-1)]Q should have x=0
    assert!(bool::from(q.x.ct_is_zero()), "Q should be above (0:0)");

    // One more doubling should give identity
    p = xdbl_a24(&p, &curve.a24, curve.is_a24_computed_and_normalized);
    q = xdbl_a24(&q, &curve.a24, curve.is_a24_computed_and_normalized);
    assert!(
        bool::from(p.is_zero()),
        "P should be identity after n doublings"
    );
    assert!(
        bool::from(q.is_zero()),
        "Q should be identity after n doublings"
    );

    true
}

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

    let (basis, _) = ec_curve_to_basis_2f_to_hint(
        &mut curve,
        TORSION_EVEN_POWER,
        &BASIS_E0_PX_BYTES,
        &BASIS_E0_QX_BYTES,
        P_COFACTOR_FOR_2F,
        P_COFACTOR_FOR_2F_BITLENGTH as usize,
        TORSION_EVEN_POWER,
    )
    .unwrap();

    assert!(inner_test_generated_basis(
        &basis,
        &curve,
        TORSION_EVEN_POWER
    ));
}

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

    let (basis, _) = ec_curve_to_basis_2f_to_hint(
        &mut curve,
        128,
        &BASIS_E0_PX_BYTES,
        &BASIS_E0_QX_BYTES,
        P_COFACTOR_FOR_2F,
        P_COFACTOR_FOR_2F_BITLENGTH as usize,
        TORSION_EVEN_POWER,
    )
    .unwrap();

    assert!(inner_test_generated_basis(&basis, &curve, 128));
}

#[test]
fn test_basis_generation_e1_full() {
    let mut curve = EcCurve::<Level1> {
        a: fp2_small(6, 0),
        c: Fp2::one(),
        ..EcCurve::default()
    };
    curve.normalize_a24();

    let (basis, _) = ec_curve_to_basis_2f_to_hint(
        &mut curve,
        TORSION_EVEN_POWER,
        &BASIS_E0_PX_BYTES,
        &BASIS_E0_QX_BYTES,
        P_COFACTOR_FOR_2F,
        P_COFACTOR_FOR_2F_BITLENGTH as usize,
        TORSION_EVEN_POWER,
    )
    .unwrap();

    assert!(inner_test_generated_basis(
        &basis,
        &curve,
        TORSION_EVEN_POWER
    ));
}

#[test]
fn test_basis_generation_e1_with_hints() {
    let mut curve = EcCurve::<Level1> {
        a: fp2_small(6, 0),
        c: Fp2::one(),
        ..EcCurve::default()
    };
    curve.normalize_a24();

    let (basis, hint) = ec_curve_to_basis_2f_to_hint(
        &mut curve,
        TORSION_EVEN_POWER,
        &BASIS_E0_PX_BYTES,
        &BASIS_E0_QX_BYTES,
        P_COFACTOR_FOR_2F,
        P_COFACTOR_FOR_2F_BITLENGTH as usize,
        TORSION_EVEN_POWER,
    )
    .unwrap();

    assert!(inner_test_generated_basis(
        &basis,
        &curve,
        TORSION_EVEN_POWER
    ));

    // Generate from hint, should produce identical basis
    let mut curve2 = EcCurve::<Level1> {
        a: fp2_small(6, 0),
        c: Fp2::one(),
        ..EcCurve::default()
    };
    curve2.normalize_a24();

    let (basis_hint, ok) = ec_curve_to_basis_2f_from_hint(
        &mut curve2,
        TORSION_EVEN_POWER,
        hint,
        &BASIS_E0_PX_BYTES,
        &BASIS_E0_QX_BYTES,
        P_COFACTOR_FOR_2F,
        P_COFACTOR_FOR_2F_BITLENGTH as usize,
        TORSION_EVEN_POWER,
    )
    .unwrap();
    assert_eq!(ok, 1);

    assert!(
        bool::from(basis.p.ct_equal(&basis_hint.p)),
        "P should match"
    );
    assert!(
        bool::from(basis.q.ct_equal(&basis_hint.q)),
        "Q should match"
    );
    assert!(
        bool::from(basis.pmq.ct_equal(&basis_hint.pmq)),
        "PmQ should match"
    );
}
