// Package main demonstrates the Synor IBC SDK for Go
//
// This example covers Inter-Blockchain Communication operations:
// - Cross-chain token transfers
// - Channel management
// - Packet handling
// - Relayer operations
package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/synor/sdk-go/ibc"
)

func main() {
	// Initialize client
	config := ibc.Config{
		APIKey:         getEnv("SYNOR_API_KEY", "your-api-key"),
		Endpoint:       "https://ibc.synor.io/v1",
		Timeout:        30 * time.Second,
		Retries:        3,
		Debug:          false,
		DefaultNetwork: ibc.NetworkMainnet,
	}

	client, err := ibc.NewClient(config)
	if err != nil {
		log.Fatalf("Failed to create client: %v", err)
	}
	defer client.Close()

	ctx := context.Background()

	// Check service health
	healthy, err := client.HealthCheck(ctx)
	if err != nil {
		log.Printf("Health check failed: %v", err)
	} else {
		fmt.Printf("Service healthy: %v\n\n", healthy)
	}

	// Run examples
	chainsExample(ctx, client)
	channelsExample(ctx, client)
	transferExample(ctx, client)
	packetExample(ctx, client)
	relayerExample(ctx, client)
	connectionExample(ctx, client)
}

func chainsExample(ctx context.Context, client *ibc.Client) {
	fmt.Println("=== Connected Chains ===")

	// Get all connected chains
	chains, err := client.Chains.List(ctx)
	if err != nil {
		log.Printf("Failed to list chains: %v", err)
		return
	}
	fmt.Printf("Connected chains: %d\n", len(chains))

	for _, chain := range chains {
		fmt.Printf("  %s:\n", chain.ChainID)
		fmt.Printf("    Name: %s\n", chain.Name)
		fmt.Printf("    Status: %s\n", chain.Status)
		fmt.Printf("    Block height: %d\n", chain.LatestHeight)
		fmt.Printf("    Channels: %d\n", chain.ChannelCount)
	}

	// Get specific chain info
	cosmos, err := client.Chains.Get(ctx, "cosmoshub-4")
	if err != nil {
		log.Printf("Failed to get Cosmos Hub: %v", err)
		return
	}
	fmt.Println("\nCosmos Hub details:")
	fmt.Printf("  RPC: %s\n", cosmos.RPCEndpoint)
	fmt.Printf("  Rest: %s\n", cosmos.RestEndpoint)
	fmt.Printf("  Native denom: %s\n", cosmos.NativeDenom)
	fmt.Printf("  Prefix: %s\n", cosmos.Bech32Prefix)

	// Get supported assets on a chain
	assets, err := client.Chains.GetAssets(ctx, "cosmoshub-4")
	if err != nil {
		log.Printf("Failed to get assets: %v", err)
		return
	}
	fmt.Println("\nSupported assets on Cosmos Hub:")
	for _, asset := range assets[:min(5, len(assets))] {
		fmt.Printf("  %s: %s\n", asset.Symbol, asset.Denom)
		fmt.Printf("    Origin: %s\n", asset.OriginChain)
		fmt.Printf("    Decimals: %d\n", asset.Decimals)
	}

	// Get chain paths (routes)
	paths, err := client.Chains.GetPaths(ctx, "synor-1", "cosmoshub-4")
	if err != nil {
		log.Printf("Failed to get paths: %v", err)
		return
	}
	fmt.Println("\nPaths from Synor to Cosmos Hub:")
	for _, path := range paths {
		fmt.Printf("  %s -> %s\n", path.SourceChannel, path.DestChannel)
		fmt.Printf("    Hops: %d\n", path.Hops)
		fmt.Printf("    Avg time: %ds\n", path.AvgTransferTime)
	}

	fmt.Println()
}

func channelsExample(ctx context.Context, client *ibc.Client) {
	fmt.Println("=== Channel Management ===")

	// List all channels
	channels, err := client.Channels.List(ctx)
	if err != nil {
		log.Printf("Failed to list channels: %v", err)
		return
	}
	fmt.Printf("Total channels: %d\n", len(channels))

	// Filter by state
	var openChannels []ibc.Channel
	for _, c := range channels {
		if c.State == ibc.ChannelStateOpen {
			openChannels = append(openChannels, c)
		}
	}
	fmt.Printf("Open channels: %d\n", len(openChannels))

	for _, channel := range openChannels[:min(3, len(openChannels))] {
		fmt.Printf("\n  Channel %s:\n", channel.ChannelID)
		fmt.Printf("    Port: %s\n", channel.PortID)
		fmt.Printf("    Counterparty: %s on %s\n", channel.CounterpartyChannelID, channel.CounterpartyChainID)
		fmt.Printf("    Ordering: %s\n", channel.Ordering)
		fmt.Printf("    Version: %s\n", channel.Version)
		fmt.Printf("    State: %s\n", channel.State)
	}

	// Get specific channel
	channel, err := client.Channels.Get(ctx, "channel-0")
	if err != nil {
		log.Printf("Failed to get channel: %v", err)
		return
	}
	fmt.Println("\nChannel-0 details:")
	fmt.Printf("  Connection: %s\n", channel.ConnectionID)
	fmt.Printf("  Counterparty port: %s\n", channel.CounterpartyPortID)

	// Get channel statistics
	stats, err := client.Channels.GetStats(ctx, "channel-0")
	if err != nil {
		log.Printf("Failed to get channel stats: %v", err)
		return
	}
	fmt.Println("\nChannel-0 statistics:")
	fmt.Printf("  Total packets sent: %d\n", stats.PacketsSent)
	fmt.Printf("  Total packets received: %d\n", stats.PacketsReceived)
	fmt.Printf("  Pending packets: %d\n", stats.PendingPackets)
	fmt.Printf("  Success rate: %.1f%%\n", stats.SuccessRate)
	fmt.Printf("  Avg relay time: %ds\n", stats.AvgRelayTime)

	// Get channel capacity
	capacity, err := client.Channels.GetCapacity(ctx, "channel-0")
	if err != nil {
		log.Printf("Failed to get channel capacity: %v", err)
		return
	}
	fmt.Println("\nChannel-0 capacity:")
	fmt.Printf("  Max throughput: %d packets/block\n", capacity.MaxPacketsPerBlock)
	fmt.Printf("  Current utilization: %.1f%%\n", capacity.Utilization)

	fmt.Println()
}

func transferExample(ctx context.Context, client *ibc.Client) {
	fmt.Println("=== Cross-Chain Transfers ===")

	// Estimate transfer fee
	estimate, err := client.Transfers.EstimateFee(ctx, &ibc.FeeEstimateRequest{
		SourceChain: "synor-1",
		DestChain:   "cosmoshub-4",
		Denom:       "usyn",
		Amount:      "1000000", // 1 SYN (6 decimals)
	})
	if err != nil {
		log.Printf("Failed to estimate fee: %v", err)
		return
	}
	fmt.Println("Transfer fee estimate:")
	fmt.Printf("  Gas: %d\n", estimate.Gas)
	fmt.Printf("  Fee: %s %s\n", estimate.Fee, estimate.FeeDenom)
	fmt.Printf("  Timeout: %ds\n", estimate.Timeout)

	// Initiate a transfer
	fmt.Println("\nInitiating transfer...")
	transfer, err := client.Transfers.Send(ctx, &ibc.TransferRequest{
		SourceChain:      "synor-1",
		DestChain:        "cosmoshub-4",
		Channel:          "channel-0",
		Sender:           "synor1abc...", // Your address
		Receiver:         "cosmos1xyz...", // Recipient address
		Denom:            "usyn",
		Amount:           "1000000", // 1 SYN
		Memo:             "Cross-chain transfer example",
		TimeoutHeight:    0, // Use timestamp instead
		TimeoutTimestamp: time.Now().Add(10 * time.Minute).UnixMilli(),
	})
	if err != nil {
		log.Printf("Failed to initiate transfer: %v", err)
		return
	}

	fmt.Println("Transfer initiated:")
	fmt.Printf("  TX Hash: %s\n", transfer.TxHash)
	fmt.Printf("  Sequence: %d\n", transfer.Sequence)
	fmt.Printf("  Status: %s\n", transfer.Status)

	// Track transfer status
	fmt.Println("\nTracking transfer...")
	status, err := client.Transfers.GetStatus(ctx, transfer.TxHash)
	if err != nil {
		log.Printf("Failed to get transfer status: %v", err)
		return
	}
	fmt.Printf("Current status: %s\n", status.State)
	fmt.Printf("  Source confirmed: %v\n", status.SourceConfirmed)
	fmt.Printf("  Relayed: %v\n", status.Relayed)
	fmt.Printf("  Dest confirmed: %v\n", status.DestConfirmed)

	// Get transfer history
	history, err := client.Transfers.GetHistory(ctx, "synor1abc...", 5)
	if err != nil {
		log.Printf("Failed to get transfer history: %v", err)
		return
	}
	fmt.Println("\nTransfer history (last 5):")
	for _, tx := range history {
		direction := "OUT"
		if tx.Sender != "synor1abc..." {
			direction = "IN"
		}
		fmt.Printf("  %s %s %s (%s)\n", direction, tx.Amount, tx.Denom, tx.Status)
	}

	// Get pending transfers
	pending, err := client.Transfers.GetPending(ctx, "synor1abc...")
	if err != nil {
		log.Printf("Failed to get pending transfers: %v", err)
		return
	}
	fmt.Printf("\nPending transfers: %d\n", len(pending))

	fmt.Println()
}

func packetExample(ctx context.Context, client *ibc.Client) {
	fmt.Println("=== Packet Handling ===")

	// Get pending packets
	packets, err := client.Packets.GetPending(ctx, "channel-0")
	if err != nil {
		log.Printf("Failed to get pending packets: %v", err)
		return
	}
	fmt.Printf("Pending packets on channel-0: %d\n", len(packets))

	for _, packet := range packets[:min(3, len(packets))] {
		fmt.Printf("\n  Packet %d:\n", packet.Sequence)
		fmt.Printf("    Source: %s/%s\n", packet.SourcePort, packet.SourceChannel)
		fmt.Printf("    Dest: %s/%s\n", packet.DestPort, packet.DestChannel)
		fmt.Printf("    State: %s\n", packet.State)
		fmt.Printf("    Data size: %d bytes\n", len(packet.Data))
		fmt.Printf("    Timeout height: %d\n", packet.TimeoutHeight)
		fmt.Printf("    Timeout timestamp: %d\n", packet.TimeoutTimestamp)
	}

	// Get packet by sequence
	packet, err := client.Packets.Get(ctx, "channel-0", 1)
	if err != nil {
		log.Printf("Failed to get packet: %v", err)
		return
	}
	fmt.Println("\nPacket details:")
	fmt.Printf("  Commitment: %s\n", packet.Commitment)
	fmt.Printf("  Receipt: %s\n", packet.Receipt)
	fmt.Printf("  Acknowledgement: %s\n", packet.Acknowledgement)

	// Get packet receipts
	receipts, err := client.Packets.GetReceipts(ctx, "channel-0", []uint64{1, 2, 3})
	if err != nil {
		log.Printf("Failed to get receipts: %v", err)
		return
	}
	fmt.Println("\nPacket receipts:")
	for seq, receipt := range receipts {
		status := "not received"
		if receipt {
			status = "received"
		}
		fmt.Printf("  Sequence %d: %s\n", seq, status)
	}

	// Get timed out packets
	timedOut, err := client.Packets.GetTimedOut(ctx, "channel-0")
	if err != nil {
		log.Printf("Failed to get timed out packets: %v", err)
		return
	}
	fmt.Printf("\nTimed out packets: %d\n", len(timedOut))
	for _, p := range timedOut {
		fmt.Printf("  Sequence %d: timeout at %d\n", p.Sequence, p.TimeoutTimestamp)
	}

	// Get unreceived packets
	unreceived, err := client.Packets.GetUnreceived(ctx, "channel-0")
	if err != nil {
		log.Printf("Failed to get unreceived packets: %v", err)
		return
	}
	unreceivedStr := "none"
	if len(unreceived) > 0 {
		unreceivedStr = fmt.Sprintf("%v", unreceived)
	}
	fmt.Printf("\nUnreceived packet sequences: %s\n", unreceivedStr)

	// Get unacknowledged packets
	unacked, err := client.Packets.GetUnacknowledged(ctx, "channel-0")
	if err != nil {
		log.Printf("Failed to get unacknowledged packets: %v", err)
		return
	}
	unackedStr := "none"
	if len(unacked) > 0 {
		unackedStr = fmt.Sprintf("%v", unacked)
	}
	fmt.Printf("Unacknowledged packet sequences: %s\n", unackedStr)

	fmt.Println()
}

func relayerExample(ctx context.Context, client *ibc.Client) {
	fmt.Println("=== Relayer Operations ===")

	// Get active relayers
	relayers, err := client.Relayers.List(ctx)
	if err != nil {
		log.Printf("Failed to list relayers: %v", err)
		return
	}
	fmt.Printf("Active relayers: %d\n", len(relayers))

	for _, relayer := range relayers[:min(3, len(relayers))] {
		fmt.Printf("\n  %s:\n", relayer.Address)
		fmt.Printf("    Chains: %v\n", relayer.Chains)
		fmt.Printf("    Packets relayed: %d\n", relayer.PacketsRelayed)
		fmt.Printf("    Success rate: %.1f%%\n", relayer.SuccessRate)
		fmt.Printf("    Avg latency: %dms\n", relayer.AvgLatency)
		fmt.Printf("    Fee rate: %.2f%%\n", relayer.FeeRate)
	}

	// Get relayer statistics
	stats, err := client.Relayers.GetStats(ctx)
	if err != nil {
		log.Printf("Failed to get relayer stats: %v", err)
		return
	}
	fmt.Println("\nGlobal relayer statistics:")
	fmt.Printf("  Total relayers: %d\n", stats.TotalRelayers)
	fmt.Printf("  Active relayers: %d\n", stats.ActiveRelayers)
	fmt.Printf("  Packets relayed (24h): %d\n", stats.PacketsRelayed24h)
	fmt.Printf("  Total fees earned: %s\n", stats.TotalFeesEarned)

	// Register as a relayer
	fmt.Println("\nRegistering as relayer...")
	registration, err := client.Relayers.Register(ctx, &ibc.RelayerRegistration{
		Chains:        []string{"synor-1", "cosmoshub-4"},
		FeeRate:       0.1, // 0.1% fee
		MinPacketSize: 0,
		MaxPacketSize: 1000000,
	})
	if err != nil {
		log.Printf("Failed to register as relayer: %v", err)
		return
	}
	fmt.Printf("Registered with address: %s\n", registration.RelayerAddress)

	// Start relaying (in background)
	fmt.Println("\nStarting relay service...")
	relaySession, err := client.Relayers.StartRelay(ctx, &ibc.RelayConfig{
		Channels:     []string{"channel-0"},
		AutoAck:      true,
		BatchSize:    10,
		PollInterval: 5000,
	})
	if err != nil {
		log.Printf("Failed to start relay: %v", err)
		return
	}
	fmt.Printf("Relay session started: %s\n", relaySession.SessionID)

	// Get relay queue
	queue, err := client.Relayers.GetQueue(ctx, "channel-0")
	if err != nil {
		log.Printf("Failed to get relay queue: %v", err)
		return
	}
	fmt.Printf("\nRelay queue for channel-0: %d packets\n", len(queue))

	// Manually relay a packet
	if len(queue) > 0 {
		fmt.Println("\nRelaying packet...")
		relayResult, err := client.Relayers.RelayPacket(ctx, queue[0].Sequence, "channel-0")
		if err != nil {
			log.Printf("Failed to relay packet: %v", err)
		} else {
			fmt.Printf("Relay result: %s\n", relayResult.Status)
			fmt.Printf("  TX Hash: %s\n", relayResult.TxHash)
			fmt.Printf("  Fee earned: %s\n", relayResult.FeeEarned)
		}
	}

	// Stop relay session
	err = client.Relayers.StopRelay(ctx, relaySession.SessionID)
	if err != nil {
		log.Printf("Failed to stop relay: %v", err)
	} else {
		fmt.Println("\nRelay session stopped")
	}

	fmt.Println()
}

func connectionExample(ctx context.Context, client *ibc.Client) {
	fmt.Println("=== Connection Information ===")

	// List connections
	connections, err := client.Connections.List(ctx)
	if err != nil {
		log.Printf("Failed to list connections: %v", err)
		return
	}
	fmt.Printf("Total connections: %d\n", len(connections))

	for _, conn := range connections[:min(3, len(connections))] {
		fmt.Printf("\n  %s:\n", conn.ConnectionID)
		fmt.Printf("    Client: %s\n", conn.ClientID)
		fmt.Printf("    Counterparty: %s\n", conn.CounterpartyConnectionID)
		fmt.Printf("    State: %s\n", conn.State)
		fmt.Printf("    Versions: %v\n", conn.Versions)
	}

	// Get connection details
	connection, err := client.Connections.Get(ctx, "connection-0")
	if err != nil {
		log.Printf("Failed to get connection: %v", err)
		return
	}
	fmt.Println("\nConnection-0 details:")
	fmt.Printf("  Delay period: %dns\n", connection.DelayPeriod)
	fmt.Printf("  Counterparty client: %s\n", connection.CounterpartyClientID)
	fmt.Printf("  Counterparty prefix: %s\n", connection.CounterpartyPrefix)

	// Get client state
	clientState, err := client.Clients.Get(ctx, connection.ClientID)
	if err != nil {
		log.Printf("Failed to get client state: %v", err)
		return
	}
	fmt.Println("\nClient state:")
	fmt.Printf("  Chain ID: %s\n", clientState.ChainID)
	fmt.Printf("  Trust level: %s\n", clientState.TrustLevel)
	fmt.Printf("  Trusting period: %s\n", clientState.TrustingPeriod)
	fmt.Printf("  Unbonding period: %s\n", clientState.UnbondingPeriod)
	fmt.Printf("  Latest height: %d\n", clientState.LatestHeight)
	fmt.Printf("  Frozen: %v\n", clientState.Frozen)

	// Get consensus state
	consensus, err := client.Clients.GetConsensusState(ctx, connection.ClientID, clientState.LatestHeight)
	if err != nil {
		log.Printf("Failed to get consensus state: %v", err)
		return
	}
	fmt.Printf("\nConsensus state at height %d:\n", clientState.LatestHeight)
	fmt.Printf("  Timestamp: %s\n", consensus.Timestamp)
	fmt.Printf("  Root: %s...\n", consensus.Root[:min(20, len(consensus.Root))])
	fmt.Printf("  Next validators hash: %s...\n", consensus.NextValidatorsHash[:min(20, len(consensus.NextValidatorsHash))])

	fmt.Println()
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func getEnv(key, defaultValue string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return defaultValue
}
