package api

import (
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"

	"nofx/logger"
	"nofx/store"
	"nofx/trader"
	"nofx/trader/aster"
	"nofx/trader/binance"
	"nofx/trader/bitget"
	"nofx/trader/bybit"
	"nofx/trader/gate"
	hyperliquidtrader "nofx/trader/hyperliquid"
	"nofx/trader/indodax"
	"nofx/trader/kucoin"
	"nofx/trader/lighter"
	"nofx/trader/okx"

	"github.com/gin-gonic/gin"
)

const exchangeAccountStateCacheTTL = 30 * time.Second

const (
	exchangeAccountStatusOK                 = "ok"
	exchangeAccountStatusDisabled           = "disabled"
	exchangeAccountStatusMissingCredentials = "missing_credentials"
	exchangeAccountStatusInvalidCredentials = "invalid_credentials"
	exchangeAccountStatusPermissionDenied   = "permission_denied"
	exchangeAccountStatusUnavailable        = "unavailable"
)

type ExchangeAccountState struct {
	ExchangeID       string    `json:"exchange_id"`
	Status           string    `json:"status"`
	DisplayBalance   string    `json:"display_balance,omitempty"`
	Asset            string    `json:"asset,omitempty"`
	TotalEquity      float64   `json:"total_equity,omitempty"`
	AvailableBalance float64   `json:"available_balance,omitempty"`
	CheckedAt        time.Time `json:"checked_at"`
	ErrorCode        string    `json:"error_code,omitempty"`
	ErrorMessage     string    `json:"error_message,omitempty"`
}

type cachedExchangeAccountStates struct {
	states   map[string]ExchangeAccountState
	cachedAt time.Time
}

type ExchangeAccountStateCache struct {
	entries map[string]cachedExchangeAccountStates
	mu      sync.RWMutex
}

func NewExchangeAccountStateCache() *ExchangeAccountStateCache {
	return &ExchangeAccountStateCache{
		entries: make(map[string]cachedExchangeAccountStates),
	}
}

func (c *ExchangeAccountStateCache) Get(userID string) (map[string]ExchangeAccountState, bool) {
	c.mu.RLock()
	entry, ok := c.entries[userID]
	c.mu.RUnlock()
	if !ok || time.Since(entry.cachedAt) >= exchangeAccountStateCacheTTL {
		return nil, false
	}
	return cloneExchangeAccountStates(entry.states), true
}

func (c *ExchangeAccountStateCache) Set(userID string, states map[string]ExchangeAccountState) {
	c.mu.Lock()
	c.entries[userID] = cachedExchangeAccountStates{
		states:   cloneExchangeAccountStates(states),
		cachedAt: time.Now(),
	}
	c.mu.Unlock()
}

func (c *ExchangeAccountStateCache) Invalidate(userID string) {
	c.mu.Lock()
	delete(c.entries, userID)
	c.mu.Unlock()
}

func cloneExchangeAccountStates(states map[string]ExchangeAccountState) map[string]ExchangeAccountState {
	cloned := make(map[string]ExchangeAccountState, len(states))
	for id, state := range states {
		cloned[id] = state
	}
	return cloned
}

func (s *Server) handleGetExchangeAccountStates(c *gin.Context) {
	userID := c.GetString("user_id")

	states, err := s.getExchangeAccountStates(userID)
	if err != nil {
		SafeInternalError(c, "Failed to get exchange account states", err)
		return
	}

	c.JSON(http.StatusOK, gin.H{"states": states})
}

func (s *Server) getExchangeAccountStates(userID string) (map[string]ExchangeAccountState, error) {
	if cached, ok := s.exchangeAccountStateCache.Get(userID); ok {
		return cached, nil
	}

	exchanges, err := s.store.Exchange().List(userID)
	if err != nil {
		return nil, err
	}

	states := make(map[string]ExchangeAccountState, len(exchanges))
	if len(exchanges) == 0 {
		return states, nil
	}

	var wg sync.WaitGroup
	var mu sync.Mutex

	for _, exchangeCfg := range exchanges {
		exchangeCfg := exchangeCfg
		wg.Add(1)
		go func() {
			defer wg.Done()
			state := probeExchangeAccountState(exchangeCfg, userID)
			mu.Lock()
			states[exchangeCfg.ID] = state
			mu.Unlock()
		}()
	}

	wg.Wait()
	s.exchangeAccountStateCache.Set(userID, states)

	return cloneExchangeAccountStates(states), nil
}

func probeExchangeAccountState(exchangeCfg *store.Exchange, userID string) ExchangeAccountState {
	state := ExchangeAccountState{
		ExchangeID: exchangeCfg.ID,
		CheckedAt:  time.Now().UTC(),
		Asset:      accountAssetForExchange(exchangeCfg.ExchangeType),
	}

	if !exchangeCfg.Enabled {
		state.Status = exchangeAccountStatusDisabled
		state.ErrorCode = "EXCHANGE_DISABLED"
		state.ErrorMessage = "Exchange account is disabled"
		return state
	}

	if status, code, message, missing := missingExchangeCredentials(exchangeCfg); missing {
		state.Status = status
		state.ErrorCode = code
		state.ErrorMessage = message
		return state
	}

	tempTrader, err := buildExchangeProbeTrader(exchangeCfg, userID)
	if err != nil {
		status, code, message := classifyExchangeProbeError(err)
		state.Status = status
		state.ErrorCode = code
		state.ErrorMessage = message
		return state
	}

	balanceInfo, err := tempTrader.GetBalance()
	if err != nil {
		status, code, message := classifyExchangeProbeError(err)
		state.Status = status
		state.ErrorCode = code
		state.ErrorMessage = message
		logger.Infof("⚠️ Failed to probe exchange account %s (%s): %v", exchangeCfg.ID, exchangeCfg.ExchangeType, err)
		return state
	}

	totalEquity, totalFound := extractFirstNumeric(balanceInfo,
		"total_equity", "totalEquity", "totalWalletBalance", "wallet_balance", "totalEq", "balance")
	availableBalance, availableFound := extractFirstNumeric(balanceInfo,
		"available_balance", "availableBalance", "available")

	if !totalFound && availableFound {
		totalEquity = availableBalance
		totalFound = true
	}

	if !availableFound && totalFound {
		availableBalance = totalEquity
		availableFound = true
	}

	if !totalFound && !availableFound {
		state.Status = exchangeAccountStatusUnavailable
		state.ErrorCode = "BALANCE_NOT_FOUND"
		state.ErrorMessage = "Connected but no balance fields were returned"
		return state
	}

	state.Status = exchangeAccountStatusOK
	if totalFound {
		state.TotalEquity = totalEquity
		state.DisplayBalance = formatDisplayBalance(totalEquity, state.Asset)
	}
	if availableFound {
		state.AvailableBalance = availableBalance
		if state.DisplayBalance == "" {
			state.DisplayBalance = formatDisplayBalance(availableBalance, state.Asset)
		}
	}

	return state
}

func buildExchangeProbeTrader(exchangeCfg *store.Exchange, userID string) (trader.Trader, error) {
	switch exchangeCfg.ExchangeType {
	case "binance":
		return binance.NewFuturesTrader(string(exchangeCfg.APIKey), string(exchangeCfg.SecretKey), userID), nil
	case "bybit":
		return bybit.NewBybitTrader(string(exchangeCfg.APIKey), string(exchangeCfg.SecretKey)), nil
	case "okx":
		return okx.NewOKXTrader(string(exchangeCfg.APIKey), string(exchangeCfg.SecretKey), string(exchangeCfg.Passphrase)), nil
	case "bitget":
		return bitget.NewBitgetTrader(string(exchangeCfg.APIKey), string(exchangeCfg.SecretKey), string(exchangeCfg.Passphrase)), nil
	case "gate":
		return gate.NewGateTrader(string(exchangeCfg.APIKey), string(exchangeCfg.SecretKey)), nil
	case "kucoin":
		return kucoin.NewKuCoinTrader(string(exchangeCfg.APIKey), string(exchangeCfg.SecretKey), string(exchangeCfg.Passphrase)), nil
	case "indodax":
		return indodax.NewIndodaxTrader(string(exchangeCfg.APIKey), string(exchangeCfg.SecretKey)), nil
	case "hyperliquid":
		return hyperliquidtrader.NewHyperliquidTrader(
			string(exchangeCfg.APIKey),
			exchangeCfg.HyperliquidWalletAddr,
			exchangeCfg.Testnet,
			exchangeCfg.HyperliquidUnifiedAcct,
		)
	case "aster":
		return aster.NewAsterTrader(
			exchangeCfg.AsterUser,
			exchangeCfg.AsterSigner,
			string(exchangeCfg.AsterPrivateKey),
		)
	case "lighter":
		return lighter.NewLighterTraderV2(
			exchangeCfg.LighterWalletAddr,
			string(exchangeCfg.LighterAPIKeyPrivateKey),
			exchangeCfg.LighterAPIKeyIndex,
			false,
		)
	default:
		return nil, fmt.Errorf("unsupported exchange type: %s", exchangeCfg.ExchangeType)
	}
}

func extractExchangeTotalEquity(balanceInfo map[string]interface{}) (float64, bool) {
	return extractFirstNumeric(balanceInfo,
		"total_equity", "totalEquity", "totalWalletBalance", "wallet_balance", "totalEq", "balance")
}

func extractFirstNumeric(values map[string]interface{}, keys ...string) (float64, bool) {
	for _, key := range keys {
		raw, ok := values[key]
		if !ok {
			continue
		}

		switch v := raw.(type) {
		case float64:
			return v, true
		case float32:
			return float64(v), true
		case int:
			return float64(v), true
		case int64:
			return float64(v), true
		case int32:
			return float64(v), true
		case string:
			parsed, err := strconv.ParseFloat(v, 64)
			if err == nil {
				return parsed, true
			}
		}
	}

	return 0, false
}

func formatDisplayBalance(value float64, asset string) string {
	formatted := strconv.FormatFloat(value, 'f', 4, 64)
	formatted = strings.TrimRight(strings.TrimRight(formatted, "0"), ".")
	if formatted == "" {
		formatted = "0"
	}
	if asset == "" {
		return formatted
	}
	return fmt.Sprintf("%s %s", formatted, asset)
}

func accountAssetForExchange(exchangeType string) string {
	switch exchangeType {
	case "hyperliquid", "aster", "lighter":
		return "USDC"
	default:
		return "USDT"
	}
}

func missingExchangeCredentials(exchangeCfg *store.Exchange) (status string, code string, message string, missing bool) {
	missingFields := store.MissingRequiredExchangeCredentialFields(
		exchangeCfg.ExchangeType,
		string(exchangeCfg.APIKey),
		string(exchangeCfg.SecretKey),
		string(exchangeCfg.Passphrase),
		exchangeCfg.HyperliquidWalletAddr,
		exchangeCfg.AsterUser,
		exchangeCfg.AsterSigner,
		string(exchangeCfg.AsterPrivateKey),
		exchangeCfg.LighterWalletAddr,
		string(exchangeCfg.LighterAPIKeyPrivateKey),
	)
	if len(missingFields) > 0 {
		if len(missingFields) == 1 && missingFields[0] == "exchange_type" {
			return exchangeAccountStatusUnavailable, "UNSUPPORTED_EXCHANGE", "Unsupported exchange type", true
		}
		return exchangeAccountStatusMissingCredentials, "MISSING_REQUIRED_FIELDS", "Missing required fields: " + strings.Join(missingFields, ", "), true
	}

	return "", "", "", false
}

func classifyExchangeProbeError(err error) (status string, code string, message string) {
	if err == nil {
		return exchangeAccountStatusOK, "", ""
	}

	rawMessage := err.Error()
	msg := strings.ToLower(rawMessage)

	switch {
	case strings.Contains(msg, "unsupported exchange type"):
		return exchangeAccountStatusUnavailable, "UNSUPPORTED_EXCHANGE", "Unsupported exchange type"
	case strings.Contains(msg, "requires ") || strings.Contains(msg, "missing") || strings.Contains(msg, "empty"):
		return exchangeAccountStatusMissingCredentials, "MISSING_REQUIRED_FIELDS", "Exchange credentials are incomplete"
	case strings.Contains(msg, "permission") || strings.Contains(msg, "forbidden") || strings.Contains(msg, "no authority") || strings.Contains(msg, "not allowed"):
		return exchangeAccountStatusPermissionDenied, "PERMISSION_DENIED", "Exchange account has no permission to read balances"
	case strings.Contains(msg, "invalid") || strings.Contains(msg, "signature") || strings.Contains(msg, "unauthorized") || strings.Contains(msg, "api key") || strings.Contains(msg, "api-key") || strings.Contains(msg, "auth"):
		return exchangeAccountStatusInvalidCredentials, "INVALID_CREDENTIALS", "Exchange credentials are invalid"
	default:
		return exchangeAccountStatusUnavailable, "EXCHANGE_UNAVAILABLE", limitErrorMessage(rawMessage)
	}
}

func limitErrorMessage(message string) string {
	message = strings.TrimSpace(message)
	if message == "" {
		return "Unable to fetch exchange balance right now"
	}
	if len(message) <= 160 {
		return message
	}
	return message[:157] + "..."
}
