package hyperliquid

import (
	"context"
	"crypto/ecdsa"
	"fmt"
	"nofx/logger"
	hlprovider "nofx/provider/hyperliquid"
	"strconv"
	"strings"
	"sync"

	"github.com/ethereum/go-ethereum/crypto"
	"github.com/sonirico/go-hyperliquid"
)

// HyperliquidTrader Hyperliquid trader
type HyperliquidTrader struct {
	exchange         *hyperliquid.Exchange
	ctx              context.Context
	walletAddr       string
	meta             *hyperliquid.Meta // Cache meta information (including precision)
	metaMutex        sync.RWMutex      // Protect concurrent access to meta field
	isCrossMargin    bool              // Whether to use cross margin mode
	isUnifiedAccount bool              // Whether to use Unified Account mode (Spot as collateral for Perps)
	// xyz dex support (stocks, forex, commodities)
	xyzMeta      *xyzDexMeta
	xyzMetaMutex sync.RWMutex
	privateKey   *ecdsa.PrivateKey // For xyz dex signing
	isTestnet    bool
}

// xyzDexMeta represents metadata for xyz dex assets
type xyzDexMeta struct {
	Universe []xyzAssetInfo `json:"universe"`
}

// xyzAssetInfo represents info for a single xyz dex asset
type xyzAssetInfo struct {
	Name        string `json:"name"`
	SzDecimals  int    `json:"szDecimals"`
	MaxLeverage int    `json:"maxLeverage"`
}

// xyz dex assets (stocks, forex, commodities, index)
// Updated based on actual available assets from xyz dex API
var xyzDexAssets = map[string]bool{
	// Stocks (US equities perpetuals)
	"TSLA": true, "NVDA": true, "AAPL": true, "MSFT": true, "META": true,
	"AMZN": true, "GOOGL": true, "AMD": true, "COIN": true, "NFLX": true,
	"PLTR": true, "HOOD": true, "INTC": true, "MSTR": true, "TSM": true,
	"ORCL": true, "MU": true, "RIVN": true, "COST": true, "LLY": true,
	"CRCL": true, "SKHX": true, "SNDK": true,
	// Forex (currency pairs)
	"EUR": true, "JPY": true,
	// Commodities (precious metals)
	"GOLD": true, "SILVER": true,
	// Index
	"XYZ100": true,
}

// defaultBuilder is the builder info for order routing.
// Users approve this builder during the top-right Hyperliquid connect flow before
// their generated agent wallet is saved for live trading.
//
// Fee is in tenths of a basis point: 50 = 5 bps = 0.05% (5 per 10,000). Existing
// approvals at the prior 0.1% cap remain valid on-chain because 0.05% is
// still within their approved max.
var defaultBuilder = &hyperliquid.BuilderInfo{
	Builder: "0x891dc6f05ad47a3c1a05da55e7a7517971faaf0d",
	Fee:     50,
}

// isXyzDexAsset checks if a symbol is an xyz dex asset.
// Keep this delegated to the provider map so newly listed xyz markets such as
// SAMSUNG-USDC / SK-HYNIX-USDC cannot accidentally fall through as crypto.
func isXyzDexAsset(symbol string) bool {
	return hlprovider.IsXYZAsset(symbol)
}

// convertSymbolToHyperliquid converts standard/display symbols to Hyperliquid format.
// Examples: "BTCUSDT" -> "BTC", "TSLA" -> "xyz:TSLA", "SAMSUNG-USDC" -> "xyz:SMSN".
func convertSymbolToHyperliquid(symbol string) string {
	return hlprovider.FormatCoinForAPI(symbol)
}

// absFloat returns absolute value of float
func absFloat(x float64) float64 {
	if x < 0 {
		return -x
	}
	return x
}

// NewHyperliquidTrader creates a Hyperliquid trader
// unifiedAccount: when true, Spot USDC balance is used as collateral for Perp trading
func NewHyperliquidTrader(privateKeyHex string, walletAddr string, testnet bool, unifiedAccount bool) (*HyperliquidTrader, error) {
	// Remove 0x prefix from private key (if present, case-insensitive)
	privateKeyHex = strings.TrimPrefix(strings.ToLower(privateKeyHex), "0x")

	// Parse private key
	privateKey, err := crypto.HexToECDSA(privateKeyHex)
	if err != nil {
		return nil, fmt.Errorf("failed to parse private key: %w", err)
	}

	// Select API URL
	apiURL := hyperliquid.MainnetAPIURL
	if testnet {
		apiURL = hyperliquid.TestnetAPIURL
	}

	// Security enhancement: Implement Agent Wallet best practices
	// Reference: https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/nonces-and-api-wallets
	agentAddr := crypto.PubkeyToAddress(*privateKey.Public().(*ecdsa.PublicKey)).Hex()

	if walletAddr == "" {
		return nil, fmt.Errorf("❌ Configuration error: Main wallet address (hyperliquid_wallet_addr) not provided\n" +
			"🔐 Correct configuration pattern:\n" +
			"  1. hyperliquid_private_key = Agent Private Key (for signing only, balance should be ~0)\n" +
			"  2. hyperliquid_wallet_addr = Main Wallet Address (holds funds, never expose private key)\n" +
			"💡 Please create an Agent Wallet on Hyperliquid official website and authorize it before configuration:\n" +
			"   https://app.hyperliquid.xyz/ → Settings → API Wallets")
	}

	// Check if user accidentally uses main wallet private key (security risk)
	if strings.EqualFold(walletAddr, agentAddr) {
		logger.Infof("⚠️⚠️⚠️ WARNING: Main wallet address (%s) matches Agent wallet address!", walletAddr)
		logger.Infof("   This indicates you may be using your main wallet private key, which poses extremely high security risks!")
		logger.Infof("   Recommendation: Immediately create a separate Agent Wallet on Hyperliquid official website")
		logger.Infof("   Reference: https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/nonces-and-api-wallets")
	} else {
		logger.Infof("✓ Using Agent Wallet mode (secure)")
		logger.Infof("  └─ Agent wallet address: %s (for signing)", agentAddr)
		logger.Infof("  └─ Main wallet address: %s (holds funds)", walletAddr)
	}

	ctx := context.Background()

	// Create Exchange client (Exchange includes Info functionality).
	// v0.36 signature: ctx, privateKey, baseURL, meta, vaultAddr, accountAddr,
	// spotMeta, perpDexs, ...opts. nil values are auto-fetched on first use.
	// v0.36 fixed the spot-meta indexing panic that crashed earlier versions
	// when Hyperliquid added new spot tokens whose Tokens[0] index pointed
	// past the Tokens array end.
	// The constructor still panics if any auto-fetch fails, so it runs inside
	// initExchangeClient which converts the panic into a returned error.
	exchange, err := initExchangeClient(func() *hyperliquid.Exchange {
		return hyperliquid.NewExchange(
			ctx,
			privateKey,
			apiURL,
			nil,        // Meta — fetched automatically
			"",         // vault address (empty for personal account)
			walletAddr, // wallet address
			nil,        // SpotMeta — fetched automatically
			nil,        // perpDexs — fetched automatically
		)
	})
	if err != nil {
		return nil, fmt.Errorf("Hyperliquid API is temporarily unavailable, please retry: %w", err)
	}

	logger.Infof("✓ Hyperliquid trader initialized successfully (testnet=%v, wallet=%s)", testnet, walletAddr)

	// Get meta information (including precision and other configurations)
	meta, err := exchange.Info().Meta(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to get meta information: %w", err)
	}

	// Security check: Validate Agent wallet balance (should be close to 0)
	// Only check if using separate Agent wallet (not when main wallet is used as agent)
	if !strings.EqualFold(walletAddr, agentAddr) {
		agentState, err := exchange.Info().UserState(ctx, agentAddr)
		if err == nil && agentState != nil && agentState.CrossMarginSummary.AccountValue != "" {
			// Parse Agent wallet balance
			agentBalance, _ := strconv.ParseFloat(agentState.CrossMarginSummary.AccountValue, 64)

			if agentBalance > 100 {
				// Critical: Agent wallet holds too much funds
				logger.Infof("🚨🚨🚨 CRITICAL SECURITY WARNING 🚨🚨🚨")
				logger.Infof("   Agent wallet balance: %.2f USDC (exceeds safe threshold of 100 USDC)", agentBalance)
				logger.Infof("   Agent wallet address: %s", agentAddr)
				logger.Infof("   ⚠️  Agent wallets should only be used for signing and hold minimal/zero balance")
				logger.Infof("   ⚠️  High balance in Agent wallet poses security risks")
				logger.Infof("   📖 Reference: https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/nonces-and-api-wallets")
				logger.Infof("   💡 Recommendation: Transfer funds to main wallet and keep Agent wallet balance near 0")
				return nil, fmt.Errorf("security check failed: Agent wallet balance too high (%.2f USDC), exceeds 100 USDC threshold", agentBalance)
			} else if agentBalance > 10 {
				// Warning: Agent wallet has some balance (acceptable but not ideal)
				logger.Infof("⚠️  Notice: Agent wallet address (%s) has some balance: %.2f USDC", agentAddr, agentBalance)
				logger.Infof("   While not critical, it's recommended to keep Agent wallet balance near 0 for security")
			} else {
				// OK: Agent wallet balance is safe
				logger.Infof("✓ Agent wallet balance is safe: %.2f USDC (near zero as recommended)", agentBalance)
			}
		} else if err != nil {
			// Failed to query agent balance - log warning but don't block initialization
			logger.Infof("⚠️  Could not verify Agent wallet balance (query failed): %v", err)
			logger.Infof("   Proceeding with initialization, but please manually verify Agent wallet balance is near 0")
		}
	}

	if unifiedAccount {
		logger.Infof("✓ Unified Account mode enabled: Spot USDC will be used as collateral for Perp trading")
	}

	return &HyperliquidTrader{
		exchange:         exchange,
		ctx:              ctx,
		walletAddr:       walletAddr,
		meta:             meta,
		isCrossMargin:    true,           // Use cross margin mode by default
		isUnifiedAccount: unifiedAccount, // Unified Account: Spot as Perp collateral
		privateKey:       privateKey,
		isTestnet:        testnet,
	}, nil
}

// FormatQuantity formats quantity to correct precision
func (t *HyperliquidTrader) FormatQuantity(symbol string, quantity float64) (string, error) {
	coin := convertSymbolToHyperliquid(symbol)
	szDecimals := t.getSzDecimals(coin)

	// Format quantity using szDecimals
	formatStr := fmt.Sprintf("%%.%df", szDecimals)
	return fmt.Sprintf(formatStr, quantity), nil
}

// getSzDecimals gets quantity precision for coin
func (t *HyperliquidTrader) getSzDecimals(coin string) int {
	// Concurrency safe: Use read lock to protect meta field access
	t.metaMutex.RLock()
	defer t.metaMutex.RUnlock()

	if t.meta == nil {
		logger.Infof("⚠️  meta information is empty, using default precision 4")
		return 4 // Default precision
	}

	// Find corresponding coin in meta.Universe
	for _, asset := range t.meta.Universe {
		if asset.Name == coin {
			return asset.SzDecimals
		}
	}

	logger.Infof("⚠️  Precision information not found for %s, using default precision 4", coin)
	return 4 // Default precision
}

// roundToSzDecimals rounds quantity to correct precision
func (t *HyperliquidTrader) roundToSzDecimals(coin string, quantity float64) float64 {
	szDecimals := t.getSzDecimals(coin)

	// Calculate multiplier (10^szDecimals)
	multiplier := 1.0
	for i := 0; i < szDecimals; i++ {
		multiplier *= 10.0
	}

	// Round
	return float64(int(quantity*multiplier+0.5)) / multiplier
}

// roundPriceToSigfigs rounds price to 5 significant figures
// Hyperliquid requires prices to use 5 significant figures
func (t *HyperliquidTrader) roundPriceToSigfigs(price float64) float64 {
	if price == 0 {
		return 0
	}

	const sigfigs = 5 // Hyperliquid standard: 5 significant figures

	// Calculate price magnitude
	var magnitude float64
	if price < 0 {
		magnitude = -price
	} else {
		magnitude = price
	}

	// Calculate required multiplier
	multiplier := 1.0
	for magnitude >= 10 {
		magnitude /= 10
		multiplier /= 10
	}
	for magnitude < 1 {
		magnitude *= 10
		multiplier *= 10
	}

	// Apply significant figures precision
	for i := 0; i < sigfigs-1; i++ {
		multiplier *= 10
	}

	// Round
	rounded := float64(int(price*multiplier+0.5)) / multiplier
	return rounded
}
