//! Witness receipt system for verifiable DHT operations
//!
//! Provides cryptographic proof of DHT operations with audit trails and non-repudiation.

use crate::dht::content_addressing::ContentAddress;
use anyhow::{Result, anyhow};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;

/// Unique identifier for operations
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct OperationId(String);

impl Default for OperationId {
    fn default() -> Self {
        Self::new()
    }
}

impl OperationId {
    pub fn new() -> Self {
        Self(uuid::Uuid::new_v4().to_string())
    }
}

/// Types of DHT operations
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum OperationType {
    Store,
    Retrieve,
    Verify,
    Delete,
    Batch(Vec<OperationType>),
}

/// Node identifier for witness system
///
/// TODO: Migrate to `crate::adaptive::NodeId` (UserId) when witness system
/// is integrated with real cryptographic identities. Currently uses String
/// for testing/placeholder purposes.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct WitnessNodeId(String);

impl WitnessNodeId {
    /// Create a new witness node identifier from a string
    pub fn new(id: &str) -> Self {
        Self(id.to_string())
    }

    /// Get the inner string value
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

// Type alias for backward compatibility within this module
// TODO: Remove this alias after updating all usages
pub type NodeId = WitnessNodeId;

/// Operation metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OperationMetadata {
    pub size_bytes: usize,
    pub chunk_count: Option<usize>,
    pub redundancy_level: Option<f64>,
    pub custom: HashMap<String, String>,
}

/// Placeholder for ML-KEM signature (will be replaced with actual implementation)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MlKemSignature {
    data: Vec<u8>,
}

impl MlKemSignature {
    pub fn placeholder() -> Self {
        Self {
            data: vec![0u8; 64],
        }
    }

    fn _verify(&self, _data: &[u8], _public_key: &MlKemPublicKey) -> bool {
        // Placeholder - always returns true for now
        true
    }
}

/// Placeholder for ML-KEM public key
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MlKemPublicKey {
    data: Vec<u8>,
}

/// Placeholder for ML-KEM private key
#[derive(Debug, Clone)]
pub struct MlKemPrivateKey {
    _data: Vec<u8>,
}

impl MlKemPrivateKey {
    fn sign(&self, _data: &[u8]) -> MlKemSignature {
        MlKemSignature::placeholder()
    }
}

/// Storage proof for store operations
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageProof {
    pub chunk_hashes: Vec<ContentAddress>,
    pub storage_commitment: ContentAddress,
    pub merkle_proof: Vec<ContentAddress>,
}

/// Retrieval proof for retrieve operations
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RetrievalProof {
    pub retrieved_hash: ContentAddress,
    pub retrieval_time: DateTime<Utc>,
    pub bandwidth_used: usize,
}

/// Witness proof from participating node
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WitnessProof {
    pub node_id: NodeId,
    pub operation_hash: ContentAddress,
    pub node_signature: MlKemSignature,
    pub storage_proof: Option<StorageProof>,
    pub retrieval_proof: Option<RetrievalProof>,
}

/// Cryptographic witness receipt for DHT operations
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WitnessReceipt {
    pub operation_id: OperationId,
    pub operation_type: OperationType,
    pub content_hash: ContentAddress,
    pub timestamp: DateTime<Utc>,
    pub participating_nodes: Vec<NodeId>,
    pub operation_metadata: OperationMetadata,
    pub signature: MlKemSignature,
    pub witness_proofs: Vec<WitnessProof>,
}

impl WitnessReceipt {
    /// Calculate hash of receipt for chaining
    pub fn hash(&self) -> Result<ContentAddress> {
        let json = serde_json::to_vec(self)?;
        Ok(ContentAddress::from_bytes(blake3::hash(&json).as_bytes()))
    }

    /// Verify internal consistency
    pub fn verify_consistency(&self) -> bool {
        // Check that all witness proofs are from participating nodes
        for proof in &self.witness_proofs {
            if !self.participating_nodes.contains(&proof.node_id) {
                return false;
            }
        }
        true
    }
}

/// DHT operation representation
pub struct DhtOperation {
    pub operation_type: OperationType,
    pub content_hash: ContentAddress,
    pub nodes: Vec<NodeId>,
    pub metadata: OperationMetadata,
}

/// Receipt storage system
struct ReceiptStorage {
    receipts: HashMap<OperationId, WitnessReceipt>,
    by_content: HashMap<ContentAddress, Vec<OperationId>>,
}

impl ReceiptStorage {
    fn new() -> Self {
        Self {
            receipts: HashMap::new(),
            by_content: HashMap::new(),
        }
    }

    fn store(&mut self, receipt: WitnessReceipt) {
        let content_hash = receipt.content_hash.clone();
        let operation_id = receipt.operation_id.clone();

        self.by_content
            .entry(content_hash)
            .or_default()
            .push(operation_id.clone());

        self.receipts.insert(operation_id, receipt);
    }

    fn _get(&self, operation_id: &OperationId) -> Option<&WitnessReceipt> {
        self.receipts.get(operation_id)
    }

    fn get_by_content(&self, content_hash: &ContentAddress) -> Vec<&WitnessReceipt> {
        self.by_content
            .get(content_hash)
            .map(|ids| ids.iter().filter_map(|id| self.receipts.get(id)).collect())
            .unwrap_or_default()
    }
}

/// Witness receipt system for verifiable DHT operations
pub struct WitnessReceiptSystem {
    signing_key: MlKemPrivateKey,
    _verification_keys: Arc<RwLock<HashMap<NodeId, MlKemPublicKey>>>,
    receipt_store: Arc<RwLock<ReceiptStorage>>,
}

impl WitnessReceiptSystem {
    /// Create new witness receipt system
    pub fn new() -> Self {
        Self {
            signing_key: MlKemPrivateKey {
                _data: vec![0u8; 32],
            },
            _verification_keys: Arc::new(RwLock::new(HashMap::new())),
            receipt_store: Arc::new(RwLock::new(ReceiptStorage::new())),
        }
    }

    /// Create receipt for DHT operation
    pub async fn create_receipt(&self, operation: &DhtOperation) -> Result<WitnessReceipt> {
        let operation_id = OperationId::new();

        // Generate witness proofs (placeholder)
        let witness_proofs = operation
            .nodes
            .iter()
            .map(|node_id| WitnessProof {
                node_id: node_id.clone(),
                operation_hash: operation.content_hash.clone(),
                node_signature: MlKemSignature::placeholder(),
                storage_proof: match operation.operation_type {
                    OperationType::Store => Some(StorageProof {
                        chunk_hashes: vec![operation.content_hash.clone()],
                        storage_commitment: operation.content_hash.clone(),
                        merkle_proof: vec![],
                    }),
                    _ => None,
                },
                retrieval_proof: match operation.operation_type {
                    OperationType::Retrieve => Some(RetrievalProof {
                        retrieved_hash: operation.content_hash.clone(),
                        retrieval_time: Utc::now(),
                        bandwidth_used: operation.metadata.size_bytes,
                    }),
                    _ => None,
                },
            })
            .collect();

        let receipt = WitnessReceipt {
            operation_id: operation_id.clone(),
            operation_type: operation.operation_type.clone(),
            content_hash: operation.content_hash.clone(),
            timestamp: Utc::now(),
            participating_nodes: operation.nodes.clone(),
            operation_metadata: operation.metadata.clone(),
            signature: self.signing_key.sign(b"receipt_data"),
            witness_proofs,
        };

        // Store receipt
        let mut store = self.receipt_store.write().await;
        store.store(receipt.clone());

        Ok(receipt)
    }

    /// Verify receipt authenticity
    pub async fn verify_receipt(&self, receipt: &WitnessReceipt) -> Result<bool> {
        // Verify consistency
        if !receipt.verify_consistency() {
            return Ok(false);
        }

        // Verify signature (placeholder - always true for now)
        // In production, would verify against actual ML-KEM public key

        // Verify witness proofs
        for proof in &receipt.witness_proofs {
            // Placeholder verification - in production would verify signatures
            if proof.node_signature.data.is_empty() {
                return Ok(false);
            }
        }

        Ok(true)
    }

    /// Create batch receipt for multiple operations
    pub async fn create_batch_receipt(
        &self,
        operations: &[DhtOperation],
    ) -> Result<WitnessReceipt> {
        if operations.is_empty() {
            return Err(anyhow!("Cannot create batch receipt for empty operations"));
        }

        // Combine operation types
        let batch_type = OperationType::Batch(
            operations
                .iter()
                .map(|op| op.operation_type.clone())
                .collect(),
        );

        // Use first operation's content hash as primary
        let batch_operation = DhtOperation {
            operation_type: batch_type,
            content_hash: operations[0].content_hash.clone(),
            nodes: operations[0].nodes.clone(),
            metadata: OperationMetadata {
                size_bytes: operations.iter().map(|op| op.metadata.size_bytes).sum(),
                chunk_count: Some(operations.len()),
                redundancy_level: operations[0].metadata.redundancy_level,
                custom: HashMap::new(),
            },
        };

        self.create_receipt(&batch_operation).await
    }

    /// Get audit trail for content
    pub async fn get_audit_trail(
        &self,
        content_hash: &ContentAddress,
    ) -> Result<Vec<WitnessReceipt>> {
        let store = self.receipt_store.read().await;
        let receipts = store
            .get_by_content(content_hash)
            .into_iter()
            .cloned()
            .collect();
        Ok(receipts)
    }

    /// Verify integrity of receipt chain
    pub async fn verify_chain_integrity(&self, receipts: &[WitnessReceipt]) -> Result<bool> {
        if receipts.is_empty() {
            return Ok(true);
        }

        // Verify each receipt
        for receipt in receipts {
            if !self.verify_receipt(receipt).await? {
                return Ok(false);
            }
        }

        // Verify chronological ordering
        for window in receipts.windows(2) {
            if window[0].timestamp > window[1].timestamp {
                return Ok(false);
            }
        }

        Ok(true)
    }
}

impl Default for WitnessReceiptSystem {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[tokio::test]
    async fn test_receipt_creation_and_verification() -> Result<()> {
        let receipt_system = WitnessReceiptSystem::new();
        let operation = DhtOperation {
            operation_type: OperationType::Store,
            content_hash: ContentAddress::from_bytes(&[1u8; 32]),
            nodes: vec![NodeId::new("node1"), NodeId::new("node2")],
            metadata: OperationMetadata {
                size_bytes: 1024,
                chunk_count: Some(1),
                redundancy_level: Some(0.5),
                custom: HashMap::new(),
            },
        };

        let receipt = receipt_system.create_receipt(&operation).await?;
        assert!(receipt_system.verify_receipt(&receipt).await?);

        Ok(())
    }

    #[tokio::test]
    async fn test_audit_trail_reconstruction() -> Result<()> {
        let receipt_system = WitnessReceiptSystem::new();
        let content_hash = ContentAddress::from_bytes(&[42u8; 32]);

        // Create multiple operations for same content
        for op_type in [
            OperationType::Store,
            OperationType::Retrieve,
            OperationType::Verify,
        ] {
            let operation = DhtOperation {
                operation_type: op_type,
                content_hash: content_hash.clone(),
                nodes: vec![NodeId::new("node1")],
                metadata: OperationMetadata {
                    size_bytes: 1024,
                    chunk_count: None,
                    redundancy_level: None,
                    custom: HashMap::new(),
                },
            };
            receipt_system.create_receipt(&operation).await?;
        }

        let audit_trail = receipt_system.get_audit_trail(&content_hash).await?;
        assert_eq!(audit_trail.len(), 3);
        assert!(receipt_system.verify_chain_integrity(&audit_trail).await?);

        Ok(())
    }
}
