//! Pedersen Commitments
//!
//! A Pedersen commitment is a cryptographic primitive that allows committing to
//! a value while keeping it hidden. The commitment is:
//!
//! ```text
//! C = g^v * h^r
//! ```
//!
//! Where:
//! - `g` and `h` are generator points on an elliptic curve
//! - `v` is the value being committed
//! - `r` is a random blinding factor
//!
//! ## Properties
//!
//! 1. **Hiding**: Given C, it's computationally infeasible to determine v
//! 2. **Binding**: It's infeasible to find different (v', r') that produce the same C
//! 3. **Homomorphic**: C(v1, r1) + C(v2, r2) = C(v1 + v2, r1 + r2)
//!
//! The homomorphic property is crucial for verifying that transaction inputs
//! equal outputs without revealing the amounts.

use core::ops::{Add, Sub, Neg};
use curve25519_dalek::{
    constants::RISTRETTO_BASEPOINT_POINT,
    ristretto::{CompressedRistretto, RistrettoPoint},
    scalar::Scalar,
};
use rand_core::{CryptoRng, RngCore};
use serde::{Deserialize, Serialize};
use borsh::{BorshSerialize, BorshDeserialize};
use sha2::{Sha512, Digest};
use zeroize::Zeroize;

use crate::{Error, Result, DOMAIN_SEPARATOR};

/// Generate a random scalar using the provided RNG
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Scalar {
    let mut bytes = [0u8; 64];
    rng.fill_bytes(&mut bytes);
    Scalar::from_bytes_mod_order_wide(&bytes)
}

/// Generator point G (base point)
pub fn generator_g() -> RistrettoPoint {
    RISTRETTO_BASEPOINT_POINT
}

/// Generator point H (derived from G via hash-to-curve)
/// H is chosen such that the discrete log relationship between G and H is unknown
pub fn generator_h() -> RistrettoPoint {
    let mut hasher = Sha512::new();
    hasher.update(DOMAIN_SEPARATOR);
    hasher.update(b"GENERATOR_H");
    hasher.update(RISTRETTO_BASEPOINT_POINT.compress().as_bytes());

    RistrettoPoint::from_hash(hasher)
}

/// A blinding factor (random scalar used in commitments)
#[derive(Clone, Zeroize)]
#[zeroize(drop)]
pub struct BlindingFactor {
    scalar: Scalar,
}

impl BlindingFactor {
    /// Generate a random blinding factor
    pub fn random<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
        Self {
            scalar: random_scalar(rng),
        }
    }

    /// Create from raw bytes (32 bytes)
    pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self> {
        let scalar = Scalar::from_canonical_bytes(*bytes)
            .into_option()
            .ok_or_else(|| Error::InvalidBlindingFactor("Invalid scalar bytes".into()))?;
        Ok(Self { scalar })
    }

    /// Convert to bytes
    pub fn to_bytes(&self) -> [u8; 32] {
        self.scalar.to_bytes()
    }

    /// Get the inner scalar
    pub fn as_scalar(&self) -> &Scalar {
        &self.scalar
    }

    /// Create a zero blinding factor (for testing only!)
    #[cfg(test)]
    pub fn zero() -> Self {
        Self {
            scalar: Scalar::ZERO,
        }
    }

    /// Create from a scalar directly
    pub fn from_scalar(scalar: Scalar) -> Self {
        Self { scalar }
    }
}

impl Add for BlindingFactor {
    type Output = Self;

    fn add(self, other: Self) -> Self {
        Self {
            scalar: self.scalar + other.scalar,
        }
    }
}

impl Add for &BlindingFactor {
    type Output = BlindingFactor;

    fn add(self, other: &BlindingFactor) -> BlindingFactor {
        BlindingFactor {
            scalar: self.scalar + other.scalar,
        }
    }
}

impl Sub for BlindingFactor {
    type Output = Self;

    fn sub(self, other: Self) -> Self {
        Self {
            scalar: self.scalar - other.scalar,
        }
    }
}

impl Sub for &BlindingFactor {
    type Output = BlindingFactor;

    fn sub(self, other: &BlindingFactor) -> BlindingFactor {
        BlindingFactor {
            scalar: self.scalar - other.scalar,
        }
    }
}

impl Neg for BlindingFactor {
    type Output = Self;

    fn neg(self) -> Self {
        Self {
            scalar: -self.scalar,
        }
    }
}

/// Serializable wrapper for blinding factor
#[derive(Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct BlindingFactorBytes {
    bytes: [u8; 32],
}

impl From<&BlindingFactor> for BlindingFactorBytes {
    fn from(bf: &BlindingFactor) -> Self {
        Self {
            bytes: bf.to_bytes(),
        }
    }
}

impl TryFrom<BlindingFactorBytes> for BlindingFactor {
    type Error = Error;

    fn try_from(bfb: BlindingFactorBytes) -> Result<Self> {
        BlindingFactor::from_bytes(&bfb.bytes)
    }
}

/// A Pedersen commitment to a value
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct PedersenCommitment {
    point: RistrettoPoint,
}

impl PedersenCommitment {
    /// Create a commitment to a value with a random blinding factor
    pub fn commit_random<R: RngCore + CryptoRng>(value: u64, rng: &mut R) -> (Self, BlindingFactor) {
        let blinding = BlindingFactor::random(rng);
        let commitment = Self::commit(value, &blinding);
        (commitment, blinding)
    }

    /// Create a commitment to a value with a specific blinding factor
    pub fn commit(value: u64, blinding: &BlindingFactor) -> Self {
        let g = generator_g();
        let h = generator_h();

        let value_scalar = Scalar::from(value);
        let point = g * value_scalar + h * blinding.scalar;

        Self { point }
    }

    /// Create a commitment to zero (for balance proofs)
    pub fn commit_zero(blinding: &BlindingFactor) -> Self {
        Self::commit(0, blinding)
    }

    /// Verify that two sets of commitments balance
    /// sum(inputs) - sum(outputs) should equal a commitment to 0
    pub fn verify_balance(
        inputs: &[PedersenCommitment],
        outputs: &[PedersenCommitment],
        excess_blinding: &BlindingFactor,
    ) -> bool {
        let sum_inputs: RistrettoPoint = inputs.iter().map(|c| c.point).sum();
        let sum_outputs: RistrettoPoint = outputs.iter().map(|c| c.point).sum();

        // The difference should be h^excess_blinding (commitment to 0)
        let expected_excess = generator_h() * excess_blinding.scalar;

        sum_inputs - sum_outputs == expected_excess
    }

    /// Get the compressed representation (32 bytes)
    pub fn to_bytes(&self) -> [u8; 32] {
        self.point.compress().to_bytes()
    }

    /// Create from compressed bytes
    pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self> {
        let compressed = CompressedRistretto::from_slice(bytes)
            .map_err(|_| Error::InvalidCommitment("Invalid compressed point length".into()))?;
        let point = compressed
            .decompress()
            .ok_or_else(|| Error::InvalidCommitment("Point not on curve".into()))?;
        Ok(Self { point })
    }

    /// Get the inner point
    pub fn as_point(&self) -> &RistrettoPoint {
        &self.point
    }

    /// Create from a point directly
    pub fn from_point(point: RistrettoPoint) -> Self {
        Self { point }
    }
}

impl Add for PedersenCommitment {
    type Output = Self;

    fn add(self, other: Self) -> Self {
        Self {
            point: self.point + other.point,
        }
    }
}

impl Add for &PedersenCommitment {
    type Output = PedersenCommitment;

    fn add(self, other: &PedersenCommitment) -> PedersenCommitment {
        PedersenCommitment {
            point: self.point + other.point,
        }
    }
}

impl Sub for PedersenCommitment {
    type Output = Self;

    fn sub(self, other: Self) -> Self {
        Self {
            point: self.point - other.point,
        }
    }
}

impl Sub for &PedersenCommitment {
    type Output = PedersenCommitment;

    fn sub(self, other: &PedersenCommitment) -> PedersenCommitment {
        PedersenCommitment {
            point: self.point - other.point,
        }
    }
}

impl Neg for PedersenCommitment {
    type Output = Self;

    fn neg(self) -> Self {
        Self { point: -self.point }
    }
}

impl core::fmt::Debug for PedersenCommitment {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        let bytes = self.to_bytes();
        write!(f, "PedersenCommitment({:02x}{:02x}{:02x}{:02x}...)",
            bytes[0], bytes[1], bytes[2], bytes[3])
    }
}

/// Serializable wrapper for Pedersen commitment
#[derive(Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize, Debug)]
pub struct CommitmentBytes {
    bytes: [u8; 32],
}

impl From<&PedersenCommitment> for CommitmentBytes {
    fn from(c: &PedersenCommitment) -> Self {
        Self {
            bytes: c.to_bytes(),
        }
    }
}

impl TryFrom<CommitmentBytes> for PedersenCommitment {
    type Error = Error;

    fn try_from(cb: CommitmentBytes) -> Result<Self> {
        PedersenCommitment::from_bytes(&cb.bytes)
    }
}

/// Batch operations on commitments
pub struct CommitmentBatch;

impl CommitmentBatch {
    /// Sum multiple commitments
    pub fn sum(commitments: &[PedersenCommitment]) -> PedersenCommitment {
        let point: RistrettoPoint = commitments.iter().map(|c| c.point).sum();
        PedersenCommitment { point }
    }

    /// Sum multiple blinding factors
    pub fn sum_blindings(blindings: &[BlindingFactor]) -> BlindingFactor {
        let scalar: Scalar = blindings.iter().map(|b| b.scalar).sum();
        BlindingFactor { scalar }
    }

    /// Compute the excess blinding factor for a transaction
    /// excess = sum(input_blindings) - sum(output_blindings)
    pub fn compute_excess(
        input_blindings: &[BlindingFactor],
        output_blindings: &[BlindingFactor],
    ) -> BlindingFactor {
        let sum_inputs: Scalar = input_blindings.iter().map(|b| b.scalar).sum();
        let sum_outputs: Scalar = output_blindings.iter().map(|b| b.scalar).sum();
        BlindingFactor {
            scalar: sum_inputs - sum_outputs,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use rand::rngs::OsRng;

    #[test]
    fn test_commitment_creation() {
        let mut rng = OsRng;
        let (commitment, _blinding) = PedersenCommitment::commit_random(1000, &mut rng);

        // Commitment should be a valid point
        let bytes = commitment.to_bytes();
        let recovered = PedersenCommitment::from_bytes(&bytes).unwrap();
        assert_eq!(commitment, recovered);
    }

    #[test]
    fn test_commitment_deterministic() {
        let blinding = BlindingFactor::from_bytes(&[1u8; 32]).unwrap();
        let c1 = PedersenCommitment::commit(100, &blinding);
        let c2 = PedersenCommitment::commit(100, &blinding);
        assert_eq!(c1, c2);
    }

    #[test]
    fn test_commitment_different_values() {
        let blinding = BlindingFactor::from_bytes(&[1u8; 32]).unwrap();
        let c1 = PedersenCommitment::commit(100, &blinding);
        let c2 = PedersenCommitment::commit(200, &blinding);
        assert_ne!(c1, c2);
    }

    #[test]
    fn test_commitment_different_blindings() {
        let b1 = BlindingFactor::from_bytes(&[1u8; 32]).unwrap();
        let b2 = BlindingFactor::from_bytes(&[2u8; 32]).unwrap();
        let c1 = PedersenCommitment::commit(100, &b1);
        let c2 = PedersenCommitment::commit(100, &b2);
        assert_ne!(c1, c2);
    }

    #[test]
    fn test_homomorphic_addition() {
        let mut rng = OsRng;
        let b1 = BlindingFactor::random(&mut rng);
        let b2 = BlindingFactor::random(&mut rng);

        let c1 = PedersenCommitment::commit(100, &b1);
        let c2 = PedersenCommitment::commit(200, &b2);
        let c_sum = c1 + c2;

        // c_sum should equal commitment to 300 with b1 + b2
        let b_sum = &b1 + &b2;
        let expected = PedersenCommitment::commit(300, &b_sum);

        assert_eq!(c_sum, expected);
    }

    #[test]
    fn test_balance_verification() {
        let mut rng = OsRng;

        // Create inputs: 100 + 200 = 300
        let b_in1 = BlindingFactor::random(&mut rng);
        let b_in2 = BlindingFactor::random(&mut rng);
        let c_in1 = PedersenCommitment::commit(100, &b_in1);
        let c_in2 = PedersenCommitment::commit(200, &b_in2);

        // Create outputs: 150 + 150 = 300
        let b_out1 = BlindingFactor::random(&mut rng);
        let b_out2 = BlindingFactor::random(&mut rng);
        let c_out1 = PedersenCommitment::commit(150, &b_out1);
        let c_out2 = PedersenCommitment::commit(150, &b_out2);

        // Calculate excess blinding
        let excess = CommitmentBatch::compute_excess(
            &[b_in1, b_in2],
            &[b_out1, b_out2],
        );

        // Verify balance
        assert!(PedersenCommitment::verify_balance(
            &[c_in1, c_in2],
            &[c_out1, c_out2],
            &excess,
        ));
    }

    #[test]
    fn test_balance_verification_fails_on_mismatch() {
        let mut rng = OsRng;

        // Inputs: 100
        let b_in = BlindingFactor::random(&mut rng);
        let c_in = PedersenCommitment::commit(100, &b_in);

        // Outputs: 200 (doesn't balance!)
        let b_out = BlindingFactor::random(&mut rng);
        let c_out = PedersenCommitment::commit(200, &b_out);

        let excess = CommitmentBatch::compute_excess(&[b_in], &[b_out]);

        // Should fail - values don't balance
        assert!(!PedersenCommitment::verify_balance(
            &[c_in],
            &[c_out],
            &excess,
        ));
    }

    #[test]
    fn test_batch_sum() {
        let mut rng = OsRng;
        let (c1, b1) = PedersenCommitment::commit_random(100, &mut rng);
        let (c2, b2) = PedersenCommitment::commit_random(200, &mut rng);
        let (c3, b3) = PedersenCommitment::commit_random(300, &mut rng);

        let sum = CommitmentBatch::sum(&[c1, c2, c3]);
        let b_sum = CommitmentBatch::sum_blindings(&[b1, b2, b3]);

        let expected = PedersenCommitment::commit(600, &b_sum);
        assert_eq!(sum, expected);
    }

    #[test]
    fn test_blinding_factor_serialization() {
        let mut rng = OsRng;
        let bf = BlindingFactor::random(&mut rng);
        let bytes = bf.to_bytes();
        let recovered = BlindingFactor::from_bytes(&bytes).unwrap();
        assert_eq!(bf.to_bytes(), recovered.to_bytes());
    }

    #[test]
    fn test_generators_are_different() {
        let g = generator_g();
        let h = generator_h();
        assert_ne!(g.compress(), h.compress());
    }
}
