//! Verification key and proof serialization for Solidity.
//!
//! Handles encoding/decoding of VK and proof data for EVM compatibility.

use crate::traits::PairingEngine;
use ff::PrimeField;

/// Serialized verification key for Solidity embedding.
#[derive(Debug, Clone)]
pub struct SerializedVk {
    /// Alpha G1 point (x, y).
    pub alpha: (String, String),

    /// Beta G2 point (x1, x2, y1, y2 for Fp2).
    pub beta: (String, String, String, String),

    /// Gamma G2 point.
    pub gamma: (String, String, String, String),

    /// Delta G2 point.
    pub delta: (String, String, String, String),

    /// Tau G2 point ([x]₂) for optimized KZG check.
    pub tau_g2: (String, String, String, String),

    /// IC (input commitments) as G1 points.
    pub ic: Vec<(String, String)>,

    /// Number of public inputs.
    pub num_public_inputs: usize,

    /// Domain size (n).
    pub domain_size: usize,

    /// Domain generator (omega).
    pub omega: String,
}

/// Serialize a G1 affine point to hex strings (x, y).
pub fn serialize_g1<E: PairingEngine>(point: &E::G1Affine) -> (String, String) {
    use group::GroupEncoding;

    // Get compressed representation
    let bytes = point.to_bytes();
    let bytes_ref: &[u8] = bytes.as_ref();

    // For BN254 G1, compressed representation is 32 bytes (just x with sign bit)
    // For Solidity, we need uncompressed x and y coordinates
    // The GroupEncoding gives us the compressed form
    if bytes_ref.len() >= 32 {
        // Extract x coordinate from compressed representation
        let x_bytes = &bytes_ref[..32.min(bytes_ref.len())];
        let mut x_padded = [0u8; 32];
        x_padded[..x_bytes.len()].copy_from_slice(x_bytes);

        // Y coordinate sign is in the high bit; we derive y from x for display
        // For full correctness, need curve equation y² = x³ + b
        let y_padded = [0u8; 32]; // Placeholder y - real impl needs curve recovery

        (
            format!("0x{}", hex::encode(x_padded)),
            format!("0x{}", hex::encode(y_padded)),
        )
    } else {
        // Fallback for unexpected format
        (
            format!("0x{}", hex::encode(bytes_ref)),
            format!("0x{}", hex::encode([0u8; 32])),
        )
    }
}

/// Serialize field element to hex string.
pub fn serialize_scalar<F: PrimeField>(scalar: &F) -> String {
    let repr = scalar.to_repr();
    format!("0x{}", hex::encode(repr.as_ref()))
}

/// Proof encoding for Solidity calldata.
#[derive(Debug, Clone)]
pub struct EncodedProof {
    /// Raw bytes for calldata.
    pub calldata: Vec<u8>,

    /// Hex string representation.
    pub hex: String,
}

impl EncodedProof {
    /// Create from raw proof bytes.
    pub fn from_bytes(bytes: Vec<u8>) -> Self {
        let hex = format!("0x{}", hex::encode(&bytes));
        Self {
            calldata: bytes,
            hex,
        }
    }

    /// Get the calldata length.
    pub fn len(&self) -> usize {
        self.calldata.len()
    }

    /// Check if empty.
    pub fn is_empty(&self) -> bool {
        self.calldata.is_empty()
    }
}

/// Encode public inputs for Solidity.
pub fn encode_public_inputs<F: PrimeField>(inputs: &[F]) -> Vec<[u8; 32]> {
    inputs
        .iter()
        .map(|f| {
            let repr = f.to_repr();
            let mut bytes = [0u8; 32];
            bytes.copy_from_slice(repr.as_ref());
            bytes
        })
        .collect()
}

/// Decode public inputs from bytes.
pub fn decode_public_inputs<F: PrimeField>(inputs: &[[u8; 32]]) -> Result<Vec<F>, String> {
    inputs
        .iter()
        .map(|bytes| {
            let mut repr = F::Repr::default();
            repr.as_mut().copy_from_slice(bytes);
            F::from_repr(repr)
                .into_option()
                .ok_or_else(|| "Invalid field element".to_string())
        })
        .collect()
}

/// Generate VK constants for Solidity embedding.
pub fn generate_vk_constants(vk: &SerializedVk) -> String {
    let mut lines = Vec::new();

    // Domain parameters
    lines.push(format!(
        "    uint256 constant VK_DOMAIN_SIZE = {};",
        vk.domain_size
    ));
    lines.push(format!("    uint256 constant VK_OMEGA = {};", vk.omega));

    // Alpha (G1)
    lines.push(format!("    uint256 constant VK_ALPHA_X = {};", vk.alpha.0));
    lines.push(format!("    uint256 constant VK_ALPHA_Y = {};", vk.alpha.1));

    // Beta (G2)
    lines.push(format!("    uint256 constant VK_BETA_X1 = {};", vk.beta.0));
    lines.push(format!("    uint256 constant VK_BETA_X2 = {};", vk.beta.1));
    lines.push(format!("    uint256 constant VK_BETA_Y1 = {};", vk.beta.2));
    lines.push(format!("    uint256 constant VK_BETA_Y2 = {};", vk.beta.3));

    // Gamma (G2)
    lines.push(format!(
        "    uint256 constant VK_GAMMA_X1 = {};",
        vk.gamma.0
    ));
    lines.push(format!(
        "    uint256 constant VK_GAMMA_X2 = {};",
        vk.gamma.1
    ));
    lines.push(format!(
        "    uint256 constant VK_GAMMA_Y1 = {};",
        vk.gamma.2
    ));
    lines.push(format!(
        "    uint256 constant VK_GAMMA_Y2 = {};",
        vk.gamma.3
    ));

    // Delta (G2)
    lines.push(format!(
        "    uint256 constant VK_DELTA_X1 = {};",
        vk.delta.0
    ));
    lines.push(format!(
        "    uint256 constant VK_DELTA_X2 = {};",
        vk.delta.1
    ));
    lines.push(format!(
        "    uint256 constant VK_DELTA_Y1 = {};",
        vk.delta.2
    ));
    lines.push(format!(
        "    uint256 constant VK_DELTA_Y2 = {};",
        vk.delta.3
    ));

    // Tau G2 (for optimized KZG)
    lines.push(format!(
        "    uint256 constant VK_TAU_G2_X1 = {};",
        vk.tau_g2.0
    ));
    lines.push(format!(
        "    uint256 constant VK_TAU_G2_X2 = {};",
        vk.tau_g2.1
    ));
    lines.push(format!(
        "    uint256 constant VK_TAU_G2_Y1 = {};",
        vk.tau_g2.2
    ));
    lines.push(format!(
        "    uint256 constant VK_TAU_G2_Y2 = {};",
        vk.tau_g2.3
    ));

    // IC points
    for (i, (x, y)) in vk.ic.iter().enumerate() {
        lines.push(format!("    uint256 constant VK_IC_{}_X = {};", i, x));
        lines.push(format!("    uint256 constant VK_IC_{}_Y = {};", i, y));
    }

    lines.join("\n")
}

#[cfg(test)]
mod tests {
    use super::*;
    use halo2curves::bn256::Fr;

    #[test]
    fn serialize_scalar_produces_hex() {
        let scalar = Fr::from(42u64);
        let hex = serialize_scalar(&scalar);

        assert!(hex.starts_with("0x"));
        assert_eq!(hex.len(), 66); // 0x + 64 hex chars
    }

    #[test]
    fn encode_decode_public_inputs_roundtrip() {
        let inputs = vec![Fr::from(100u64), Fr::from(200u64)];
        let encoded = encode_public_inputs(&inputs);
        let decoded: Vec<Fr> = decode_public_inputs(&encoded).unwrap();

        assert_eq!(inputs, decoded);
    }

    #[test]
    fn encoded_proof_from_bytes() {
        let bytes = vec![1, 2, 3, 4, 5];
        let proof = EncodedProof::from_bytes(bytes.clone());

        assert_eq!(proof.len(), 5);
        assert_eq!(proof.hex, "0x0102030405");
    }

    #[test]
    fn generate_vk_constants_format() {
        let vk = SerializedVk {
            alpha: ("0x1".to_string(), "0x2".to_string()),
            beta: (
                "0x3".to_string(),
                "0x4".to_string(),
                "0x5".to_string(),
                "0x6".to_string(),
            ),
            gamma: (
                "0x7".to_string(),
                "0x8".to_string(),
                "0x9".to_string(),
                "0xa".to_string(),
            ),
            delta: (
                "0xb".to_string(),
                "0xc".to_string(),
                "0xd".to_string(),
                "0xe".to_string(),
            ),
            tau_g2: (
                "0xf".to_string(),
                "0x10".to_string(),
                "0x11".to_string(),
                "0x12".to_string(),
            ),
            ic: vec![("0xf".to_string(), "0x10".to_string())],
            num_public_inputs: 1,
            domain_size: 1024,
            omega: "0xabc".to_string(),
        };

        let constants = generate_vk_constants(&vk);

        assert!(constants.contains("VK_DOMAIN_SIZE = 1024"));
        assert!(constants.contains("VK_OMEGA = 0xabc"));
        assert!(constants.contains("VK_ALPHA_X"));
        assert!(constants.contains("VK_BETA_X1"));
        assert!(constants.contains("VK_IC_0_X"));
    }
}
