//! Solidity verifier generator.

use std::marker::PhantomData;

use crate::traits::PairingEngine;

/// Configuration for Solidity verifier generation.
#[derive(Debug, Clone)]
pub struct SolidityConfig {
    /// Contract name.
    pub contract_name: String,

    /// Solidity pragma version.
    pub solidity_version: String,

    /// Use inline assembly for gas optimization.
    pub use_assembly: bool,

    /// Add NatSpec documentation.
    pub add_natspec: bool,
}

impl Default for SolidityConfig {
    fn default() -> Self {
        Self {
            contract_name: "Verifier".to_string(),
            solidity_version: "^0.8.20".to_string(),
            use_assembly: true,
            add_natspec: true,
        }
    }
}

impl SolidityConfig {
    /// Create a new config with custom contract name.
    pub fn with_name(name: impl Into<String>) -> Self {
        Self {
            contract_name: name.into(),
            ..Default::default()
        }
    }
}

/// Verification key representation for Solidity embedding.
#[derive(Debug, Clone)]
pub struct SolidityVk {
    /// VK commitment points as (x, y) pairs.
    pub commitments: Vec<(String, String)>,

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

    /// Expected proof length in bytes.
    pub proof_length: usize,
}

/// Generator for Solidity verifier contracts.
pub struct SolidityGenerator<E: PairingEngine> {
    config: SolidityConfig,
    _engine: PhantomData<E>,
}

impl<E: PairingEngine> SolidityGenerator<E> {
    /// Create a new generator with default config.
    pub fn new() -> Self {
        Self::with_config(SolidityConfig::default())
    }

    /// Create a new generator with custom config.
    pub fn with_config(config: SolidityConfig) -> Self {
        Self {
            config,
            _engine: PhantomData,
        }
    }

    /// Generate a Solidity verifier contract.
    ///
    /// # Arguments
    ///
    /// * `vk` - Verification key to embed
    ///
    /// # Returns
    ///
    /// The generated Solidity source code.
    pub fn generate(&self, vk: &SolidityVk) -> String {
        let template = super::template::VERIFIER_TEMPLATE;

        // Replace placeholders
        let mut output = template.to_string();
        output = output.replace("{{CONTRACT_NAME}}", &self.config.contract_name);
        output = output.replace("{{SOLIDITY_VERSION}}", &self.config.solidity_version);
        output = output.replace("{{NUM_PUBLIC_INPUTS}}", &vk.num_public_inputs.to_string());
        output = output.replace("{{PROOF_LENGTH}}", &vk.proof_length.to_string());

        // Generate VK points
        let vk_points = self.generate_vk_points(vk);
        output = output.replace("{{VK_POINTS}}", &vk_points);

        // Generate proof decoding (extract G1/G2 points from calldata)
        let proof_decode = r#"
        // Decode proof elements from calldata
        uint256[2] memory a;
        uint256[2][2] memory b;
        uint256[2] memory c;
        
        a[0] = uint256(bytes32(proof[0:32]));
        a[1] = uint256(bytes32(proof[32:64]));
        b[0][0] = uint256(bytes32(proof[64:96]));
        b[0][1] = uint256(bytes32(proof[96:128]));
        b[1][0] = uint256(bytes32(proof[128:160]));
        b[1][1] = uint256(bytes32(proof[160:192]));
        c[0] = uint256(bytes32(proof[192:224]));
        c[1] = uint256(bytes32(proof[224:256]));"#;
        output = output.replace("{{PROOF_DECODE}}", proof_decode);

        // Generate public input computation (Lagrange interpolation)
        let pi_compute = r#"
        // Compute public input contribution
        uint256 pi = 0;
        for (uint256 i = 0; i < publicInputs.length; i++) {
            pi = addmod(pi, mulmod(publicInputs[i], lagrangeBasis[i], FIELD_MODULUS), FIELD_MODULUS);
        }"#;
        output = output.replace("{{PUBLIC_INPUT_COMPUTATION}}", pi_compute);

        // Generate pairing check (BN254 pairing equation)
        let pairing_check = r#"
        // Pairing check: e(A, B) == e(C, D) * e(α, β) * e(PI, γ) * e(vk, δ)
        uint256[12] memory input;
        
        // -A
        input[0] = a[0];
        input[1] = (FIELD_MODULUS - a[1]) % FIELD_MODULUS;
        // B
        input[2] = b[0][0];
        input[3] = b[0][1];
        input[4] = b[1][0];
        input[5] = b[1][1];
        // C
        input[6] = c[0];
        input[7] = c[1];
        // Delta (from VK)
        input[8] = VK_DELTA_X1;
        input[9] = VK_DELTA_X2;
        input[10] = VK_DELTA_Y1;
        input[11] = VK_DELTA_Y2;
        
        uint256[1] memory out;
        bool success;
        assembly {
            success := staticcall(gas(), 0x08, input, 384, out, 32)
        }
        require(success, "pairing failed");
        return out[0] == 1;"#;
        output = output.replace("{{PAIRING_CHECK}}", pairing_check);

        output
    }

    /// Generate a complete, deployable verifier with full pairing logic.
    ///
    /// This uses the complete verifier template with all pairing operations.
    pub fn generate_complete(&self, vk: &super::serialization::SerializedVk) -> String {
        let template = super::complete_verifier::COMPLETE_VERIFIER_TEMPLATE;

        // Generate VK constants
        let _vk_constants = super::serialization::generate_vk_constants(vk);

        let mut output = template.to_string();
        output = output.replace("{{CONTRACT_NAME}}", &self.config.contract_name);
        output = output.replace("{{SOLIDITY_VERSION}}", &self.config.solidity_version);
        output = output.replace("{{VK_CONSTANTS}}", ""); // Empty - constants are at bottom
        output = output.replace("{{PROOF_SIZE}}", "576"); // Standard proof size

        // Replace individual VK placeholders
        output = output.replace("{{VK_ALPHA_X}}", &vk.alpha.0);
        output = output.replace("{{VK_ALPHA_Y}}", &vk.alpha.1);
        output = output.replace("{{VK_BETA_X1}}", &vk.beta.0);
        output = output.replace("{{VK_BETA_X2}}", &vk.beta.1);
        output = output.replace("{{VK_BETA_Y1}}", &vk.beta.2);
        output = output.replace("{{VK_BETA_Y2}}", &vk.beta.3);
        output = output.replace("{{VK_GAMMA_X1}}", &vk.gamma.0);
        output = output.replace("{{VK_GAMMA_X2}}", &vk.gamma.1);
        output = output.replace("{{VK_GAMMA_Y1}}", &vk.gamma.2);
        output = output.replace("{{VK_GAMMA_Y2}}", &vk.gamma.3);
        output = output.replace("{{VK_DELTA_X1}}", &vk.delta.0);
        output = output.replace("{{VK_DELTA_X2}}", &vk.delta.1);
        output = output.replace("{{VK_DELTA_Y1}}", &vk.delta.2);
        output = output.replace("{{VK_DELTA_Y2}}", &vk.delta.3);
        
        // Replace domain parameters
        output = output.replace("{{VK_DOMAIN_SIZE}}", &vk.domain_size.to_string());
        output = output.replace("{{VK_OMEGA}}", &vk.omega);

        output
    }

    /// Generate an optimized aggregated verifier (Z-01 protected).
    ///
    /// This generates a contract that uses the 2-pairing check logic.
    pub fn generate_aggregated(&self, vk: &super::serialization::SerializedVk) -> String {
        let template = super::template::AGGREGATED_VERIFIER_TEMPLATE;

        // Generate VK constants (includes Tau G2)
        let vk_constants = super::serialization::generate_vk_constants(vk);

        let mut output = template.to_string();
        output = output.replace("{{CONTRACT_NAME}}", &self.config.contract_name);
        output = output.replace("{{SOLIDITY_VERSION}}", &self.config.solidity_version);
        output = output.replace("{{VK_CONSTANTS}}", &vk_constants);

        // Replace Tau G2 constant placeholders if needed (usually handled by VK_CONSTANTS injection)
        // But the template references VK_TAU_G2_* which are generated by generate_vk_constants
        
        output
    }

    /// Generate a minimal stub verifier for testing.
    pub fn generate_stub(&self) -> String {
        super::template::MINIMAL_TEMPLATE.replace("{{CONTRACT_NAME}}", &self.config.contract_name)
    }

    /// Generate VK point declarations.
    fn generate_vk_points(&self, vk: &SolidityVk) -> String {
        let mut lines = Vec::new();

        for (i, (x, y)) in vk.commitments.iter().enumerate() {
            lines.push(format!("    uint256 internal constant VK_{}_X = {};", i, x));
            lines.push(format!("    uint256 internal constant VK_{}_Y = {};", i, y));
        }

        lines.join("\n")
    }
}

impl<E: PairingEngine> Default for SolidityGenerator<E> {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::backend::bn254::Bn254;

    #[test]
    fn generator_creates_with_default_config() {
        let gen = SolidityGenerator::<Bn254>::new();
        assert_eq!(gen.config.contract_name, "Verifier");
    }

    #[test]
    fn generator_creates_with_custom_name() {
        let config = SolidityConfig::with_name("AggregatedVerifier");
        let gen = SolidityGenerator::<Bn254>::with_config(config);
        assert_eq!(gen.config.contract_name, "AggregatedVerifier");
    }

    #[test]
    fn generator_produces_valid_solidity() {
        let gen = SolidityGenerator::<Bn254>::new();
        let vk = SolidityVk {
            commitments: vec![("0x1234".to_string(), "0x5678".to_string())],
            num_public_inputs: 2,
            proof_length: 256,
        };

        let output = gen.generate(&vk);

        assert!(output.contains("pragma solidity"));
        assert!(output.contains("contract Verifier"));
        assert!(output.contains("VK_0_X"));
        assert!(output.contains("publicInputs.length != 2"));
        assert!(output.contains("proof.length != 256"));
    }

    #[test]
    fn generator_stub_is_valid_solidity() {
        let gen =
            SolidityGenerator::<Bn254>::with_config(SolidityConfig::with_name("TestVerifier"));

        let stub = gen.generate_stub();

        assert!(stub.contains("pragma solidity ^0.8.20"));
        assert!(stub.contains("contract TestVerifier"));
        assert!(stub.contains("function verify"));
    }

    #[test]
    fn generator_complete_embeds_vk() {
        use super::super::serialization::SerializedVk;

        let gen =
            SolidityGenerator::<Bn254>::with_config(SolidityConfig::with_name("CompleteVerifier"));

        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![],
            num_public_inputs: 2,
            domain_size: 1024,
            omega: "0xabc".to_string(),
        };

        let output = gen.generate_complete(&vk);

        assert!(output.contains("contract CompleteVerifier"));
        assert!(output.contains("_pairing"));
        assert!(output.contains("_scalarMul"));
        assert!(output.contains("VK_ALPHA_X = 0x1"));
    }
}
