//! IBC Packet Handling
//!
//! Packets are the fundamental unit of data transfer in IBC.
//! Each packet contains application data and routing information.

use crate::channel::{ChannelId, PortId};
use crate::error::{IbcError, IbcResult};
use crate::types::{Height, Timestamp};
use crate::MAX_PACKET_DATA_SIZE;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::HashMap;

/// IBC Packet
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Packet {
    /// Packet sequence number
    pub sequence: u64,
    /// Source port
    pub source_port: PortId,
    /// Source channel
    pub source_channel: ChannelId,
    /// Destination port
    pub dest_port: PortId,
    /// Destination channel
    pub dest_channel: ChannelId,
    /// Packet data (app-specific)
    pub data: Vec<u8>,
    /// Timeout height (0 = disabled)
    pub timeout_height: Height,
    /// Timeout timestamp in nanoseconds (0 = disabled)
    pub timeout_timestamp: Timestamp,
}

impl Packet {
    /// Create a new packet
    pub fn new(
        sequence: u64,
        source_port: PortId,
        source_channel: ChannelId,
        dest_port: PortId,
        dest_channel: ChannelId,
        data: Vec<u8>,
        timeout_height: Height,
        timeout_timestamp: Timestamp,
    ) -> Self {
        Self {
            sequence,
            source_port,
            source_channel,
            dest_port,
            dest_channel,
            data,
            timeout_height,
            timeout_timestamp,
        }
    }

    /// Check if packet has timed out based on height
    pub fn is_timed_out_by_height(&self, current_height: Height) -> bool {
        !self.timeout_height.is_zero() && current_height >= self.timeout_height
    }

    /// Check if packet has timed out based on timestamp
    pub fn is_timed_out_by_timestamp(&self, current_time: Timestamp) -> bool {
        !self.timeout_timestamp.is_zero() && current_time >= self.timeout_timestamp
    }

    /// Check if packet has timed out
    pub fn is_timed_out(&self, current_height: Height, current_time: Timestamp) -> bool {
        self.is_timed_out_by_height(current_height) || self.is_timed_out_by_timestamp(current_time)
    }

    /// Validate the packet
    pub fn validate(&self) -> IbcResult<()> {
        if self.sequence == 0 {
            return Err(IbcError::InvalidPacketSequence {
                expected: 1,
                actual: 0,
            });
        }

        self.source_port.validate()?;
        self.dest_port.validate()?;

        if self.data.len() > MAX_PACKET_DATA_SIZE {
            return Err(IbcError::PacketDataTooLarge {
                size: self.data.len(),
                max: MAX_PACKET_DATA_SIZE,
            });
        }

        // At least one timeout must be set
        if self.timeout_height.is_zero() && self.timeout_timestamp.is_zero() {
            return Err(IbcError::InvalidCommitment(
                "at least one timeout must be set".to_string(),
            ));
        }

        Ok(())
    }

    /// Compute packet commitment hash
    pub fn commitment(&self) -> PacketCommitment {
        let mut hasher = Sha256::new();
        hasher.update(&self.timeout_timestamp.nanoseconds().to_be_bytes());
        hasher.update(&self.timeout_height.revision_number.to_be_bytes());
        hasher.update(&self.timeout_height.revision_height.to_be_bytes());

        // Hash the data
        let data_hash = Sha256::digest(&self.data);
        hasher.update(&data_hash);

        PacketCommitment(hasher.finalize().to_vec())
    }
}

/// Packet commitment hash
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PacketCommitment(pub Vec<u8>);

impl PacketCommitment {
    /// Create from bytes
    pub fn new(bytes: Vec<u8>) -> Self {
        Self(bytes)
    }

    /// Get as bytes
    pub fn as_bytes(&self) -> &[u8] {
        &self.0
    }

    /// Get as hex string
    pub fn to_hex(&self) -> String {
        hex::encode(&self.0)
    }
}

/// Packet acknowledgement
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Acknowledgement {
    /// Success result with optional data
    Success(Vec<u8>),
    /// Error result
    Error(String),
}

impl Acknowledgement {
    /// Create a success acknowledgement
    pub fn success(data: Vec<u8>) -> Self {
        Acknowledgement::Success(data)
    }

    /// Create an error acknowledgement
    pub fn error(msg: impl Into<String>) -> Self {
        Acknowledgement::Error(msg.into())
    }

    /// Check if acknowledgement is success
    pub fn is_success(&self) -> bool {
        matches!(self, Acknowledgement::Success(_))
    }

    /// Compute acknowledgement commitment
    pub fn commitment(&self) -> PacketCommitment {
        let bytes = match self {
            Acknowledgement::Success(data) => {
                let mut result = vec![0x01]; // Success prefix
                result.extend(data);
                result
            }
            Acknowledgement::Error(msg) => {
                let mut result = vec![0x00]; // Error prefix
                result.extend(msg.as_bytes());
                result
            }
        };

        let hash = Sha256::digest(&bytes);
        PacketCommitment(hash.to_vec())
    }

    /// Encode to bytes
    pub fn encode(&self) -> Vec<u8> {
        serde_json::to_vec(self).unwrap_or_default()
    }

    /// Decode from bytes
    pub fn decode(bytes: &[u8]) -> IbcResult<Self> {
        serde_json::from_slice(bytes)
            .map_err(|e| IbcError::InvalidAcknowledgement(e.to_string()))
    }
}

/// Timeout information
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Timeout {
    /// Timeout height
    pub height: Height,
    /// Timeout timestamp
    pub timestamp: Timestamp,
}

impl Timeout {
    /// Create a new timeout
    pub fn new(height: Height, timestamp: Timestamp) -> Self {
        Self { height, timestamp }
    }

    /// Create a height-only timeout
    pub fn height(height: u64) -> Self {
        Self {
            height: Height::from_height(height),
            timestamp: Timestamp::default(),
        }
    }

    /// Create a timestamp-only timeout
    pub fn timestamp(timestamp: u64) -> Self {
        Self {
            height: Height::default(),
            timestamp: Timestamp::from_nanoseconds(timestamp),
        }
    }

    /// Check if timed out
    pub fn is_timed_out(&self, current_height: Height, current_time: Timestamp) -> bool {
        (!self.height.is_zero() && current_height >= self.height)
            || (!self.timestamp.is_zero() && current_time >= self.timestamp)
    }
}

impl Default for Timeout {
    fn default() -> Self {
        Self {
            height: Height::default(),
            timestamp: Timestamp::default(),
        }
    }
}

/// Packet receipt (for unordered channels)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PacketReceipt {
    /// Received at height
    pub received_at: Height,
    /// Acknowledgement (if processed)
    pub acknowledgement: Option<Acknowledgement>,
}

/// Packet state tracking
pub struct PacketHandler {
    /// Sent packet commitments by (port, channel, sequence)
    sent_commitments: HashMap<(PortId, ChannelId, u64), PacketCommitment>,
    /// Received packet receipts by (port, channel, sequence)
    receipts: HashMap<(PortId, ChannelId, u64), PacketReceipt>,
    /// Acknowledgement commitments by (port, channel, sequence)
    ack_commitments: HashMap<(PortId, ChannelId, u64), PacketCommitment>,
}

impl PacketHandler {
    /// Create a new packet handler
    pub fn new() -> Self {
        Self {
            sent_commitments: HashMap::new(),
            receipts: HashMap::new(),
            ack_commitments: HashMap::new(),
        }
    }

    /// Store a sent packet commitment
    pub fn store_packet_commitment(&mut self, packet: &Packet) {
        let key = (
            packet.source_port.clone(),
            packet.source_channel.clone(),
            packet.sequence,
        );
        self.sent_commitments.insert(key, packet.commitment());
    }

    /// Get packet commitment
    pub fn get_packet_commitment(
        &self,
        port: &PortId,
        channel: &ChannelId,
        sequence: u64,
    ) -> Option<&PacketCommitment> {
        self.sent_commitments
            .get(&(port.clone(), channel.clone(), sequence))
    }

    /// Delete packet commitment (after ack received)
    pub fn delete_packet_commitment(&mut self, port: &PortId, channel: &ChannelId, sequence: u64) {
        self.sent_commitments
            .remove(&(port.clone(), channel.clone(), sequence));
    }

    /// Store packet receipt
    pub fn store_packet_receipt(
        &mut self,
        packet: &Packet,
        current_height: Height,
        ack: Option<Acknowledgement>,
    ) {
        let key = (
            packet.dest_port.clone(),
            packet.dest_channel.clone(),
            packet.sequence,
        );
        self.receipts.insert(
            key,
            PacketReceipt {
                received_at: current_height,
                acknowledgement: ack,
            },
        );
    }

    /// Check if packet was received
    pub fn has_packet_receipt(
        &self,
        port: &PortId,
        channel: &ChannelId,
        sequence: u64,
    ) -> bool {
        self.receipts
            .contains_key(&(port.clone(), channel.clone(), sequence))
    }

    /// Store acknowledgement commitment
    pub fn store_ack_commitment(
        &mut self,
        port: &PortId,
        channel: &ChannelId,
        sequence: u64,
        ack: &Acknowledgement,
    ) {
        let key = (port.clone(), channel.clone(), sequence);
        self.ack_commitments.insert(key, ack.commitment());
    }

    /// Get acknowledgement commitment
    pub fn get_ack_commitment(
        &self,
        port: &PortId,
        channel: &ChannelId,
        sequence: u64,
    ) -> Option<&PacketCommitment> {
        self.ack_commitments
            .get(&(port.clone(), channel.clone(), sequence))
    }

    /// Delete acknowledgement commitment (after processed)
    pub fn delete_ack_commitment(&mut self, port: &PortId, channel: &ChannelId, sequence: u64) {
        self.ack_commitments
            .remove(&(port.clone(), channel.clone(), sequence));
    }

    /// Verify packet hasn't already been received (for unordered channels)
    pub fn verify_no_receipt(&self, packet: &Packet) -> IbcResult<()> {
        if self.has_packet_receipt(&packet.dest_port, &packet.dest_channel, packet.sequence) {
            return Err(IbcError::PacketAlreadyReceived(packet.sequence));
        }
        Ok(())
    }

    /// Verify packet sequence for ordered channels
    pub fn verify_ordered_sequence(
        &self,
        packet: &Packet,
        expected_sequence: u64,
    ) -> IbcResult<()> {
        if packet.sequence != expected_sequence {
            return Err(IbcError::InvalidPacketSequence {
                expected: expected_sequence,
                actual: packet.sequence,
            });
        }
        Ok(())
    }

    /// Process timeout for a packet
    pub fn timeout_packet(&mut self, packet: &Packet) -> IbcResult<()> {
        // Verify commitment exists
        let key = (
            packet.source_port.clone(),
            packet.source_channel.clone(),
            packet.sequence,
        );
        if !self.sent_commitments.contains_key(&key) {
            return Err(IbcError::PacketNotFound(packet.sequence));
        }

        // Remove commitment
        self.sent_commitments.remove(&key);

        Ok(())
    }
}

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

/// Transfer packet data (ICS-20)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FungibleTokenPacketData {
    /// Token denomination
    pub denom: String,
    /// Amount to transfer
    pub amount: String,
    /// Sender address
    pub sender: String,
    /// Receiver address
    pub receiver: String,
    /// Optional memo
    pub memo: String,
}

impl FungibleTokenPacketData {
    /// Create a new transfer packet
    pub fn new(
        denom: impl Into<String>,
        amount: impl Into<String>,
        sender: impl Into<String>,
        receiver: impl Into<String>,
    ) -> Self {
        Self {
            denom: denom.into(),
            amount: amount.into(),
            sender: sender.into(),
            receiver: receiver.into(),
            memo: String::new(),
        }
    }

    /// Add memo
    pub fn with_memo(mut self, memo: impl Into<String>) -> Self {
        self.memo = memo.into();
        self
    }

    /// Encode to bytes
    pub fn encode(&self) -> Vec<u8> {
        serde_json::to_vec(self).unwrap_or_default()
    }

    /// Decode from bytes
    pub fn decode(bytes: &[u8]) -> IbcResult<Self> {
        serde_json::from_slice(bytes)
            .map_err(|e| IbcError::DeserializationError(e.to_string()))
    }

    /// Get the denomination trace path
    pub fn get_denom_trace(&self) -> Vec<String> {
        self.denom.split('/').map(String::from).collect()
    }

    /// Check if this is a native token
    pub fn is_native(&self) -> bool {
        !self.denom.contains('/')
    }
}

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

    fn test_packet() -> Packet {
        Packet::new(
            1,
            PortId::transfer(),
            ChannelId::new(0),
            PortId::transfer(),
            ChannelId::new(0),
            b"test data".to_vec(),
            Height::from_height(100),
            Timestamp::default(),
        )
    }

    #[test]
    fn test_packet_commitment() {
        let packet = test_packet();
        let commitment = packet.commitment();
        assert!(!commitment.as_bytes().is_empty());

        // Same packet should have same commitment
        let commitment2 = packet.commitment();
        assert_eq!(commitment, commitment2);
    }

    #[test]
    fn test_packet_timeout() {
        let packet = test_packet();

        // Not timed out at lower height
        assert!(!packet.is_timed_out_by_height(Height::from_height(50)));

        // Timed out at same height
        assert!(packet.is_timed_out_by_height(Height::from_height(100)));

        // Timed out at higher height
        assert!(packet.is_timed_out_by_height(Height::from_height(150)));
    }

    #[test]
    fn test_acknowledgement() {
        let ack = Acknowledgement::success(b"ok".to_vec());
        assert!(ack.is_success());

        let ack_err = Acknowledgement::error("failed");
        assert!(!ack_err.is_success());

        // Commitments should be different
        assert_ne!(ack.commitment(), ack_err.commitment());
    }

    #[test]
    fn test_packet_handler() {
        let mut handler = PacketHandler::new();
        let packet = test_packet();

        // Store commitment
        handler.store_packet_commitment(&packet);
        assert!(handler
            .get_packet_commitment(&packet.source_port, &packet.source_channel, packet.sequence)
            .is_some());

        // Store receipt
        handler.store_packet_receipt(&packet, Height::from_height(50), None);
        assert!(handler.has_packet_receipt(
            &packet.dest_port,
            &packet.dest_channel,
            packet.sequence
        ));

        // Verify no duplicate receipt
        assert!(handler.verify_no_receipt(&packet).is_err());
    }

    #[test]
    fn test_transfer_packet_data() {
        let data = FungibleTokenPacketData::new("uatom", "1000000", "cosmos1...", "synor1...");

        let encoded = data.encode();
        let decoded = FungibleTokenPacketData::decode(&encoded).unwrap();

        assert_eq!(decoded.denom, data.denom);
        assert_eq!(decoded.amount, data.amount);
    }

    #[test]
    fn test_denom_trace() {
        // Native token
        let native = FungibleTokenPacketData::new("usynor", "1000", "a", "b");
        assert!(native.is_native());

        // IBC token
        let ibc = FungibleTokenPacketData::new("transfer/channel-0/uatom", "1000", "a", "b");
        assert!(!ibc.is_native());
        assert_eq!(ibc.get_denom_trace(), vec!["transfer", "channel-0", "uatom"]);
    }
}
