//! Synor IBC SDK Examples for Rust
//!
//! Demonstrates Inter-Blockchain Communication operations:
//! - Cross-chain token transfers
//! - Channel management
//! - Packet handling
//! - Relayer operations

use std::env;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use synor_ibc::{
    ChannelState, FeeEstimateRequest, IbcConfig, Network, RelayConfig, RelayerRegistration,
    SynorIbc, TransferRequest,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize client
    let config = IbcConfig {
        api_key: env::var("SYNOR_API_KEY").unwrap_or_else(|_| "your-api-key".to_string()),
        endpoint: "https://ibc.synor.io/v1".to_string(),
        timeout: 30000,
        retries: 3,
        debug: false,
        default_network: Network::Mainnet,
    };

    let ibc = SynorIbc::new(config)?;

    // Check service health
    let healthy = ibc.health_check().await?;
    println!("Service healthy: {}\n", healthy);

    // Run examples
    chains_example(&ibc).await?;
    channels_example(&ibc).await?;
    transfer_example(&ibc).await?;
    packet_example(&ibc).await?;
    relayer_example(&ibc).await?;
    connection_example(&ibc).await?;

    ibc.close().await?;
    Ok(())
}

async fn chains_example(ibc: &SynorIbc) -> Result<(), Box<dyn std::error::Error>> {
    println!("=== Connected Chains ===");

    // Get all connected chains
    let chains = ibc.chains.list().await?;
    println!("Connected chains: {}", chains.len());

    for chain in &chains {
        println!("  {}:", chain.chain_id);
        println!("    Name: {}", chain.name);
        println!("    Status: {}", chain.status);
        println!("    Block height: {}", chain.latest_height);
        println!("    Channels: {}", chain.channel_count);
    }

    // Get specific chain info
    let cosmos = ibc.chains.get("cosmoshub-4").await?;
    println!("\nCosmos Hub details:");
    println!("  RPC: {}", cosmos.rpc_endpoint);
    println!("  Rest: {}", cosmos.rest_endpoint);
    println!("  Native denom: {}", cosmos.native_denom);
    println!("  Prefix: {}", cosmos.bech32_prefix);

    // Get supported assets on a chain
    let assets = ibc.chains.get_assets("cosmoshub-4").await?;
    println!("\nSupported assets on Cosmos Hub:");
    for asset in assets.iter().take(5) {
        println!("  {}: {}", asset.symbol, asset.denom);
        println!("    Origin: {}", asset.origin_chain);
        println!("    Decimals: {}", asset.decimals);
    }

    // Get chain paths (routes)
    let paths = ibc.chains.get_paths("synor-1", "cosmoshub-4").await?;
    println!("\nPaths from Synor to Cosmos Hub:");
    for path in &paths {
        println!("  {} -> {}", path.source_channel, path.dest_channel);
        println!("    Hops: {}", path.hops);
        println!("    Avg time: {}s", path.avg_transfer_time);
    }

    println!();
    Ok(())
}

async fn channels_example(ibc: &SynorIbc) -> Result<(), Box<dyn std::error::Error>> {
    println!("=== Channel Management ===");

    // List all channels
    let channels = ibc.channels.list().await?;
    println!("Total channels: {}", channels.len());

    // Filter by state
    let open_channels: Vec<_> = channels
        .iter()
        .filter(|c| c.state == ChannelState::Open)
        .collect();
    println!("Open channels: {}", open_channels.len());

    for channel in open_channels.iter().take(3) {
        println!("\n  Channel {}:", channel.channel_id);
        println!("    Port: {}", channel.port_id);
        println!(
            "    Counterparty: {} on {}",
            channel.counterparty_channel_id, channel.counterparty_chain_id
        );
        println!("    Ordering: {}", channel.ordering);
        println!("    Version: {}", channel.version);
        println!("    State: {:?}", channel.state);
    }

    // Get specific channel
    let channel = ibc.channels.get("channel-0").await?;
    println!("\nChannel-0 details:");
    println!("  Connection: {}", channel.connection_id);
    println!("  Counterparty port: {}", channel.counterparty_port_id);

    // Get channel statistics
    let stats = ibc.channels.get_stats("channel-0").await?;
    println!("\nChannel-0 statistics:");
    println!("  Total packets sent: {}", stats.packets_sent);
    println!("  Total packets received: {}", stats.packets_received);
    println!("  Pending packets: {}", stats.pending_packets);
    println!("  Success rate: {:.1}%", stats.success_rate);
    println!("  Avg relay time: {}s", stats.avg_relay_time);

    // Get channel capacity
    let capacity = ibc.channels.get_capacity("channel-0").await?;
    println!("\nChannel-0 capacity:");
    println!("  Max throughput: {} packets/block", capacity.max_packets_per_block);
    println!("  Current utilization: {:.1}%", capacity.utilization);

    println!();
    Ok(())
}

async fn transfer_example(ibc: &SynorIbc) -> Result<(), Box<dyn std::error::Error>> {
    println!("=== Cross-Chain Transfers ===");

    // Estimate transfer fee
    let estimate = ibc
        .transfers
        .estimate_fee(FeeEstimateRequest {
            source_chain: "synor-1".to_string(),
            dest_chain: "cosmoshub-4".to_string(),
            denom: "usyn".to_string(),
            amount: "1000000".to_string(), // 1 SYN (6 decimals)
        })
        .await?;

    println!("Transfer fee estimate:");
    println!("  Gas: {}", estimate.gas);
    println!("  Fee: {} {}", estimate.fee, estimate.fee_denom);
    println!("  Timeout: {}s", estimate.timeout);

    // Calculate timeout timestamp (10 minutes from now)
    let timeout_timestamp = SystemTime::now()
        .duration_since(UNIX_EPOCH)?
        .as_millis() as u64
        + 600000;

    // Initiate a transfer
    println!("\nInitiating transfer...");
    let transfer = ibc
        .transfers
        .send(TransferRequest {
            source_chain: "synor-1".to_string(),
            dest_chain: "cosmoshub-4".to_string(),
            channel: "channel-0".to_string(),
            sender: "synor1abc...".to_string(),   // Your address
            receiver: "cosmos1xyz...".to_string(), // Recipient address
            denom: "usyn".to_string(),
            amount: "1000000".to_string(), // 1 SYN
            memo: Some("Cross-chain transfer example".to_string()),
            timeout_height: 0, // Use timestamp instead
            timeout_timestamp,
        })
        .await?;

    println!("Transfer initiated:");
    println!("  TX Hash: {}", transfer.tx_hash);
    println!("  Sequence: {}", transfer.sequence);
    println!("  Status: {}", transfer.status);

    // Track transfer status
    println!("\nTracking transfer...");
    let status = ibc.transfers.get_status(&transfer.tx_hash).await?;
    println!("Current status: {}", status.state);
    println!("  Source confirmed: {}", status.source_confirmed);
    println!("  Relayed: {}", status.relayed);
    println!("  Dest confirmed: {}", status.dest_confirmed);

    // Get transfer history
    let history = ibc.transfers.get_history("synor1abc...", 5).await?;
    println!("\nTransfer history (last 5):");
    for tx in &history {
        let direction = if tx.sender == "synor1abc..." {
            "OUT"
        } else {
            "IN"
        };
        println!("  {} {} {} ({})", direction, tx.amount, tx.denom, tx.status);
    }

    // Get pending transfers
    let pending = ibc.transfers.get_pending("synor1abc...").await?;
    println!("\nPending transfers: {}", pending.len());

    println!();
    Ok(())
}

async fn packet_example(ibc: &SynorIbc) -> Result<(), Box<dyn std::error::Error>> {
    println!("=== Packet Handling ===");

    // Get pending packets
    let packets = ibc.packets.get_pending("channel-0").await?;
    println!("Pending packets on channel-0: {}", packets.len());

    for packet in packets.iter().take(3) {
        println!("\n  Packet {}:", packet.sequence);
        println!("    Source: {}/{}", packet.source_port, packet.source_channel);
        println!("    Dest: {}/{}", packet.dest_port, packet.dest_channel);
        println!("    State: {}", packet.state);
        println!("    Data size: {} bytes", packet.data.len());
        println!("    Timeout height: {}", packet.timeout_height);
        println!("    Timeout timestamp: {}", packet.timeout_timestamp);
    }

    // Get packet by sequence
    let packet = ibc.packets.get("channel-0", 1).await?;
    println!("\nPacket details:");
    println!("  Commitment: {}", packet.commitment);
    println!("  Receipt: {}", packet.receipt.unwrap_or_default());
    println!("  Acknowledgement: {}", packet.acknowledgement.unwrap_or_default());

    // Get packet receipts
    let receipts = ibc.packets.get_receipts("channel-0", &[1, 2, 3]).await?;
    println!("\nPacket receipts:");
    for (seq, receipt) in &receipts {
        let status = if *receipt { "received" } else { "not received" };
        println!("  Sequence {}: {}", seq, status);
    }

    // Get timed out packets
    let timed_out = ibc.packets.get_timed_out("channel-0").await?;
    println!("\nTimed out packets: {}", timed_out.len());
    for p in &timed_out {
        println!("  Sequence {}: timeout at {}", p.sequence, p.timeout_timestamp);
    }

    // Get unreceived packets
    let unreceived = ibc.packets.get_unreceived("channel-0").await?;
    let unreceived_str = if unreceived.is_empty() {
        "none".to_string()
    } else {
        format!("{:?}", unreceived)
    };
    println!("\nUnreceived packet sequences: {}", unreceived_str);

    // Get unacknowledged packets
    let unacked = ibc.packets.get_unacknowledged("channel-0").await?;
    let unacked_str = if unacked.is_empty() {
        "none".to_string()
    } else {
        format!("{:?}", unacked)
    };
    println!("Unacknowledged packet sequences: {}", unacked_str);

    println!();
    Ok(())
}

async fn relayer_example(ibc: &SynorIbc) -> Result<(), Box<dyn std::error::Error>> {
    println!("=== Relayer Operations ===");

    // Get active relayers
    let relayers = ibc.relayers.list().await?;
    println!("Active relayers: {}", relayers.len());

    for relayer in relayers.iter().take(3) {
        println!("\n  {}:", relayer.address);
        println!("    Chains: {:?}", relayer.chains);
        println!("    Packets relayed: {}", relayer.packets_relayed);
        println!("    Success rate: {:.1}%", relayer.success_rate);
        println!("    Avg latency: {}ms", relayer.avg_latency);
        println!("    Fee rate: {:.2}%", relayer.fee_rate);
    }

    // Get relayer statistics
    let stats = ibc.relayers.get_stats().await?;
    println!("\nGlobal relayer statistics:");
    println!("  Total relayers: {}", stats.total_relayers);
    println!("  Active relayers: {}", stats.active_relayers);
    println!("  Packets relayed (24h): {}", stats.packets_relayed_24h);
    println!("  Total fees earned: {}", stats.total_fees_earned);

    // Register as a relayer
    println!("\nRegistering as relayer...");
    let registration = ibc
        .relayers
        .register(RelayerRegistration {
            chains: vec!["synor-1".to_string(), "cosmoshub-4".to_string()],
            fee_rate: 0.1, // 0.1% fee
            min_packet_size: 0,
            max_packet_size: 1000000,
        })
        .await?;
    println!("Registered with address: {}", registration.relayer_address);

    // Start relaying (in background)
    println!("\nStarting relay service...");
    let relay_session = ibc
        .relayers
        .start_relay(RelayConfig {
            channels: vec!["channel-0".to_string()],
            auto_ack: true,
            batch_size: 10,
            poll_interval: 5000,
        })
        .await?;
    println!("Relay session started: {}", relay_session.session_id);

    // Get relay queue
    let queue = ibc.relayers.get_queue("channel-0").await?;
    println!("\nRelay queue for channel-0: {} packets", queue.len());

    // Manually relay a packet
    if let Some(first_packet) = queue.first() {
        println!("\nRelaying packet...");
        let relay_result = ibc
            .relayers
            .relay_packet(first_packet.sequence, "channel-0")
            .await?;
        println!("Relay result: {}", relay_result.status);
        println!("  TX Hash: {}", relay_result.tx_hash);
        println!("  Fee earned: {}", relay_result.fee_earned);
    }

    // Stop relay session
    ibc.relayers.stop_relay(&relay_session.session_id).await?;
    println!("\nRelay session stopped");

    println!();
    Ok(())
}

async fn connection_example(ibc: &SynorIbc) -> Result<(), Box<dyn std::error::Error>> {
    println!("=== Connection Information ===");

    // List connections
    let connections = ibc.connections.list().await?;
    println!("Total connections: {}", connections.len());

    for conn in connections.iter().take(3) {
        println!("\n  {}:", conn.connection_id);
        println!("    Client: {}", conn.client_id);
        println!("    Counterparty: {}", conn.counterparty_connection_id);
        println!("    State: {}", conn.state);
        println!("    Versions: {:?}", conn.versions);
    }

    // Get connection details
    let connection = ibc.connections.get("connection-0").await?;
    println!("\nConnection-0 details:");
    println!("  Delay period: {}ns", connection.delay_period);
    println!("  Counterparty client: {}", connection.counterparty_client_id);
    println!("  Counterparty prefix: {}", connection.counterparty_prefix);

    // Get client state
    let client = ibc.clients.get(&connection.client_id).await?;
    println!("\nClient state:");
    println!("  Chain ID: {}", client.chain_id);
    println!("  Trust level: {}", client.trust_level);
    println!("  Trusting period: {}", client.trusting_period);
    println!("  Unbonding period: {}", client.unbonding_period);
    println!("  Latest height: {}", client.latest_height);
    println!("  Frozen: {}", client.frozen);

    // Get consensus state
    let consensus = ibc
        .clients
        .get_consensus_state(&connection.client_id, client.latest_height)
        .await?;
    println!("\nConsensus state at height {}:", client.latest_height);
    println!("  Timestamp: {}", consensus.timestamp);
    let root_preview = if consensus.root.len() > 20 {
        &consensus.root[..20]
    } else {
        &consensus.root
    };
    println!("  Root: {}...", root_preview);
    let validators_preview = if consensus.next_validators_hash.len() > 20 {
        &consensus.next_validators_hash[..20]
    } else {
        &consensus.next_validators_hash
    };
    println!("  Next validators hash: {}...", validators_preview);

    println!();
    Ok(())
}
