package io.synor.examples;

import io.synor.ibc.*;
import io.synor.ibc.types.*;

import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.CompletableFuture;

/**
 * Synor IBC SDK Examples for Java
 *
 * Demonstrates Inter-Blockchain Communication operations:
 * - Cross-chain transfers and messages
 * - Channel lifecycle management
 * - Packet handling and acknowledgments
 * - Relayer operations
 * - Connection monitoring
 */
public class IbcExample {

    public static void main(String[] args) throws Exception {
        // Initialize client
        IbcConfig config = IbcConfig.builder()
            .apiKey(System.getenv("SYNOR_API_KEY") != null ?
                    System.getenv("SYNOR_API_KEY") : "your-api-key")
            .endpoint("https://ibc.synor.io/v1")
            .timeout(30000)
            .retries(3)
            .debug(false)
            .defaultChain("synor-mainnet-1")
            .confirmations(1)
            .build();

        SynorIbc ibc = new SynorIbc(config);

        try {
            // Check service health
            boolean healthy = ibc.healthCheck().get();
            System.out.println("Service healthy: " + healthy + "\n");

            // Run examples
            chainsExample(ibc);
            channelsExample(ibc);
            transferExample(ibc);
            packetExample(ibc);
            relayerExample(ibc);
            monitoringExample(ibc);
        } finally {
            ibc.close();
        }
    }

    static void chainsExample(SynorIbc ibc) throws Exception {
        System.out.println("=== Chain Discovery ===");

        // Get all connected chains
        List<Chain> chains = ibc.chains().list().get();
        System.out.println("Connected chains: " + chains.size());

        for (Chain chain : chains.subList(0, Math.min(5, chains.size()))) {
            System.out.println("\n  " + chain.getChainId() + ":");
            System.out.println("    Name: " + chain.getName());
            System.out.println("    Type: " + chain.getChainType());
            System.out.println("    Status: " + chain.getStatus());
            System.out.println("    Block height: " + chain.getLatestHeight());
            System.out.println("    Light client: " + chain.getLightClient());
        }

        // Get specific chain info
        Chain synorChain = ibc.chains().get("synor-mainnet-1").get();
        System.out.println("\nSynor chain details:");
        System.out.println("  Bech32 prefix: " + synorChain.getBech32Prefix());
        System.out.println("  Gas price: " + synorChain.getGasPrice());
        System.out.println("  Supported features: " + String.join(", ", synorChain.getFeatures()));

        // Get chain connections
        List<Connection> connections = ibc.chains().getConnections("synor-mainnet-1").get();
        System.out.println("\nChain connections: " + connections.size());
        for (Connection conn : connections.subList(0, Math.min(3, connections.size()))) {
            System.out.println("  " + conn.getConnectionId() + " -> " + conn.getCounterpartyChainId());
        }

        System.out.println();
    }

    static void channelsExample(SynorIbc ibc) throws Exception {
        System.out.println("=== Channel Management ===");

        // List existing channels
        List<Channel> channels = ibc.channels().list().get();
        System.out.println("Active channels: " + channels.size());

        for (Channel channel : channels.subList(0, Math.min(3, channels.size()))) {
            System.out.println("\n  Channel " + channel.getChannelId() + ":");
            System.out.println("    Port: " + channel.getPortId());
            System.out.println("    State: " + channel.getState());
            System.out.println("    Order: " + channel.getOrdering());
            System.out.println("    Counterparty: " + channel.getCounterpartyChannelId() +
                             " on " + channel.getCounterpartyChainId());
            System.out.println("    Version: " + channel.getVersion());
        }

        // Create a new channel (4-step handshake)
        System.out.println("\nInitiating channel creation...");

        // Step 1: ChanOpenInit
        ChannelInitResult initResult = ibc.channels().openInit(ChannelInitRequest.builder()
            .portId("transfer")
            .counterpartyChainId("cosmos-hub-4")
            .counterpartyPortId("transfer")
            .version("ics20-1")
            .ordering(ChannelOrdering.UNORDERED)
            .build()).get();

        System.out.println("Channel init:");
        System.out.println("  Channel ID: " + initResult.getChannelId());
        System.out.println("  State: " + initResult.getState());
        System.out.println("  TX hash: " + initResult.getTxHash());

        // Step 2: Wait for ChanOpenTry (counterparty)
        System.out.println("\nWaiting for counterparty ChanOpenTry...");
        ChannelState tryState = ibc.channels().waitForState(
            initResult.getChannelId(),
            ChannelState.TRYOPEN,
            Duration.ofMinutes(5)
        ).get();
        System.out.println("Channel state: " + tryState);

        // Step 3: ChanOpenAck
        System.out.println("\nSending ChanOpenAck...");
        ChannelAckResult ackResult = ibc.channels().openAck(ChannelAckRequest.builder()
            .channelId(initResult.getChannelId())
            .counterpartyChannelId("channel-0")
            .counterpartyVersion("ics20-1")
            .build()).get();
        System.out.println("Ack TX: " + ackResult.getTxHash());

        // Step 4: Wait for ChanOpenConfirm (counterparty)
        System.out.println("\nWaiting for channel to open...");
        ChannelState openState = ibc.channels().waitForState(
            initResult.getChannelId(),
            ChannelState.OPEN,
            Duration.ofMinutes(5)
        ).get();
        System.out.println("Channel is now: " + openState);

        // Get channel details
        Channel channel = ibc.channels().get(initResult.getChannelId()).get();
        System.out.println("\nChannel details:");
        System.out.println("  Sequences - Send: " + channel.getNextSequenceSend() +
                         ", Recv: " + channel.getNextSequenceRecv() +
                         ", Ack: " + channel.getNextSequenceAck());

        System.out.println();
    }

    static void transferExample(SynorIbc ibc) throws Exception {
        System.out.println("=== Cross-Chain Transfers ===");

        // Get supported tokens for transfer
        List<TransferableToken> tokens = ibc.transfers().getSupportedTokens("cosmos-hub-4").get();
        System.out.println("Transferable tokens to Cosmos Hub:");
        for (TransferableToken token : tokens.subList(0, Math.min(5, tokens.size()))) {
            System.out.println("  " + token.getSymbol() + " (" + token.getDenom() + ")");
        }

        // Initiate a cross-chain transfer
        System.out.println("\nInitiating transfer...");
        TransferResult transfer = ibc.transfers().send(TransferRequest.builder()
            .sourceChannel("channel-0")
            .denom("usynor")
            .amount("1000000")
            .receiver("cosmos1...")
            .timeoutHeight(0)  // Use timestamp instead
            .timeoutTimestamp(Instant.now().plusSeconds(600).toEpochMilli() * 1_000_000L)
            .memo("IBC transfer from Synor")
            .build()).get();

        System.out.println("Transfer initiated:");
        System.out.println("  TX hash: " + transfer.getTxHash());
        System.out.println("  Sequence: " + transfer.getSequence());
        System.out.println("  Packet ID: " + transfer.getPacketId());
        System.out.println("  Status: " + transfer.getStatus());

        // Track transfer progress
        System.out.println("\nTracking transfer...");
        TransferStatus status = ibc.transfers().track(transfer.getPacketId()).get();
        System.out.println("Current status: " + status.getState());
        System.out.println("Source TX: " + status.getSourceTxHash());
        if (status.getDestTxHash() != null) {
            System.out.println("Dest TX: " + status.getDestTxHash());
        }

        // Wait for completion
        System.out.println("\nWaiting for transfer completion...");
        TransferStatus finalStatus = ibc.transfers().waitForCompletion(
            transfer.getPacketId(),
            Duration.ofMinutes(10)
        ).get();

        System.out.println("Transfer completed:");
        System.out.println("  Final status: " + finalStatus.getState());
        System.out.println("  Acknowledgment: " + finalStatus.getAcknowledgment());

        // Get transfer history
        List<Transfer> history = ibc.transfers().getHistory(10).get();
        System.out.println("\nRecent transfers: " + history.size());
        for (Transfer t : history.subList(0, Math.min(3, history.size()))) {
            System.out.println("  " + t.getAmount() + " " + t.getDenom() +
                             " -> " + t.getDestChain() + " (" + t.getStatus() + ")");
        }

        System.out.println();
    }

    static void packetExample(SynorIbc ibc) throws Exception {
        System.out.println("=== Packet Operations ===");

        // List pending packets
        List<Packet> pending = ibc.packets().listPending("channel-0").get();
        System.out.println("Pending packets on channel-0: " + pending.size());

        for (Packet packet : pending.subList(0, Math.min(3, pending.size()))) {
            System.out.println("\n  Packet #" + packet.getSequence() + ":");
            System.out.println("    Source: " + packet.getSourcePort() + "/" + packet.getSourceChannel());
            System.out.println("    Dest: " + packet.getDestPort() + "/" + packet.getDestChannel());
            System.out.println("    State: " + packet.getState());
            System.out.println("    Timeout: " + packet.getTimeoutTimestamp());
        }

        // Get specific packet
        Packet packet = ibc.packets().get("channel-0", 1).get();
        System.out.println("\nPacket details:");
        System.out.println("  Data (hex): " + packet.getDataHex().substring(0, 40) + "...");
        System.out.println("  Created: " + packet.getCreatedAt());

        // Get packet commitment proof
        PacketProof proof = ibc.packets().getCommitmentProof("channel-0", 1).get();
        System.out.println("\nCommitment proof:");
        System.out.println("  Height: " + proof.getProofHeight());
        System.out.println("  Proof size: " + proof.getProof().length + " bytes");

        // Get acknowledgment
        PacketAck ack = ibc.packets().getAcknowledgment("channel-0", 1).get();
        System.out.println("\nAcknowledgment:");
        System.out.println("  Result: " + (ack.isSuccess() ? "Success" : "Error: " + ack.getError()));
        System.out.println("  TX hash: " + ack.getTxHash());

        // List timed-out packets
        List<Packet> timedOut = ibc.packets().listTimedOut().get();
        System.out.println("\nTimed-out packets: " + timedOut.size());

        // Timeout a packet manually
        if (!timedOut.isEmpty()) {
            Packet toTimeout = timedOut.get(0);
            System.out.println("\nProcessing timeout for packet #" + toTimeout.getSequence());
            TimeoutResult timeout = ibc.packets().timeout(
                toTimeout.getSourceChannel(),
                toTimeout.getSequence()
            ).get();
            System.out.println("Timeout TX: " + timeout.getTxHash());
        }

        System.out.println();
    }

    static void relayerExample(SynorIbc ibc) throws Exception {
        System.out.println("=== Relayer Operations ===");

        // Get relayer status
        RelayerStatus status = ibc.relayer().getStatus().get();
        System.out.println("Relayer status:");
        System.out.println("  Running: " + status.isRunning());
        System.out.println("  Uptime: " + status.getUptime());
        System.out.println("  Packets relayed: " + status.getPacketsRelayed());
        System.out.println("  Errors: " + status.getErrorCount());

        // List active paths
        List<RelayPath> paths = ibc.relayer().listPaths().get();
        System.out.println("\nActive relay paths: " + paths.size());

        for (RelayPath path : paths) {
            System.out.println("\n  " + path.getPathId() + ":");
            System.out.println("    " + path.getSourceChain() + " <-> " + path.getDestChain());
            System.out.println("    Channel: " + path.getSourceChannel() + " <-> " + path.getDestChannel());
            System.out.println("    Status: " + path.getStatus());
            System.out.println("    Pending packets: " + path.getPendingPackets());
        }

        // Configure a new path
        System.out.println("\nConfiguring new relay path...");
        RelayPath newPath = ibc.relayer().addPath(PathConfig.builder()
            .sourceChain("synor-mainnet-1")
            .destChain("osmosis-1")
            .sourceChannel("channel-1")
            .destChannel("channel-100")
            .filterDenoms(List.of("usynor", "uosmo"))
            .minRelayAmount("1000")
            .maxRelayAmount("1000000000")
            .build()).get();

        System.out.println("Path created: " + newPath.getPathId());

        // Start relaying on path
        System.out.println("\nStarting relayer on path...");
        ibc.relayer().startPath(newPath.getPathId()).get();
        System.out.println("Relayer started");

        // Manually relay pending packets
        System.out.println("\nRelaying pending packets...");
        RelayResult relayResult = ibc.relayer().relayPending(newPath.getPathId()).get();
        System.out.println("Relayed " + relayResult.getPacketCount() + " packets");
        System.out.println("TX hashes: " + relayResult.getTxHashes().size());

        // Get relay history
        List<RelayEvent> history = ibc.relayer().getHistory(newPath.getPathId(), 10).get();
        System.out.println("\nRelay history:");
        for (RelayEvent event : history.subList(0, Math.min(3, history.size()))) {
            System.out.println("  " + event.getTimestamp() + ": " +
                             event.getEventType() + " - " + event.getPacketCount() + " packets");
        }

        System.out.println();
    }

    static void monitoringExample(SynorIbc ibc) throws Exception {
        System.out.println("=== IBC Monitoring ===");

        // Get IBC metrics
        IbcMetrics metrics = ibc.monitoring().getMetrics().get();
        System.out.println("IBC metrics:");
        System.out.println("  Total channels: " + metrics.getTotalChannels());
        System.out.println("  Active channels: " + metrics.getActiveChannels());
        System.out.println("  Total packets: " + metrics.getTotalPackets());
        System.out.println("  Pending packets: " + metrics.getPendingPackets());
        System.out.println("  Failed packets: " + metrics.getFailedPackets());
        System.out.println("  Avg relay time: " + metrics.getAvgRelayTime() + "ms");

        // Get chain health
        List<ChainHealth> chainHealth = ibc.monitoring().getChainHealth().get();
        System.out.println("\nChain health:");
        for (ChainHealth health : chainHealth.subList(0, Math.min(3, chainHealth.size()))) {
            System.out.println("  " + health.getChainId() + ":");
            System.out.println("    Status: " + health.getStatus());
            System.out.println("    Block lag: " + health.getBlockLag());
            System.out.println("    Last update: " + health.getLastUpdate());
        }

        // Get channel statistics
        ChannelStats stats = ibc.monitoring().getChannelStats("channel-0").get();
        System.out.println("\nChannel-0 statistics:");
        System.out.println("  Packets sent: " + stats.getPacketsSent());
        System.out.println("  Packets received: " + stats.getPacketsReceived());
        System.out.println("  Success rate: " + stats.getSuccessRate() + "%");
        System.out.println("  Avg confirmation time: " + stats.getAvgConfirmationTime() + "ms");

        // Subscribe to IBC events
        System.out.println("\nSubscribing to IBC events...");
        ibc.monitoring().subscribe(event -> {
            System.out.println("Event: " + event.getType() + " on " + event.getChannelId());
        }).get();

        // Get alerts
        List<IbcAlert> alerts = ibc.monitoring().getAlerts().get();
        System.out.println("\nActive alerts: " + alerts.size());
        for (IbcAlert alert : alerts) {
            System.out.println("  [" + alert.getSeverity() + "] " + alert.getMessage());
        }

        System.out.println();
    }
}
