// Package main demonstrates the Synor DEX SDK for Go
//
// This example covers decentralized exchange operations:
// - Spot trading (limit/market orders)
// - Perpetual futures trading
// - Liquidity provision (AMM pools)
// - Order book management
// - Portfolio tracking
package main

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

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

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

	client, err := dex.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
	marketsExample(ctx, client)
	spotTradingExample(ctx, client)
	perpsTradingExample(ctx, client)
	liquidityExample(ctx, client)
	orderbookExample(ctx, client)
	portfolioExample(ctx, client)
}

func marketsExample(ctx context.Context, client *dex.Client) {
	fmt.Println("=== Markets ===")

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

	for _, market := range markets[:min(5, len(markets))] {
		fmt.Printf("\n  %s:\n", market.Symbol)
		fmt.Printf("    Base: %s, Quote: %s\n", market.BaseAsset, market.QuoteAsset)
		fmt.Printf("    Price: %s\n", market.LastPrice)
		fmt.Printf("    24h Volume: %s\n", market.Volume24h)
		fmt.Printf("    24h Change: %s%%\n", market.Change24h)
		fmt.Printf("    Status: %s\n", market.Status)
	}

	// Get specific market
	market, err := client.Markets.Get(ctx, "SYN-USDC")
	if err != nil {
		log.Printf("Failed to get market: %v", err)
		return
	}
	fmt.Printf("\nSYN-USDC details:\n")
	fmt.Printf("  Min order size: %s\n", market.MinOrderSize)
	fmt.Printf("  Tick size: %s\n", market.TickSize)
	fmt.Printf("  Maker fee: %s%%\n", market.MakerFee)
	fmt.Printf("  Taker fee: %s%%\n", market.TakerFee)

	// Get market statistics
	stats, err := client.Markets.GetStats(ctx, "SYN-USDC")
	if err != nil {
		log.Printf("Failed to get market stats: %v", err)
		return
	}
	fmt.Printf("\nMarket statistics:\n")
	fmt.Printf("  High 24h: %s\n", stats.High24h)
	fmt.Printf("  Low 24h: %s\n", stats.Low24h)
	fmt.Printf("  Open interest: %s\n", stats.OpenInterest)
	fmt.Printf("  Funding rate: %s%%\n", stats.FundingRate)

	fmt.Println()
}

func spotTradingExample(ctx context.Context, client *dex.Client) {
	fmt.Println("=== Spot Trading ===")

	// Place a limit order
	fmt.Println("Placing limit buy order...")
	limitOrder, err := client.Spot.PlaceOrder(ctx, &dex.OrderRequest{
		Market:    "SYN-USDC",
		Side:      dex.SideBuy,
		OrderType: dex.OrderTypeLimit,
		Price:     "1.50",
		Quantity:  "100",
		TimeInForce: dex.TimeInForceGTC,
	})
	if err != nil {
		log.Printf("Failed to place limit order: %v", err)
	} else {
		fmt.Printf("Limit order placed:\n")
		fmt.Printf("  Order ID: %s\n", limitOrder.OrderID)
		fmt.Printf("  Status: %s\n", limitOrder.Status)
		fmt.Printf("  Price: %s\n", limitOrder.Price)
		fmt.Printf("  Quantity: %s\n", limitOrder.Quantity)
	}

	// Place a market order
	fmt.Println("\nPlacing market sell order...")
	marketOrder, err := client.Spot.PlaceOrder(ctx, &dex.OrderRequest{
		Market:    "SYN-USDC",
		Side:      dex.SideSell,
		OrderType: dex.OrderTypeMarket,
		Quantity:  "50",
	})
	if err != nil {
		log.Printf("Failed to place market order: %v", err)
	} else {
		fmt.Printf("Market order executed:\n")
		fmt.Printf("  Order ID: %s\n", marketOrder.OrderID)
		fmt.Printf("  Status: %s\n", marketOrder.Status)
		fmt.Printf("  Filled: %s\n", marketOrder.FilledQuantity)
		fmt.Printf("  Avg price: %s\n", marketOrder.AveragePrice)
	}

	// Get order status
	if limitOrder != nil {
		status, err := client.Spot.GetOrder(ctx, limitOrder.OrderID)
		if err != nil {
			log.Printf("Failed to get order status: %v", err)
		} else {
			fmt.Printf("\nOrder status:\n")
			fmt.Printf("  Status: %s\n", status.Status)
			fmt.Printf("  Filled: %s / %s\n", status.FilledQuantity, status.Quantity)
			fmt.Printf("  Remaining: %s\n", status.RemainingQuantity)
		}

		// Cancel order
		fmt.Println("\nCancelling order...")
		err = client.Spot.CancelOrder(ctx, limitOrder.OrderID)
		if err != nil {
			log.Printf("Failed to cancel order: %v", err)
		} else {
			fmt.Println("Order cancelled successfully")
		}
	}

	// Get open orders
	openOrders, err := client.Spot.GetOpenOrders(ctx, "SYN-USDC")
	if err != nil {
		log.Printf("Failed to get open orders: %v", err)
	} else {
		fmt.Printf("\nOpen orders: %d\n", len(openOrders))
	}

	// Get trade history
	trades, err := client.Spot.GetTradeHistory(ctx, "SYN-USDC", 10)
	if err != nil {
		log.Printf("Failed to get trade history: %v", err)
	} else {
		fmt.Printf("\nRecent trades: %d\n", len(trades))
		for _, trade := range trades[:min(3, len(trades))] {
			fmt.Printf("  %s %s @ %s (%s)\n", trade.Side, trade.Quantity, trade.Price, trade.Timestamp.Format(time.RFC3339))
		}
	}

	fmt.Println()
}

func perpsTradingExample(ctx context.Context, client *dex.Client) {
	fmt.Println("=== Perpetual Futures Trading ===")

	// Get available perps markets
	perpsMarkets, err := client.Perps.ListMarkets(ctx)
	if err != nil {
		log.Printf("Failed to list perps markets: %v", err)
		return
	}
	fmt.Printf("Available perps markets: %d\n", len(perpsMarkets))

	for _, market := range perpsMarkets[:min(3, len(perpsMarkets))] {
		fmt.Printf("  %s: %s (funding: %s%%)\n", market.Symbol, market.MarkPrice, market.FundingRate)
	}

	// Open a long position
	fmt.Println("\nOpening long position...")
	position, err := client.Perps.OpenPosition(ctx, &dex.PerpsOrderRequest{
		Market:    "SYN-USDC-PERP",
		Side:      dex.SideBuy,
		OrderType: dex.OrderTypeLimit,
		Price:     "1.50",
		Size:      "1000",
		Leverage:  10,
		ReduceOnly: false,
	})
	if err != nil {
		log.Printf("Failed to open position: %v", err)
	} else {
		fmt.Printf("Position opened:\n")
		fmt.Printf("  Position ID: %s\n", position.PositionID)
		fmt.Printf("  Size: %s\n", position.Size)
		fmt.Printf("  Entry price: %s\n", position.EntryPrice)
		fmt.Printf("  Leverage: %dx\n", position.Leverage)
		fmt.Printf("  Liquidation price: %s\n", position.LiquidationPrice)
	}

	// Get position details
	if position != nil {
		details, err := client.Perps.GetPosition(ctx, position.PositionID)
		if err != nil {
			log.Printf("Failed to get position: %v", err)
		} else {
			fmt.Printf("\nPosition details:\n")
			fmt.Printf("  Unrealized PnL: %s\n", details.UnrealizedPnL)
			fmt.Printf("  Margin: %s\n", details.Margin)
			fmt.Printf("  Margin ratio: %s%%\n", details.MarginRatio)
		}
	}

	// Set stop loss and take profit
	if position != nil {
		fmt.Println("\nSetting stop loss and take profit...")
		err = client.Perps.SetStopLoss(ctx, position.PositionID, "1.40")
		if err != nil {
			log.Printf("Failed to set stop loss: %v", err)
		} else {
			fmt.Println("Stop loss set at 1.40")
		}

		err = client.Perps.SetTakeProfit(ctx, position.PositionID, "1.80")
		if err != nil {
			log.Printf("Failed to set take profit: %v", err)
		} else {
			fmt.Println("Take profit set at 1.80")
		}

		// Close position
		fmt.Println("\nClosing position...")
		closeResult, err := client.Perps.ClosePosition(ctx, position.PositionID)
		if err != nil {
			log.Printf("Failed to close position: %v", err)
		} else {
			fmt.Printf("Position closed:\n")
			fmt.Printf("  Realized PnL: %s\n", closeResult.RealizedPnL)
			fmt.Printf("  Close price: %s\n", closeResult.ClosePrice)
		}
	}

	// Get all positions
	positions, err := client.Perps.GetPositions(ctx)
	if err != nil {
		log.Printf("Failed to get positions: %v", err)
	} else {
		fmt.Printf("\nOpen positions: %d\n", len(positions))
	}

	// Get funding history
	funding, err := client.Perps.GetFundingHistory(ctx, "SYN-USDC-PERP", 10)
	if err != nil {
		log.Printf("Failed to get funding history: %v", err)
	} else {
		fmt.Printf("Funding payments: %d\n", len(funding))
	}

	fmt.Println()
}

func liquidityExample(ctx context.Context, client *dex.Client) {
	fmt.Println("=== Liquidity Provision ===")

	// Get available pools
	pools, err := client.Liquidity.ListPools(ctx)
	if err != nil {
		log.Printf("Failed to list pools: %v", err)
		return
	}
	fmt.Printf("Available pools: %d\n", len(pools))

	for _, pool := range pools[:min(3, len(pools))] {
		fmt.Printf("\n  %s:\n", pool.Name)
		fmt.Printf("    TVL: $%s\n", pool.TVL)
		fmt.Printf("    APR: %s%%\n", pool.APR)
		fmt.Printf("    Volume 24h: $%s\n", pool.Volume24h)
		fmt.Printf("    Fee tier: %s%%\n", pool.FeeTier)
	}

	// Get pool details
	pool, err := client.Liquidity.GetPool(ctx, "SYN-USDC")
	if err != nil {
		log.Printf("Failed to get pool: %v", err)
		return
	}
	fmt.Printf("\nSYN-USDC pool details:\n")
	fmt.Printf("  Token0: %s (%s)\n", pool.Token0.Symbol, pool.Token0Reserve)
	fmt.Printf("  Token1: %s (%s)\n", pool.Token1.Symbol, pool.Token1Reserve)
	fmt.Printf("  Price: %s\n", pool.Price)
	fmt.Printf("  Total LP tokens: %s\n", pool.TotalLPTokens)

	// Add liquidity
	fmt.Println("\nAdding liquidity...")
	addResult, err := client.Liquidity.AddLiquidity(ctx, &dex.AddLiquidityRequest{
		Pool:          "SYN-USDC",
		Amount0:       "100",
		Amount1:       "150",
		SlippageBps:   50, // 0.5%
	})
	if err != nil {
		log.Printf("Failed to add liquidity: %v", err)
	} else {
		fmt.Printf("Liquidity added:\n")
		fmt.Printf("  LP tokens received: %s\n", addResult.LPTokens)
		fmt.Printf("  Position ID: %s\n", addResult.PositionID)
		fmt.Printf("  Share of pool: %s%%\n", addResult.ShareOfPool)
	}

	// Get LP positions
	lpPositions, err := client.Liquidity.GetPositions(ctx)
	if err != nil {
		log.Printf("Failed to get LP positions: %v", err)
	} else {
		fmt.Printf("\nLP positions: %d\n", len(lpPositions))
		for _, pos := range lpPositions {
			fmt.Printf("  %s: %s LP tokens (value: $%s)\n", pos.Pool, pos.LPTokens, pos.Value)
		}
	}

	// Claim fees
	if addResult != nil {
		fmt.Println("\nClaiming fees...")
		fees, err := client.Liquidity.ClaimFees(ctx, addResult.PositionID)
		if err != nil {
			log.Printf("Failed to claim fees: %v", err)
		} else {
			fmt.Printf("Fees claimed:\n")
			fmt.Printf("  Token0: %s\n", fees.Amount0)
			fmt.Printf("  Token1: %s\n", fees.Amount1)
		}

		// Remove liquidity
		fmt.Println("\nRemoving liquidity...")
		removeResult, err := client.Liquidity.RemoveLiquidity(ctx, &dex.RemoveLiquidityRequest{
			PositionID:  addResult.PositionID,
			LPTokens:    addResult.LPTokens,
			SlippageBps: 50,
		})
		if err != nil {
			log.Printf("Failed to remove liquidity: %v", err)
		} else {
			fmt.Printf("Liquidity removed:\n")
			fmt.Printf("  Token0 received: %s\n", removeResult.Amount0)
			fmt.Printf("  Token1 received: %s\n", removeResult.Amount1)
		}
	}

	fmt.Println()
}

func orderbookExample(ctx context.Context, client *dex.Client) {
	fmt.Println("=== Order Book ===")

	// Get order book snapshot
	orderbook, err := client.Orderbook.GetSnapshot(ctx, "SYN-USDC", 10)
	if err != nil {
		log.Printf("Failed to get orderbook: %v", err)
		return
	}

	fmt.Println("Order book for SYN-USDC:")
	fmt.Println("\nAsks (sells):")
	for _, ask := range orderbook.Asks[:min(5, len(orderbook.Asks))] {
		fmt.Printf("  %s @ %s\n", ask.Quantity, ask.Price)
	}

	fmt.Println("\nBids (buys):")
	for _, bid := range orderbook.Bids[:min(5, len(orderbook.Bids))] {
		fmt.Printf("  %s @ %s\n", bid.Quantity, bid.Price)
	}

	fmt.Printf("\nSpread: %s (%s%%)\n", orderbook.Spread, orderbook.SpreadPercent)
	fmt.Printf("Mid price: %s\n", orderbook.MidPrice)

	// Get recent trades
	recentTrades, err := client.Orderbook.GetRecentTrades(ctx, "SYN-USDC", 10)
	if err != nil {
		log.Printf("Failed to get recent trades: %v", err)
	} else {
		fmt.Println("\nRecent trades:")
		for _, trade := range recentTrades[:min(5, len(recentTrades))] {
			fmt.Printf("  %s %s @ %s\n", trade.Side, trade.Quantity, trade.Price)
		}
	}

	// Subscribe to orderbook updates (simulated)
	fmt.Println("\nSubscribing to orderbook updates...")
	updates := client.Orderbook.Subscribe(ctx, "SYN-USDC")

	// Process a few updates (in real usage, this would be a goroutine)
	go func() {
		for i := 0; i < 3; i++ {
			select {
			case update := <-updates:
				fmt.Printf("  Update: %s %s @ %s\n", update.Side, update.Quantity, update.Price)
			case <-time.After(5 * time.Second):
				return
			}
		}
	}()

	time.Sleep(2 * time.Second) // Wait for some updates

	fmt.Println()
}

func portfolioExample(ctx context.Context, client *dex.Client) {
	fmt.Println("=== Portfolio ===")

	// Get portfolio overview
	portfolio, err := client.Portfolio.GetOverview(ctx)
	if err != nil {
		log.Printf("Failed to get portfolio: %v", err)
		return
	}

	fmt.Println("Portfolio overview:")
	fmt.Printf("  Total value: $%s\n", portfolio.TotalValue)
	fmt.Printf("  Available balance: $%s\n", portfolio.AvailableBalance)
	fmt.Printf("  In orders: $%s\n", portfolio.InOrders)
	fmt.Printf("  In positions: $%s\n", portfolio.InPositions)
	fmt.Printf("  Unrealized PnL: $%s\n", portfolio.UnrealizedPnL)

	// Get balances
	balances, err := client.Portfolio.GetBalances(ctx)
	if err != nil {
		log.Printf("Failed to get balances: %v", err)
	} else {
		fmt.Println("\nBalances:")
		for _, balance := range balances {
			fmt.Printf("  %s: %s (available: %s, in orders: %s)\n",
				balance.Asset, balance.Total, balance.Available, balance.InOrders)
		}
	}

	// Get PnL history
	pnlHistory, err := client.Portfolio.GetPnLHistory(ctx, 30) // Last 30 days
	if err != nil {
		log.Printf("Failed to get PnL history: %v", err)
	} else {
		fmt.Printf("\nPnL history (last %d days):\n", len(pnlHistory))
		if len(pnlHistory) > 0 {
			totalPnL := "0"
			for _, day := range pnlHistory {
				totalPnL = day.CumulativePnL
			}
			fmt.Printf("  Total PnL: $%s\n", totalPnL)
		}
	}

	// Get trade statistics
	stats, err := client.Portfolio.GetStats(ctx)
	if err != nil {
		log.Printf("Failed to get trade stats: %v", err)
	} else {
		fmt.Println("\nTrade statistics:")
		fmt.Printf("  Total trades: %d\n", stats.TotalTrades)
		fmt.Printf("  Win rate: %s%%\n", stats.WinRate)
		fmt.Printf("  Avg profit: $%s\n", stats.AvgProfit)
		fmt.Printf("  Avg loss: $%s\n", stats.AvgLoss)
		fmt.Printf("  Best trade: $%s\n", stats.BestTrade)
		fmt.Printf("  Worst trade: $%s\n", stats.WorstTrade)
	}

	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
}
