//! Atomic Swap Engine
//!
//! Implements Hashed Time-Locked Contracts (HTLC) for trustless cross-chain swaps.
//! This enables atomic token exchanges between two parties on different chains
//! without requiring trust or intermediaries.
//!
//! # HTLC Flow
//!
//! 1. Alice creates a secret and its hash, locks her tokens on Chain A
//! 2. Bob sees the hashlock, locks his tokens on Chain B with same hashlock
//! 3. Alice claims Bob's tokens by revealing the secret
//! 4. Bob uses the revealed secret to claim Alice's tokens
//! 5. If either party fails to act before timeout, tokens are refunded
//!
//! # Security Properties
//!
//! - **Atomicity**: Either both transfers complete or neither does
//! - **Trustless**: No third party can steal funds
//! - **Timeout Safety**: Funds always recoverable via timelock

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

// ============================================================================
// Constants
// ============================================================================

/// Minimum timelock duration (in seconds) for safety
pub const MIN_TIMELOCK_SECONDS: u64 = 3600; // 1 hour

/// Maximum timelock duration (in seconds)
pub const MAX_TIMELOCK_SECONDS: u64 = 7 * 24 * 3600; // 1 week

/// Default timelock for initiator (longer to give counterparty time)
pub const DEFAULT_INITIATOR_TIMELOCK: u64 = 48 * 3600; // 48 hours

/// Default timelock for responder (shorter, must claim before initiator's expires)
pub const DEFAULT_RESPONDER_TIMELOCK: u64 = 24 * 3600; // 24 hours

/// Secret length in bytes
pub const SECRET_LENGTH: usize = 32;

// ============================================================================
// Swap Types
// ============================================================================

/// Unique identifier for a swap
#[derive(
    Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
)]
pub struct SwapId(pub String);

impl SwapId {
    pub fn new(id: impl Into<String>) -> Self {
        Self(id.into())
    }

    /// Generate a unique swap ID from parameters
    pub fn generate(initiator: &str, responder: &str, hashlock: &[u8], timestamp: u64) -> Self {
        let mut hasher = Sha256::new();
        hasher.update(initiator.as_bytes());
        hasher.update(responder.as_bytes());
        hasher.update(hashlock);
        hasher.update(&timestamp.to_le_bytes());
        Self(hex::encode(&hasher.finalize()[..16]))
    }
}

impl fmt::Display for SwapId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// State of an atomic swap
#[derive(
    Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
)]
pub enum SwapState {
    /// Swap created but not yet locked
    Pending,
    /// Tokens are locked with HTLC
    Locked,
    /// Swap completed successfully
    Completed,
    /// Swap refunded after timeout
    Refunded,
    /// Swap expired (can be refunded)
    Expired,
    /// Swap cancelled before locking
    Cancelled,
}

impl fmt::Display for SwapState {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            SwapState::Pending => write!(f, "pending"),
            SwapState::Locked => write!(f, "locked"),
            SwapState::Completed => write!(f, "completed"),
            SwapState::Refunded => write!(f, "refunded"),
            SwapState::Expired => write!(f, "expired"),
            SwapState::Cancelled => write!(f, "cancelled"),
        }
    }
}

/// Type of token being swapped
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub enum SwapAsset {
    /// Native blockchain token
    Native {
        /// Amount in smallest unit
        amount: u128,
    },
    /// ICS-20 fungible token
    Ics20 {
        /// Token denomination (e.g., "transfer/channel-0/uatom")
        denom: String,
        /// Amount
        amount: u128,
    },
    /// NFT (ICS-721)
    Ics721 {
        /// Class ID
        class_id: String,
        /// Token IDs
        token_ids: Vec<String>,
    },
}

impl SwapAsset {
    pub fn native(amount: u128) -> Self {
        Self::Native { amount }
    }

    pub fn ics20(denom: impl Into<String>, amount: u128) -> Self {
        Self::Ics20 {
            denom: denom.into(),
            amount,
        }
    }

    /// Validate the asset
    pub fn validate(&self) -> IbcResult<()> {
        match self {
            SwapAsset::Native { amount } | SwapAsset::Ics20 { amount, .. } => {
                if *amount == 0 {
                    return Err(IbcError::InvalidPacketData(
                        "swap amount cannot be zero".to_string(),
                    ));
                }
            }
            SwapAsset::Ics721 { token_ids, .. } => {
                if token_ids.is_empty() {
                    return Err(IbcError::InvalidPacketData(
                        "NFT swap must include token IDs".to_string(),
                    ));
                }
            }
        }
        Ok(())
    }
}

/// Hashlock - the hash of the secret
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct Hashlock(pub Vec<u8>);

impl Hashlock {
    /// Create a hashlock from a secret
    pub fn from_secret(secret: &[u8]) -> Self {
        let mut hasher = Sha256::new();
        hasher.update(secret);
        Self(hasher.finalize().to_vec())
    }

    /// Verify that a preimage matches this hashlock
    pub fn verify(&self, preimage: &[u8]) -> bool {
        let computed = Self::from_secret(preimage);
        self.0 == computed.0
    }

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

impl fmt::Display for Hashlock {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", hex::encode(&self.0))
    }
}

/// Timelock - expiration time for the HTLC
#[derive(
    Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
)]
pub struct Timelock {
    /// Absolute expiration timestamp (nanoseconds since epoch)
    pub expiry: Timestamp,
}

impl Timelock {
    pub fn new(expiry: Timestamp) -> Self {
        Self { expiry }
    }

    /// Create timelock from current time plus duration
    pub fn from_duration(current: Timestamp, duration_secs: u64) -> Self {
        Self {
            expiry: Timestamp::from_nanoseconds(
                current.nanoseconds() + duration_secs * 1_000_000_000,
            ),
        }
    }

    /// Check if timelock has expired
    pub fn is_expired(&self, current: Timestamp) -> bool {
        current >= self.expiry
    }

    /// Get remaining time in seconds
    pub fn remaining_secs(&self, current: Timestamp) -> u64 {
        if current >= self.expiry {
            0
        } else {
            (self.expiry.nanoseconds() - current.nanoseconds()) / 1_000_000_000
        }
    }
}

// ============================================================================
// HTLC Structure
// ============================================================================

/// Hashed Time-Locked Contract
#[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct Htlc {
    /// Unique identifier
    pub swap_id: SwapId,
    /// Current state
    pub state: SwapState,
    /// The party who initiated the swap
    pub initiator: String,
    /// The counterparty
    pub responder: String,
    /// Asset being locked by this HTLC
    pub asset: SwapAsset,
    /// Hash of the secret (SHA256)
    pub hashlock: Hashlock,
    /// Expiration time
    pub timelock: Timelock,
    /// Secret (only set after claim)
    pub secret: Option<Vec<u8>>,
    /// IBC channel for cross-chain communication
    pub channel_id: Option<ChannelId>,
    /// IBC port
    pub port_id: Option<PortId>,
    /// Creation timestamp
    pub created_at: Timestamp,
    /// Completion/refund timestamp
    pub completed_at: Option<Timestamp>,
}

impl Htlc {
    /// Create a new HTLC
    pub fn new(
        initiator: impl Into<String>,
        responder: impl Into<String>,
        asset: SwapAsset,
        hashlock: Hashlock,
        timelock: Timelock,
        created_at: Timestamp,
    ) -> Self {
        let initiator = initiator.into();
        let responder = responder.into();
        let swap_id =
            SwapId::generate(&initiator, &responder, hashlock.as_bytes(), created_at.nanoseconds());

        Self {
            swap_id,
            state: SwapState::Pending,
            initiator,
            responder,
            asset,
            hashlock,
            timelock,
            secret: None,
            channel_id: None,
            port_id: None,
            created_at,
            completed_at: None,
        }
    }

    /// Lock the HTLC (transition from Pending to Locked)
    pub fn lock(&mut self) -> IbcResult<()> {
        if self.state != SwapState::Pending {
            return Err(IbcError::InvalidChannelState {
                expected: "pending".to_string(),
                actual: self.state.to_string(),
            });
        }
        self.state = SwapState::Locked;
        Ok(())
    }

    /// Claim the HTLC with the secret
    pub fn claim(&mut self, secret: &[u8], current_time: Timestamp) -> IbcResult<()> {
        // Verify state
        if self.state != SwapState::Locked {
            return Err(IbcError::InvalidChannelState {
                expected: "locked".to_string(),
                actual: self.state.to_string(),
            });
        }

        // Check timelock hasn't expired
        if self.timelock.is_expired(current_time) {
            self.state = SwapState::Expired;
            return Err(IbcError::SwapTimeoutExpired(self.swap_id.to_string()));
        }

        // Verify secret matches hashlock
        if !self.hashlock.verify(secret) {
            return Err(IbcError::InvalidSecret(
                "secret does not match hashlock".to_string(),
            ));
        }

        // Complete the swap
        self.secret = Some(secret.to_vec());
        self.state = SwapState::Completed;
        self.completed_at = Some(current_time);

        Ok(())
    }

    /// Refund the HTLC after timeout
    pub fn refund(&mut self, current_time: Timestamp) -> IbcResult<()> {
        // Verify state is locked or expired
        if self.state != SwapState::Locked && self.state != SwapState::Expired {
            return Err(IbcError::InvalidChannelState {
                expected: "locked or expired".to_string(),
                actual: self.state.to_string(),
            });
        }

        // Check timelock has expired
        if !self.timelock.is_expired(current_time) {
            return Err(IbcError::InvalidPacketData(format!(
                "cannot refund: {} seconds remaining",
                self.timelock.remaining_secs(current_time)
            )));
        }

        // Refund
        self.state = SwapState::Refunded;
        self.completed_at = Some(current_time);

        Ok(())
    }

    /// Cancel a pending swap (before locking)
    pub fn cancel(&mut self) -> IbcResult<()> {
        if self.state != SwapState::Pending {
            return Err(IbcError::InvalidChannelState {
                expected: "pending".to_string(),
                actual: self.state.to_string(),
            });
        }
        self.state = SwapState::Cancelled;
        Ok(())
    }

    /// Check if swap is finalized (completed, refunded, or cancelled)
    pub fn is_finalized(&self) -> bool {
        matches!(
            self.state,
            SwapState::Completed | SwapState::Refunded | SwapState::Cancelled
        )
    }
}

// ============================================================================
// Cross-Chain Swap
// ============================================================================

/// Full atomic swap between two chains
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AtomicSwap {
    /// Swap ID (shared between chains)
    pub swap_id: SwapId,
    /// HTLC on the initiator's chain
    pub initiator_htlc: Htlc,
    /// HTLC on the responder's chain (optional, set after response)
    pub responder_htlc: Option<Htlc>,
    /// Overall swap state
    pub state: SwapState,
    /// The secret (known only to initiator until claim)
    secret: Vec<u8>,
}

impl AtomicSwap {
    /// Create a new atomic swap (called by initiator)
    pub fn new(
        initiator: impl Into<String>,
        responder: impl Into<String>,
        initiator_asset: SwapAsset,
        responder_asset: SwapAsset,
        current_time: Timestamp,
    ) -> IbcResult<Self> {
        // Validate assets
        initiator_asset.validate()?;
        responder_asset.validate()?;

        // Generate secret
        let mut secret = vec![0u8; SECRET_LENGTH];
        use rand::RngCore;
        rand::thread_rng().fill_bytes(&mut secret);

        let hashlock = Hashlock::from_secret(&secret);
        let initiator_timelock =
            Timelock::from_duration(current_time, DEFAULT_INITIATOR_TIMELOCK);

        let initiator_htlc = Htlc::new(
            initiator,
            responder,
            initiator_asset,
            hashlock,
            initiator_timelock,
            current_time,
        );

        Ok(Self {
            swap_id: initiator_htlc.swap_id.clone(),
            initiator_htlc,
            responder_htlc: None,
            state: SwapState::Pending,
            secret,
        })
    }

    /// Get the hashlock for sharing with counterparty
    pub fn hashlock(&self) -> &Hashlock {
        &self.initiator_htlc.hashlock
    }

    /// Lock initiator's side of the swap
    pub fn lock_initiator(&mut self) -> IbcResult<()> {
        self.initiator_htlc.lock()?;
        self.state = SwapState::Locked;
        Ok(())
    }

    /// Create responder's HTLC (called after seeing initiator's lock)
    pub fn create_responder_htlc(
        &mut self,
        responder_asset: SwapAsset,
        current_time: Timestamp,
    ) -> IbcResult<&Htlc> {
        if self.state != SwapState::Locked {
            return Err(IbcError::InvalidChannelState {
                expected: "locked".to_string(),
                actual: self.state.to_string(),
            });
        }

        responder_asset.validate()?;

        // Responder's timelock must be shorter than initiator's
        let responder_timelock =
            Timelock::from_duration(current_time, DEFAULT_RESPONDER_TIMELOCK);

        // Verify responder's timelock expires before initiator's
        if responder_timelock.expiry >= self.initiator_htlc.timelock.expiry {
            return Err(IbcError::InvalidPacketData(
                "responder timelock must expire before initiator's".to_string(),
            ));
        }

        let responder_htlc = Htlc::new(
            &self.initiator_htlc.responder,
            &self.initiator_htlc.initiator,
            responder_asset,
            self.initiator_htlc.hashlock.clone(),
            responder_timelock,
            current_time,
        );

        self.responder_htlc = Some(responder_htlc);
        Ok(self.responder_htlc.as_ref().unwrap())
    }

    /// Lock responder's side
    pub fn lock_responder(&mut self) -> IbcResult<()> {
        if let Some(ref mut htlc) = self.responder_htlc {
            htlc.lock()
        } else {
            Err(IbcError::InvalidChannelState {
                expected: "responder HTLC created".to_string(),
                actual: "no responder HTLC".to_string(),
            })
        }
    }

    /// Initiator claims responder's tokens (reveals secret)
    pub fn claim_responder(&mut self, current_time: Timestamp) -> IbcResult<Vec<u8>> {
        if let Some(ref mut htlc) = self.responder_htlc {
            htlc.claim(&self.secret, current_time)?;
            // Return secret so responder can see it
            Ok(self.secret.clone())
        } else {
            Err(IbcError::InvalidChannelState {
                expected: "responder HTLC exists".to_string(),
                actual: "no responder HTLC".to_string(),
            })
        }
    }

    /// Responder claims initiator's tokens using revealed secret
    pub fn claim_initiator(&mut self, secret: &[u8], current_time: Timestamp) -> IbcResult<()> {
        self.initiator_htlc.claim(secret, current_time)?;
        self.state = SwapState::Completed;
        Ok(())
    }

    /// Check and update expired status
    pub fn check_expired(&mut self, current_time: Timestamp) {
        if self.initiator_htlc.timelock.is_expired(current_time)
            && self.initiator_htlc.state == SwapState::Locked
        {
            self.initiator_htlc.state = SwapState::Expired;
        }

        if let Some(ref mut htlc) = self.responder_htlc {
            if htlc.timelock.is_expired(current_time) && htlc.state == SwapState::Locked {
                htlc.state = SwapState::Expired;
            }
        }

        // If both expired, overall state is expired
        if self.initiator_htlc.state == SwapState::Expired {
            self.state = SwapState::Expired;
        }
    }
}

// ============================================================================
// Swap Manager
// ============================================================================

/// Manages atomic swaps
pub struct SwapManager {
    /// Active swaps by ID
    swaps: HashMap<SwapId, AtomicSwap>,
    /// HTLCs by swap ID (for single-side tracking)
    htlcs: HashMap<SwapId, Htlc>,
    /// Swaps by participant address
    by_participant: HashMap<String, Vec<SwapId>>,
}

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

impl SwapManager {
    pub fn new() -> Self {
        Self {
            swaps: HashMap::new(),
            htlcs: HashMap::new(),
            by_participant: HashMap::new(),
        }
    }

    /// Initiate a new atomic swap
    pub fn initiate_swap(
        &mut self,
        initiator: impl Into<String>,
        responder: impl Into<String>,
        initiator_asset: SwapAsset,
        responder_asset: SwapAsset,
        current_time: Timestamp,
    ) -> IbcResult<SwapId> {
        let initiator = initiator.into();
        let responder = responder.into();

        let swap = AtomicSwap::new(
            &initiator,
            &responder,
            initiator_asset,
            responder_asset,
            current_time,
        )?;

        let swap_id = swap.swap_id.clone();

        // Index by participants
        self.by_participant
            .entry(initiator)
            .or_default()
            .push(swap_id.clone());
        self.by_participant
            .entry(responder)
            .or_default()
            .push(swap_id.clone());

        self.swaps.insert(swap_id.clone(), swap);

        tracing::info!(swap_id = %swap_id, "Atomic swap initiated");

        Ok(swap_id)
    }

    /// Get a swap by ID
    pub fn get_swap(&self, swap_id: &SwapId) -> Option<&AtomicSwap> {
        self.swaps.get(swap_id)
    }

    /// Get mutable swap
    pub fn get_swap_mut(&mut self, swap_id: &SwapId) -> Option<&mut AtomicSwap> {
        self.swaps.get_mut(swap_id)
    }

    /// Lock initiator's side of a swap
    pub fn lock_swap(&mut self, swap_id: &SwapId) -> IbcResult<()> {
        let swap = self
            .swaps
            .get_mut(swap_id)
            .ok_or_else(|| IbcError::SwapNotFound(swap_id.to_string()))?;
        swap.lock_initiator()?;
        tracing::info!(swap_id = %swap_id, "Initiator locked swap");
        Ok(())
    }

    /// Create and lock responder's HTLC
    pub fn respond_to_swap(
        &mut self,
        swap_id: &SwapId,
        responder_asset: SwapAsset,
        current_time: Timestamp,
    ) -> IbcResult<()> {
        let swap = self
            .swaps
            .get_mut(swap_id)
            .ok_or_else(|| IbcError::SwapNotFound(swap_id.to_string()))?;

        swap.create_responder_htlc(responder_asset, current_time)?;
        swap.lock_responder()?;

        tracing::info!(swap_id = %swap_id, "Responder locked swap");
        Ok(())
    }

    /// Complete swap - initiator claims responder's tokens
    pub fn initiator_claim(
        &mut self,
        swap_id: &SwapId,
        current_time: Timestamp,
    ) -> IbcResult<Vec<u8>> {
        let swap = self
            .swaps
            .get_mut(swap_id)
            .ok_or_else(|| IbcError::SwapNotFound(swap_id.to_string()))?;

        let secret = swap.claim_responder(current_time)?;
        tracing::info!(swap_id = %swap_id, "Initiator claimed responder's tokens");
        Ok(secret)
    }

    /// Complete swap - responder claims initiator's tokens
    pub fn responder_claim(
        &mut self,
        swap_id: &SwapId,
        secret: &[u8],
        current_time: Timestamp,
    ) -> IbcResult<()> {
        let swap = self
            .swaps
            .get_mut(swap_id)
            .ok_or_else(|| IbcError::SwapNotFound(swap_id.to_string()))?;

        swap.claim_initiator(secret, current_time)?;
        tracing::info!(swap_id = %swap_id, "Responder claimed initiator's tokens - swap complete");
        Ok(())
    }

    /// Refund expired swap
    pub fn refund_swap(&mut self, swap_id: &SwapId, current_time: Timestamp) -> IbcResult<()> {
        let swap = self
            .swaps
            .get_mut(swap_id)
            .ok_or_else(|| IbcError::SwapNotFound(swap_id.to_string()))?;

        swap.check_expired(current_time);
        swap.initiator_htlc.refund(current_time)?;

        if let Some(ref mut htlc) = swap.responder_htlc {
            if htlc.state == SwapState::Expired || htlc.state == SwapState::Locked {
                let _ = htlc.refund(current_time);
            }
        }

        swap.state = SwapState::Refunded;
        tracing::info!(swap_id = %swap_id, "Swap refunded");
        Ok(())
    }

    /// Get swaps for a participant
    pub fn get_swaps_for_participant(&self, address: &str) -> Vec<&AtomicSwap> {
        self.by_participant
            .get(address)
            .map(|ids| ids.iter().filter_map(|id| self.swaps.get(id)).collect())
            .unwrap_or_default()
    }

    /// Get active (non-finalized) swaps
    pub fn get_active_swaps(&self) -> Vec<&AtomicSwap> {
        self.swaps
            .values()
            .filter(|s| !s.initiator_htlc.is_finalized())
            .collect()
    }

    /// Store a single HTLC (for incoming cross-chain swaps)
    pub fn store_htlc(&mut self, htlc: Htlc) {
        let swap_id = htlc.swap_id.clone();
        self.by_participant
            .entry(htlc.initiator.clone())
            .or_default()
            .push(swap_id.clone());
        self.by_participant
            .entry(htlc.responder.clone())
            .or_default()
            .push(swap_id.clone());
        self.htlcs.insert(swap_id, htlc);
    }

    /// Get HTLC by ID
    pub fn get_htlc(&self, swap_id: &SwapId) -> Option<&Htlc> {
        self.htlcs.get(swap_id)
    }

    /// Claim an HTLC with secret
    pub fn claim_htlc(
        &mut self,
        swap_id: &SwapId,
        secret: &[u8],
        current_time: Timestamp,
    ) -> IbcResult<()> {
        let htlc = self
            .htlcs
            .get_mut(swap_id)
            .ok_or_else(|| IbcError::SwapNotFound(swap_id.to_string()))?;
        htlc.claim(secret, current_time)
    }

    /// Refund an HTLC after timeout
    pub fn refund_htlc(&mut self, swap_id: &SwapId, current_time: Timestamp) -> IbcResult<()> {
        let htlc = self
            .htlcs
            .get_mut(swap_id)
            .ok_or_else(|| IbcError::SwapNotFound(swap_id.to_string()))?;
        htlc.refund(current_time)
    }
}

// ============================================================================
// IBC Swap Packet
// ============================================================================

/// Packet data for cross-chain atomic swap
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SwapPacketData {
    /// Swap ID (shared between chains)
    pub swap_id: SwapId,
    /// Action type
    pub action: SwapAction,
    /// Initiator address on source chain
    pub initiator: String,
    /// Responder address on destination chain
    pub responder: String,
    /// Asset details
    pub asset: SwapAsset,
    /// Hashlock
    pub hashlock: Hashlock,
    /// Timelock expiry (nanoseconds)
    pub timelock_expiry: u64,
    /// Secret (only for claim action)
    pub secret: Option<Vec<u8>>,
}

/// Swap packet actions
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SwapAction {
    /// Initiate swap (lock tokens)
    Initiate,
    /// Respond to swap (lock counterparty tokens)
    Respond,
    /// Claim tokens with secret
    Claim,
    /// Refund after timeout
    Refund,
}

impl SwapPacketData {
    /// Create initiate packet
    pub fn initiate(
        swap_id: SwapId,
        initiator: String,
        responder: String,
        asset: SwapAsset,
        hashlock: Hashlock,
        timelock_expiry: u64,
    ) -> Self {
        Self {
            swap_id,
            action: SwapAction::Initiate,
            initiator,
            responder,
            asset,
            hashlock,
            timelock_expiry,
            secret: None,
        }
    }

    /// Create claim packet
    pub fn claim(swap_id: SwapId, secret: Vec<u8>) -> Self {
        Self {
            swap_id,
            action: SwapAction::Claim,
            initiator: String::new(),
            responder: String::new(),
            asset: SwapAsset::Native { amount: 0 },
            hashlock: Hashlock(Vec::new()),
            timelock_expiry: 0,
            secret: Some(secret),
        }
    }

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

    /// Deserialize from JSON bytes
    pub fn from_bytes(data: &[u8]) -> IbcResult<Self> {
        serde_json::from_slice(data)
            .map_err(|e| IbcError::InvalidPacketData(format!("invalid swap packet: {}", e)))
    }
}

// ============================================================================
// Tests
// ============================================================================

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

    fn test_timestamp() -> Timestamp {
        Timestamp::from_nanoseconds(1_700_000_000_000_000_000)
    }

    fn later_timestamp(secs: u64) -> Timestamp {
        Timestamp::from_nanoseconds(1_700_000_000_000_000_000 + secs * 1_000_000_000)
    }

    #[test]
    fn test_hashlock_verification() {
        let secret = b"my_secret_preimage_32bytes_here!";
        let hashlock = Hashlock::from_secret(secret);

        assert!(hashlock.verify(secret));
        assert!(!hashlock.verify(b"wrong_secret"));
    }

    #[test]
    fn test_timelock() {
        let current = test_timestamp();
        let timelock = Timelock::from_duration(current, 3600);

        assert!(!timelock.is_expired(current));
        assert!(!timelock.is_expired(later_timestamp(1800)));
        assert!(timelock.is_expired(later_timestamp(3600)));
        assert!(timelock.is_expired(later_timestamp(7200)));
    }

    #[test]
    fn test_htlc_lifecycle() {
        let secret = b"my_secret_preimage_32bytes_here!";
        let hashlock = Hashlock::from_secret(secret);
        let current = test_timestamp();
        let timelock = Timelock::from_duration(current, 3600);

        let mut htlc = Htlc::new(
            "alice",
            "bob",
            SwapAsset::native(1000),
            hashlock,
            timelock,
            current,
        );

        assert_eq!(htlc.state, SwapState::Pending);

        // Lock
        htlc.lock().unwrap();
        assert_eq!(htlc.state, SwapState::Locked);

        // Claim with correct secret
        htlc.claim(secret, later_timestamp(1800)).unwrap();
        assert_eq!(htlc.state, SwapState::Completed);
        assert_eq!(htlc.secret, Some(secret.to_vec()));
    }

    #[test]
    fn test_htlc_refund() {
        let secret = b"my_secret_preimage_32bytes_here!";
        let hashlock = Hashlock::from_secret(secret);
        let current = test_timestamp();
        let timelock = Timelock::from_duration(current, 3600);

        let mut htlc = Htlc::new(
            "alice",
            "bob",
            SwapAsset::native(1000),
            hashlock,
            timelock,
            current,
        );

        htlc.lock().unwrap();

        // Cannot refund before timeout
        assert!(htlc.refund(later_timestamp(1800)).is_err());

        // Can refund after timeout
        htlc.refund(later_timestamp(3601)).unwrap();
        assert_eq!(htlc.state, SwapState::Refunded);
    }

    #[test]
    fn test_atomic_swap_full_flow() {
        let current = test_timestamp();

        // Alice initiates swap
        let mut swap = AtomicSwap::new(
            "alice",
            "bob",
            SwapAsset::native(1000),
            SwapAsset::ics20("uatom", 500),
            current,
        )
        .unwrap();

        // Lock Alice's side
        swap.lock_initiator().unwrap();
        assert_eq!(swap.state, SwapState::Locked);

        // Bob responds
        swap.create_responder_htlc(SwapAsset::ics20("uatom", 500), later_timestamp(100))
            .unwrap();
        swap.lock_responder().unwrap();

        // Alice claims Bob's tokens (reveals secret)
        let revealed_secret = swap.claim_responder(later_timestamp(200)).unwrap();

        // Bob uses revealed secret to claim Alice's tokens
        swap.claim_initiator(&revealed_secret, later_timestamp(300))
            .unwrap();

        assert_eq!(swap.state, SwapState::Completed);
    }

    #[test]
    fn test_swap_manager() {
        let mut manager = SwapManager::new();
        let current = test_timestamp();

        // Initiate swap
        let swap_id = manager
            .initiate_swap(
                "alice",
                "bob",
                SwapAsset::native(1000),
                SwapAsset::ics20("uatom", 500),
                current,
            )
            .unwrap();

        // Lock
        manager.lock_swap(&swap_id).unwrap();

        // Respond
        manager
            .respond_to_swap(&swap_id, SwapAsset::ics20("uatom", 500), later_timestamp(100))
            .unwrap();

        // Initiator claims
        let secret = manager.initiator_claim(&swap_id, later_timestamp(200)).unwrap();

        // Responder claims
        manager
            .responder_claim(&swap_id, &secret, later_timestamp(300))
            .unwrap();

        let swap = manager.get_swap(&swap_id).unwrap();
        assert_eq!(swap.state, SwapState::Completed);
    }
}
