//! IBC Commitment and Proof Verification
//!
//! IBC uses Merkle proofs to verify state on remote chains.
//! This module handles commitment paths and proof verification.

use crate::error::{IbcError, IbcResult};
use crate::channel::{ChannelId, PortId};
use crate::client::ClientId;
use crate::connection::ConnectionId;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};

/// Merkle proof for IBC commitments
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MerkleProof {
    /// Proof operations (inner nodes)
    pub proofs: Vec<ProofOp>,
}

impl MerkleProof {
    /// Create a new Merkle proof
    pub fn new(proofs: Vec<ProofOp>) -> Self {
        Self { proofs }
    }

    /// Create an empty proof (for testing)
    pub fn empty() -> Self {
        Self { proofs: Vec::new() }
    }

    /// Verify the proof against a root and value
    pub fn verify(&self, root: &[u8], _path: &MerklePath, value: &[u8]) -> IbcResult<bool> {
        if self.proofs.is_empty() {
            return Err(IbcError::MissingProof("empty proof".to_string()));
        }

        // Compute expected hash
        let leaf_hash = hash_leaf(value);
        let mut computed_hash = leaf_hash;

        // Apply proof operations
        for (i, op) in self.proofs.iter().enumerate() {
            computed_hash = match &op.op_type {
                ProofOpType::InnerHash { prefix, suffix } => {
                    let mut hasher = Sha256::new();
                    hasher.update(prefix);
                    hasher.update(&computed_hash);
                    hasher.update(suffix);
                    hasher.finalize().to_vec()
                }
                ProofOpType::LeafHash { prefix } => {
                    if i != 0 {
                        return Err(IbcError::ProofVerificationFailed(
                            "leaf hash must be first".to_string(),
                        ));
                    }
                    let mut hasher = Sha256::new();
                    hasher.update(prefix);
                    hasher.update(value);
                    hasher.finalize().to_vec()
                }
            };
        }

        // Compare with root
        Ok(computed_hash == root)
    }
}

/// Proof operation type
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ProofOpType {
    /// Inner node hash
    InnerHash {
        /// Prefix bytes
        prefix: Vec<u8>,
        /// Suffix bytes
        suffix: Vec<u8>,
    },
    /// Leaf hash
    LeafHash {
        /// Prefix bytes
        prefix: Vec<u8>,
    },
}

/// Single proof operation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProofOp {
    /// Key at this level
    pub key: Vec<u8>,
    /// Operation type
    pub op_type: ProofOpType,
}

impl ProofOp {
    /// Create an inner hash operation
    pub fn inner(key: Vec<u8>, prefix: Vec<u8>, suffix: Vec<u8>) -> Self {
        Self {
            key,
            op_type: ProofOpType::InnerHash { prefix, suffix },
        }
    }

    /// Create a leaf hash operation
    pub fn leaf(key: Vec<u8>, prefix: Vec<u8>) -> Self {
        Self {
            key,
            op_type: ProofOpType::LeafHash { prefix },
        }
    }
}

/// Merkle path for key lookups
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MerklePath {
    /// Path segments
    pub key_path: Vec<String>,
}

impl MerklePath {
    /// Create a new Merkle path
    pub fn new(segments: Vec<String>) -> Self {
        Self { key_path: segments }
    }

    /// Create from key prefix and key
    pub fn from_prefix_key(prefix: &str, key: &str) -> Self {
        Self {
            key_path: vec![prefix.to_string(), key.to_string()],
        }
    }

    /// Get full path string
    pub fn path(&self) -> String {
        self.key_path.join("/")
    }

    /// Get path bytes for hashing
    pub fn to_bytes(&self) -> Vec<u8> {
        self.path().into_bytes()
    }
}

/// Commitment proof wrapper
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitmentProof {
    /// Merkle proof
    pub proof: MerkleProof,
    /// Proof height
    pub height: crate::types::Height,
}

impl CommitmentProof {
    /// Create a new commitment proof
    pub fn new(proof: MerkleProof, height: crate::types::Height) -> Self {
        Self { proof, height }
    }

    /// Verify the proof
    pub fn verify(&self, root: &[u8], path: &MerklePath, value: &[u8]) -> IbcResult<bool> {
        self.proof.verify(root, path, value)
    }
}

/// Hash a leaf value
fn hash_leaf(value: &[u8]) -> Vec<u8> {
    let mut hasher = Sha256::new();
    hasher.update([0x00]); // Leaf prefix
    hasher.update(value);
    hasher.finalize().to_vec()
}

/// Hash an inner node (used in full Merkle tree verification)
#[allow(dead_code)]
fn hash_inner(left: &[u8], right: &[u8]) -> Vec<u8> {
    let mut hasher = Sha256::new();
    hasher.update([0x01]); // Inner prefix
    hasher.update(left);
    hasher.update(right);
    hasher.finalize().to_vec()
}

// ============================================================================
// IBC Commitment Paths
// ============================================================================

/// Client state commitment path
pub fn client_state_path(client_id: &ClientId) -> MerklePath {
    MerklePath::new(vec![
        "clients".to_string(),
        client_id.to_string(),
        "clientState".to_string(),
    ])
}

/// Consensus state commitment path
pub fn consensus_state_path(client_id: &ClientId, height: &crate::types::Height) -> MerklePath {
    MerklePath::new(vec![
        "clients".to_string(),
        client_id.to_string(),
        "consensusStates".to_string(),
        height.to_string(),
    ])
}

/// Connection commitment path
pub fn connection_path(conn_id: &ConnectionId) -> MerklePath {
    MerklePath::new(vec![
        "connections".to_string(),
        conn_id.to_string(),
    ])
}

/// Channel commitment path
pub fn channel_path(port_id: &PortId, channel_id: &ChannelId) -> MerklePath {
    MerklePath::new(vec![
        "channelEnds".to_string(),
        "ports".to_string(),
        port_id.to_string(),
        "channels".to_string(),
        channel_id.to_string(),
    ])
}

/// Packet commitment path
pub fn packet_commitment_path(
    port_id: &PortId,
    channel_id: &ChannelId,
    sequence: u64,
) -> MerklePath {
    MerklePath::new(vec![
        "commitments".to_string(),
        "ports".to_string(),
        port_id.to_string(),
        "channels".to_string(),
        channel_id.to_string(),
        "sequences".to_string(),
        sequence.to_string(),
    ])
}

/// Packet receipt path (for unordered channels)
pub fn packet_receipt_path(
    port_id: &PortId,
    channel_id: &ChannelId,
    sequence: u64,
) -> MerklePath {
    MerklePath::new(vec![
        "receipts".to_string(),
        "ports".to_string(),
        port_id.to_string(),
        "channels".to_string(),
        channel_id.to_string(),
        "sequences".to_string(),
        sequence.to_string(),
    ])
}

/// Packet acknowledgement path
pub fn packet_acknowledgement_path(
    port_id: &PortId,
    channel_id: &ChannelId,
    sequence: u64,
) -> MerklePath {
    MerklePath::new(vec![
        "acks".to_string(),
        "ports".to_string(),
        port_id.to_string(),
        "channels".to_string(),
        channel_id.to_string(),
        "sequences".to_string(),
        sequence.to_string(),
    ])
}

/// Next sequence send path
pub fn next_sequence_send_path(port_id: &PortId, channel_id: &ChannelId) -> MerklePath {
    MerklePath::new(vec![
        "nextSequenceSend".to_string(),
        "ports".to_string(),
        port_id.to_string(),
        "channels".to_string(),
        channel_id.to_string(),
    ])
}

/// Next sequence receive path
pub fn next_sequence_recv_path(port_id: &PortId, channel_id: &ChannelId) -> MerklePath {
    MerklePath::new(vec![
        "nextSequenceRecv".to_string(),
        "ports".to_string(),
        port_id.to_string(),
        "channels".to_string(),
        channel_id.to_string(),
    ])
}

/// Next sequence acknowledge path
pub fn next_sequence_ack_path(port_id: &PortId, channel_id: &ChannelId) -> MerklePath {
    MerklePath::new(vec![
        "nextSequenceAck".to_string(),
        "ports".to_string(),
        port_id.to_string(),
        "channels".to_string(),
        channel_id.to_string(),
    ])
}

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

    #[test]
    fn test_merkle_path() {
        let path = client_state_path(&ClientId::new("07-tendermint", 0));
        assert_eq!(
            path.path(),
            "clients/07-tendermint-0/clientState"
        );
    }

    #[test]
    fn test_packet_paths() {
        let port = PortId::transfer();
        let channel = ChannelId::new(0);

        let commit_path = packet_commitment_path(&port, &channel, 1);
        assert!(commit_path.path().contains("commitments"));

        let receipt_path = packet_receipt_path(&port, &channel, 1);
        assert!(receipt_path.path().contains("receipts"));

        let ack_path = packet_acknowledgement_path(&port, &channel, 1);
        assert!(ack_path.path().contains("acks"));
    }

    #[test]
    fn test_hash_leaf() {
        let value = b"test value";
        let hash1 = hash_leaf(value);
        let hash2 = hash_leaf(value);

        // Same value should produce same hash
        assert_eq!(hash1, hash2);

        // Different value should produce different hash
        let hash3 = hash_leaf(b"different");
        assert_ne!(hash1, hash3);
    }

    #[test]
    fn test_hash_inner() {
        let left = vec![1, 2, 3];
        let right = vec![4, 5, 6];

        let hash1 = hash_inner(&left, &right);
        let hash2 = hash_inner(&left, &right);
        assert_eq!(hash1, hash2);

        // Order matters
        let hash3 = hash_inner(&right, &left);
        assert_ne!(hash1, hash3);
    }

    #[test]
    fn test_proof_op() {
        let leaf = ProofOp::leaf(b"key".to_vec(), b"prefix".to_vec());
        assert!(matches!(leaf.op_type, ProofOpType::LeafHash { .. }));

        let inner = ProofOp::inner(b"key".to_vec(), b"prefix".to_vec(), b"suffix".to_vec());
        assert!(matches!(inner.op_type, ProofOpType::InnerHash { .. }));
    }
}
