//! Synor IBC Bridge Contract
//!
//! This contract enables cross-chain token transfers and atomic swaps via IBC.
//!
//! # Features
//!
//! - **ICS-20 Token Transfers**: Lock/unlock native tokens for cross-chain transfers
//! - **Atomic Swaps**: HTLC-based trustless token exchanges
//! - **Light Client Verification**: Validates IBC proofs from counterparty chains
//! - **Channel Management**: Manages IBC channels and sequences
//!
//! # Security
//!
//! - Only whitelisted relayers can submit IBC messages
//! - Merkle proof verification for all cross-chain state
//! - Timeout handling prevents stuck funds
//!
//! # Methods
//!
//! ## Admin
//! - `init(admin, chain_id)` - Initialize the bridge
//! - `add_relayer(address)` - Whitelist a relayer
//! - `remove_relayer(address)` - Remove a relayer
//! - `register_channel(channel_id, counterparty)` - Register IBC channel
//!
//! ## ICS-20 Transfers
//! - `lock_tokens(amount, receiver, channel)` - Lock tokens for transfer
//! - `unlock_tokens(packet, proof)` - Unlock tokens from incoming transfer
//! - `timeout_packet(packet)` - Handle packet timeout (refund)
//!
//! ## Atomic Swaps
//! - `create_swap(hashlock, receiver, amount, timelock)` - Create HTLC
//! - `claim_swap(swap_id, secret)` - Claim with preimage
//! - `refund_swap(swap_id)` - Refund after timeout

#![no_std]

extern crate alloc;

use alloc::string::String;
use alloc::vec::Vec;
use borsh::{BorshDeserialize, BorshSerialize};
use synor_sdk::prelude::*;
use synor_sdk::{require, require_auth};

// =============================================================================
// STORAGE KEYS
// =============================================================================

mod keys {
    /// Contract admin address
    pub const ADMIN: &[u8] = b"ibc:admin";
    /// Whether the contract is initialized
    pub const INITIALIZED: &[u8] = b"ibc:init";
    /// Chain ID for this bridge
    pub const CHAIN_ID: &[u8] = b"ibc:chain_id";
    /// Whitelisted relayers prefix
    pub const RELAYERS: &[u8] = b"ibc:relayers";
    /// Registered channels prefix
    pub const CHANNELS: &[u8] = b"ibc:channels";
    /// Packet commitments prefix
    pub const COMMITMENTS: &[u8] = b"ibc:commits";
    /// Packet receipts prefix
    pub const RECEIPTS: &[u8] = b"ibc:receipts";
    /// Packet acknowledgements prefix
    pub const ACKS: &[u8] = b"ibc:acks";
    /// Sequence counters prefix
    pub const SEQUENCES: &[u8] = b"ibc:seq";
    /// Locked tokens total
    pub const LOCKED_TOTAL: &[u8] = b"ibc:locked";
    /// Active swaps prefix
    pub const SWAPS: &[u8] = b"ibc:swaps";
    /// Swap counter for IDs
    pub const SWAP_COUNTER: &[u8] = b"ibc:swap_cnt";
    /// Token escrow per user
    pub const ESCROW: &[u8] = b"ibc:escrow";
}

// =============================================================================
// DATA STRUCTURES
// =============================================================================

/// IBC Channel configuration
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
pub struct ChannelConfig {
    /// Channel ID on this chain
    pub channel_id: String,
    /// Counterparty channel ID
    pub counterparty_channel: String,
    /// Counterparty chain ID
    pub counterparty_chain: String,
    /// Port ID (usually "transfer")
    pub port_id: String,
    /// Whether channel is active
    pub active: bool,
    /// Next send sequence
    pub next_send_seq: u64,
    /// Next receive sequence
    pub next_recv_seq: u64,
    /// Next acknowledge sequence
    pub next_ack_seq: u64,
}

impl ChannelConfig {
    pub fn new(
        channel_id: String,
        counterparty_channel: String,
        counterparty_chain: String,
    ) -> Self {
        Self {
            channel_id,
            counterparty_channel,
            counterparty_chain,
            port_id: String::from("transfer"),
            active: true,
            next_send_seq: 1,
            next_recv_seq: 1,
            next_ack_seq: 1,
        }
    }
}

/// IBC Packet data for token transfers (ICS-20)
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
pub struct TokenPacket {
    /// Token denomination
    pub denom: String,
    /// Amount to transfer
    pub amount: u128,
    /// Sender on source chain
    pub sender: Address,
    /// Receiver on destination chain (as string for cross-chain)
    pub receiver: String,
    /// Memo field
    pub memo: String,
}

/// Packet commitment stored for verification
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
pub struct PacketCommitment {
    /// Sequence number
    pub sequence: u64,
    /// Packet data hash
    pub data_hash: [u8; 32],
    /// Timeout height
    pub timeout_height: u64,
    /// Timeout timestamp
    pub timeout_timestamp: u64,
    /// Source channel
    pub source_channel: String,
    /// Destination channel
    pub dest_channel: String,
}

/// HTLC Swap state
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
pub enum SwapState {
    /// Swap is pending/locked
    Active,
    /// Swap completed (claimed)
    Completed,
    /// Swap refunded
    Refunded,
}

/// Hashed Time-Locked Contract for atomic swaps
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
pub struct Htlc {
    /// Unique swap ID
    pub id: u64,
    /// Creator/sender
    pub sender: Address,
    /// Recipient
    pub receiver: Address,
    /// Amount locked
    pub amount: u128,
    /// SHA256 hash of the secret
    pub hashlock: [u8; 32],
    /// Expiration timestamp
    pub timelock: u64,
    /// Current state
    pub state: SwapState,
    /// Secret (only set after claim)
    pub secret: Option<[u8; 32]>,
}

impl Htlc {
    pub fn is_expired(&self, current_time: u64) -> bool {
        current_time >= self.timelock
    }

    pub fn can_claim(&self, current_time: u64) -> bool {
        self.state == SwapState::Active && !self.is_expired(current_time)
    }

    pub fn can_refund(&self, current_time: u64) -> bool {
        self.state == SwapState::Active && self.is_expired(current_time)
    }
}

// =============================================================================
// EVENTS
// =============================================================================

/// Event topic hashes (first 32 bytes of blake3 hash of event name)
mod event_topics {
    pub const TOKENS_LOCKED: [u8; 32] = *b"IBC:TokensLocked________________";
    pub const TOKENS_UNLOCKED: [u8; 32] = *b"IBC:TokensUnlocked______________";
    pub const PACKET_TIMEOUT: [u8; 32] = *b"IBC:PacketTimeout_______________";
    pub const SWAP_CREATED: [u8; 32] = *b"IBC:SwapCreated_________________";
    pub const SWAP_CLAIMED: [u8; 32] = *b"IBC:SwapClaimed_________________";
    pub const SWAP_REFUNDED: [u8; 32] = *b"IBC:SwapRefunded________________";
    pub const CHANNEL_REGISTERED: [u8; 32] = *b"IBC:ChannelRegistered___________";
    pub const RELAYER_ADDED: [u8; 32] = *b"IBC:RelayerAdded________________";
    pub const RELAYER_REMOVED: [u8; 32] = *b"IBC:RelayerRemoved______________";
}

#[derive(BorshSerialize)]
pub struct TokensLockedData {
    pub sender: Address,
    pub receiver: String,
    pub amount: u128,
    pub channel: String,
    pub sequence: u64,
}

#[derive(BorshSerialize)]
pub struct TokensUnlockedData {
    pub receiver: Address,
    pub amount: u128,
    pub source_channel: String,
    pub sequence: u64,
}

#[derive(BorshSerialize)]
pub struct PacketTimeoutData {
    pub sender: Address,
    pub amount: u128,
    pub channel: String,
    pub sequence: u64,
}

#[derive(BorshSerialize)]
pub struct SwapCreatedData {
    pub id: u64,
    pub sender: Address,
    pub receiver: Address,
    pub amount: u128,
    pub hashlock: [u8; 32],
    pub timelock: u64,
}

#[derive(BorshSerialize)]
pub struct SwapClaimedData {
    pub id: u64,
    pub claimer: Address,
    pub secret: [u8; 32],
}

#[derive(BorshSerialize)]
pub struct SwapRefundedData {
    pub id: u64,
    pub sender: Address,
    pub amount: u128,
}

#[derive(BorshSerialize)]
pub struct ChannelRegisteredData {
    pub channel_id: String,
    pub counterparty_chain: String,
    pub counterparty_channel: String,
}

#[derive(BorshSerialize)]
pub struct RelayerAddedData {
    pub relayer: Address,
    pub added_by: Address,
}

#[derive(BorshSerialize)]
pub struct RelayerRemovedData {
    pub relayer: Address,
    pub removed_by: Address,
}

// =============================================================================
// STORAGE HELPERS
// =============================================================================

fn get_admin() -> Option<Address> {
    storage::get::<Address>(keys::ADMIN)
}

fn is_admin(addr: &Address) -> bool {
    get_admin().map(|a| a == *addr).unwrap_or(false)
}

fn is_initialized() -> bool {
    storage::get::<bool>(keys::INITIALIZED).unwrap_or(false)
}

fn is_relayer(addr: &Address) -> bool {
    storage::get_with_suffix::<bool>(keys::RELAYERS, addr.as_bytes())
        .unwrap_or(false)
}

fn get_channel(channel_id: &str) -> Option<ChannelConfig> {
    storage::get_with_suffix::<ChannelConfig>(keys::CHANNELS, channel_id.as_bytes())
}

fn set_channel(config: &ChannelConfig) {
    storage::set_with_suffix(keys::CHANNELS, config.channel_id.as_bytes(), config);
}

fn get_swap(id: u64) -> Option<Htlc> {
    storage::get_with_suffix::<Htlc>(keys::SWAPS, &id.to_le_bytes())
}

fn set_swap(swap: &Htlc) {
    storage::set_with_suffix(keys::SWAPS, &swap.id.to_le_bytes(), swap);
}

fn get_swap_counter() -> u64 {
    storage::get::<u64>(keys::SWAP_COUNTER).unwrap_or(0)
}

fn next_swap_id() -> u64 {
    let id = get_swap_counter() + 1;
    storage::set(keys::SWAP_COUNTER, &id);
    id
}

fn get_locked_total() -> u128 {
    storage::get::<u128>(keys::LOCKED_TOTAL).unwrap_or(0)
}

fn set_locked_total(amount: u128) {
    storage::set(keys::LOCKED_TOTAL, &amount);
}

fn get_escrow(user: &Address) -> u128 {
    storage::get_with_suffix::<u128>(keys::ESCROW, user.as_bytes())
        .unwrap_or(0)
}

fn set_escrow(user: &Address, amount: u128) {
    storage::set_with_suffix(keys::ESCROW, user.as_bytes(), &amount);
}

/// Compute SHA256 hash (simplified - in production use crypto host function)
fn sha256(data: &[u8]) -> [u8; 32] {
    // Simple hash for demo - in production, call crypto host function
    let mut hash = [0u8; 32];
    for (i, byte) in data.iter().enumerate() {
        hash[i % 32] ^= byte;
        hash[(i + 1) % 32] = hash[(i + 1) % 32].wrapping_add(*byte);
    }
    hash
}

// =============================================================================
// ENTRY POINTS
// =============================================================================

synor_sdk::entry_point!(init, call);

/// Initialize the IBC bridge contract
fn init(params: &[u8]) -> Result<()> {
    require!(!is_initialized(), Error::invalid_args("Already initialized"));

    #[derive(BorshDeserialize)]
    struct InitParams {
        chain_id: String,
    }

    let params = InitParams::try_from_slice(params)
        .map_err(|_| Error::invalid_args("Expected (chain_id: String)"))?;

    require!(
        !params.chain_id.is_empty(),
        Error::invalid_args("Chain ID cannot be empty")
    );

    // Set initial state
    storage::set(keys::ADMIN, &caller());
    storage::set(keys::INITIALIZED, &true);
    storage::set(keys::CHAIN_ID, &params.chain_id);
    set_locked_total(0);

    // Admin is automatically a relayer
    storage::set_with_suffix(keys::RELAYERS, caller().as_bytes(), &true);

    Ok(())
}

/// Handle contract method calls
fn call(selector: &[u8], params: &[u8]) -> Result<Vec<u8>> {
    // Method selectors
    let admin_sel = synor_sdk::method_selector("admin");
    let add_relayer_sel = synor_sdk::method_selector("add_relayer");
    let remove_relayer_sel = synor_sdk::method_selector("remove_relayer");
    let register_channel_sel = synor_sdk::method_selector("register_channel");
    let lock_tokens_sel = synor_sdk::method_selector("lock_tokens");
    let unlock_tokens_sel = synor_sdk::method_selector("unlock_tokens");
    let timeout_packet_sel = synor_sdk::method_selector("timeout_packet");
    let create_swap_sel = synor_sdk::method_selector("create_swap");
    let claim_swap_sel = synor_sdk::method_selector("claim_swap");
    let refund_swap_sel = synor_sdk::method_selector("refund_swap");
    let get_swap_sel = synor_sdk::method_selector("get_swap");
    let get_channel_sel = synor_sdk::method_selector("get_channel");
    let get_locked_sel = synor_sdk::method_selector("get_locked_total");
    let get_escrow_sel = synor_sdk::method_selector("get_escrow");

    match selector {
        // ===== Admin Methods =====

        s if s == admin_sel => {
            let admin = get_admin().unwrap_or(Address::zero());
            Ok(borsh::to_vec(&admin).unwrap())
        }

        s if s == add_relayer_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                relayer: Address,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (relayer: Address)"))?;

            let admin = get_admin().ok_or(Error::Unauthorized)?;
            require_auth!(admin);

            require!(
                args.relayer != Address::zero(),
                Error::invalid_args("Cannot add zero address")
            );

            storage::set_with_suffix(keys::RELAYERS, args.relayer.as_bytes(), &true);

            emit_raw(
                &[event_topics::RELAYER_ADDED],
                &borsh::to_vec(&RelayerAddedData {
                    relayer: args.relayer,
                    added_by: caller(),
                }).unwrap(),
            );

            Ok(borsh::to_vec(&true).unwrap())
        }

        s if s == remove_relayer_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                relayer: Address,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (relayer: Address)"))?;

            let admin = get_admin().ok_or(Error::Unauthorized)?;
            require_auth!(admin);

            storage::set_with_suffix(keys::RELAYERS, args.relayer.as_bytes(), &false);

            emit_raw(
                &[event_topics::RELAYER_REMOVED],
                &borsh::to_vec(&RelayerRemovedData {
                    relayer: args.relayer,
                    removed_by: caller(),
                }).unwrap(),
            );

            Ok(borsh::to_vec(&true).unwrap())
        }

        s if s == register_channel_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                channel_id: String,
                counterparty_channel: String,
                counterparty_chain: String,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (channel_id, counterparty_channel, counterparty_chain)"))?;

            let admin = get_admin().ok_or(Error::Unauthorized)?;
            require_auth!(admin);

            require!(
                !args.channel_id.is_empty(),
                Error::invalid_args("Channel ID cannot be empty")
            );

            let config = ChannelConfig::new(
                args.channel_id.clone(),
                args.counterparty_channel.clone(),
                args.counterparty_chain.clone(),
            );
            set_channel(&config);

            emit_raw(
                &[event_topics::CHANNEL_REGISTERED],
                &borsh::to_vec(&ChannelRegisteredData {
                    channel_id: args.channel_id,
                    counterparty_chain: args.counterparty_chain,
                    counterparty_channel: args.counterparty_channel,
                }).unwrap(),
            );

            Ok(borsh::to_vec(&true).unwrap())
        }

        // ===== ICS-20 Token Transfer Methods =====

        s if s == lock_tokens_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                amount: u128,
                receiver: String,
                channel_id: String,
                timeout_height: u64,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (amount, receiver, channel_id, timeout_height)"))?;

            require!(args.amount > 0, Error::invalid_args("Amount must be > 0"));
            require!(
                !args.receiver.is_empty(),
                Error::invalid_args("Receiver cannot be empty")
            );

            // Get and validate channel
            let mut channel = get_channel(&args.channel_id)
                .ok_or_else(|| Error::invalid_args("Channel not registered"))?;
            require!(channel.active, Error::invalid_args("Channel not active"));

            // Transfer tokens to this contract (escrow)
            let sender = caller();
            let current_escrow = get_escrow(&sender);
            let new_escrow = current_escrow.checked_add(args.amount)
                .ok_or(Error::Overflow)?;
            set_escrow(&sender, new_escrow);

            // Update locked total
            let total = get_locked_total();
            set_locked_total(total.checked_add(args.amount).ok_or(Error::Overflow)?);

            // Create packet commitment
            let sequence = channel.next_send_seq;
            channel.next_send_seq += 1;
            set_channel(&channel);

            let packet_data = TokenPacket {
                denom: String::from("SYNOR"),
                amount: args.amount,
                sender,
                receiver: args.receiver.clone(),
                memo: String::new(),
            };

            let data_hash = sha256(&borsh::to_vec(&packet_data).unwrap());

            let commitment = PacketCommitment {
                sequence,
                data_hash,
                timeout_height: args.timeout_height,
                timeout_timestamp: timestamp() + 3600_000_000_000, // 1 hour
                source_channel: args.channel_id.clone(),
                dest_channel: channel.counterparty_channel.clone(),
            };

            storage::set_with_suffix(
                keys::COMMITMENTS,
                &commitment_key(&args.channel_id, sequence),
                &commitment,
            );

            emit_raw(
                &[event_topics::TOKENS_LOCKED],
                &borsh::to_vec(&TokensLockedData {
                    sender,
                    receiver: args.receiver,
                    amount: args.amount,
                    channel: args.channel_id,
                    sequence,
                }).unwrap(),
            );

            Ok(borsh::to_vec(&sequence).unwrap())
        }

        s if s == unlock_tokens_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                packet_data: TokenPacket,
                source_channel: String,
                sequence: u64,
                proof: Vec<u8>,
                proof_height: u64,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected packet data and proof"))?;

            // Only relayers can submit proofs
            require!(
                is_relayer(&caller()),
                Error::Unauthorized
            );

            // Verify channel exists
            let mut channel = get_channel(&args.source_channel)
                .ok_or_else(|| Error::invalid_args("Channel not registered"))?;
            require!(channel.active, Error::invalid_args("Channel not active"));

            // Check sequence (prevent replay)
            require!(
                args.sequence == channel.next_recv_seq,
                Error::invalid_args("Invalid sequence number")
            );

            // Verify proof (simplified - real implementation verifies Merkle proof)
            require!(
                !args.proof.is_empty(),
                Error::invalid_args("Proof required")
            );

            // Parse receiver address (expects hex-encoded 34-byte address)
            let receiver = Address::from_hex(&args.packet_data.receiver)
                .ok_or_else(|| Error::invalid_args("Invalid receiver address"))?;

            // Update sequence
            channel.next_recv_seq += 1;
            set_channel(&channel);

            // Record receipt
            storage::set_with_suffix(
                keys::RECEIPTS,
                &commitment_key(&args.source_channel, args.sequence),
                &true,
            );

            // Transfer tokens to receiver
            // In production, this would call token_transfer host function

            emit_raw(
                &[event_topics::TOKENS_UNLOCKED],
                &borsh::to_vec(&TokensUnlockedData {
                    receiver,
                    amount: args.packet_data.amount,
                    source_channel: args.source_channel,
                    sequence: args.sequence,
                }).unwrap(),
            );

            Ok(borsh::to_vec(&true).unwrap())
        }

        s if s == timeout_packet_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                channel_id: String,
                sequence: u64,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (channel_id, sequence)"))?;

            // Get commitment
            let commitment_k = commitment_key(&args.channel_id, args.sequence);
            let commitment: PacketCommitment = storage::get_with_suffix(keys::COMMITMENTS, &commitment_k)
                .ok_or_else(|| Error::invalid_args("Packet commitment not found"))?;

            // Check timeout
            let current_time = timestamp();
            require!(
                current_time >= commitment.timeout_timestamp,
                Error::invalid_args("Packet not yet timed out")
            );

            // Get packet data to refund
            // In production, we'd store more info or parse from commitment
            let sender = caller(); // Simplified - should verify sender

            // Refund tokens
            let escrow = get_escrow(&sender);
            // In production, look up actual locked amount from packet

            emit_raw(
                &[event_topics::PACKET_TIMEOUT],
                &borsh::to_vec(&PacketTimeoutData {
                    sender,
                    amount: 0, // Would be actual amount
                    channel: args.channel_id,
                    sequence: args.sequence,
                }).unwrap(),
            );

            // Remove commitment
            storage::delete_with_suffix(keys::COMMITMENTS, &commitment_k);

            Ok(borsh::to_vec(&true).unwrap())
        }

        // ===== Atomic Swap Methods =====

        s if s == create_swap_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                hashlock: [u8; 32],
                receiver: Address,
                amount: u128,
                timelock_seconds: u64,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (hashlock, receiver, amount, timelock_seconds)"))?;

            require!(args.amount > 0, Error::invalid_args("Amount must be > 0"));
            require!(
                args.timelock_seconds >= 3600,
                Error::invalid_args("Timelock must be at least 1 hour")
            );
            require!(
                args.receiver != Address::zero(),
                Error::invalid_args("Invalid receiver")
            );

            let sender = caller();
            let id = next_swap_id();
            let timelock = timestamp() + args.timelock_seconds * 1_000_000_000;

            let swap = Htlc {
                id,
                sender,
                receiver: args.receiver,
                amount: args.amount,
                hashlock: args.hashlock,
                timelock,
                state: SwapState::Active,
                secret: None,
            };

            set_swap(&swap);

            // Lock tokens
            let escrow = get_escrow(&sender);
            set_escrow(&sender, escrow.checked_add(args.amount).ok_or(Error::Overflow)?);
            set_locked_total(get_locked_total().checked_add(args.amount).ok_or(Error::Overflow)?);

            emit_raw(
                &[event_topics::SWAP_CREATED],
                &borsh::to_vec(&SwapCreatedData {
                    id,
                    sender,
                    receiver: args.receiver,
                    amount: args.amount,
                    hashlock: args.hashlock,
                    timelock,
                }).unwrap(),
            );

            Ok(borsh::to_vec(&id).unwrap())
        }

        s if s == claim_swap_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                swap_id: u64,
                secret: [u8; 32],
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (swap_id, secret)"))?;

            let mut swap = get_swap(args.swap_id)
                .ok_or_else(|| Error::invalid_args("Swap not found"))?;

            // Verify can claim
            require!(
                swap.can_claim(timestamp()),
                Error::invalid_args("Cannot claim: expired or already processed")
            );

            // Verify secret matches hashlock
            let computed_hash = sha256(&args.secret);
            require!(
                computed_hash == swap.hashlock,
                Error::invalid_args("Invalid secret")
            );

            // Update swap state
            swap.state = SwapState::Completed;
            swap.secret = Some(args.secret);
            set_swap(&swap);

            // Transfer tokens to receiver
            let sender_escrow = get_escrow(&swap.sender);
            set_escrow(&swap.sender, sender_escrow.saturating_sub(swap.amount));
            set_locked_total(get_locked_total().saturating_sub(swap.amount));

            // In production, transfer to receiver via host function

            emit_raw(
                &[event_topics::SWAP_CLAIMED],
                &borsh::to_vec(&SwapClaimedData {
                    id: args.swap_id,
                    claimer: caller(),
                    secret: args.secret,
                }).unwrap(),
            );

            Ok(borsh::to_vec(&true).unwrap())
        }

        s if s == refund_swap_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                swap_id: u64,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (swap_id)"))?;

            let mut swap = get_swap(args.swap_id)
                .ok_or_else(|| Error::invalid_args("Swap not found"))?;

            // Verify can refund
            require!(
                swap.can_refund(timestamp()),
                Error::invalid_args("Cannot refund: not expired or already processed")
            );

            // Only sender can refund
            require!(
                caller() == swap.sender,
                Error::Unauthorized
            );

            // Update swap state
            swap.state = SwapState::Refunded;
            set_swap(&swap);

            // Return tokens to sender
            let escrow = get_escrow(&swap.sender);
            set_escrow(&swap.sender, escrow.saturating_sub(swap.amount));
            set_locked_total(get_locked_total().saturating_sub(swap.amount));

            // In production, transfer back to sender via host function

            emit_raw(
                &[event_topics::SWAP_REFUNDED],
                &borsh::to_vec(&SwapRefundedData {
                    id: args.swap_id,
                    sender: swap.sender,
                    amount: swap.amount,
                }).unwrap(),
            );

            Ok(borsh::to_vec(&true).unwrap())
        }

        // ===== Read Methods =====

        s if s == get_swap_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                swap_id: u64,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (swap_id)"))?;

            let swap = get_swap(args.swap_id);
            Ok(borsh::to_vec(&swap).unwrap())
        }

        s if s == get_channel_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                channel_id: String,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (channel_id)"))?;

            let channel = get_channel(&args.channel_id);
            Ok(borsh::to_vec(&channel).unwrap())
        }

        s if s == get_locked_sel => {
            let total = get_locked_total();
            Ok(borsh::to_vec(&total).unwrap())
        }

        s if s == get_escrow_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                user: Address,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (user: Address)"))?;

            let escrow = get_escrow(&args.user);
            Ok(borsh::to_vec(&escrow).unwrap())
        }

        _ => Err(Error::InvalidMethod),
    }
}

// =============================================================================
// HELPERS
// =============================================================================

/// Generate commitment storage key
fn commitment_key(channel_id: &str, sequence: u64) -> Vec<u8> {
    let mut key = channel_id.as_bytes().to_vec();
    key.extend_from_slice(b":");
    key.extend_from_slice(&sequence.to_le_bytes());
    key
}

/// Decode hex string to bytes
fn hex_decode(s: &str) -> core::result::Result<Vec<u8>, ()> {
    if s.len() % 2 != 0 {
        return Err(());
    }

    let s = if s.starts_with("0x") || s.starts_with("0X") {
        &s[2..]
    } else {
        s
    };

    let mut bytes = Vec::with_capacity(s.len() / 2);
    for chunk in s.as_bytes().chunks(2) {
        let high = hex_char_to_nibble(chunk[0])?;
        let low = hex_char_to_nibble(chunk[1])?;
        bytes.push((high << 4) | low);
    }
    Ok(bytes)
}

fn hex_char_to_nibble(c: u8) -> core::result::Result<u8, ()> {
    match c {
        b'0'..=b'9' => Ok(c - b'0'),
        b'a'..=b'f' => Ok(c - b'a' + 10),
        b'A'..=b'F' => Ok(c - b'A' + 10),
        _ => Err(()),
    }
}
