package bybit

import (
	"encoding/json"
	"fmt"
	"io"
	"math"
	"net/http"
	"nofx/logger"
	"strconv"
	"strings"
	"sync"
	"time"

	bybit "github.com/bybit-exchange/bybit.go.api"
)

// BybitTrader Bybit USDT Perpetual Futures Trader
type BybitTrader struct {
	client    *bybit.Client
	apiKey    string
	secretKey string

	// Balance cache
	cachedBalance     map[string]interface{}
	balanceCacheTime  time.Time
	balanceCacheMutex sync.RWMutex

	// Position cache
	cachedPositions     []map[string]interface{}
	positionsCacheTime  time.Time
	positionsCacheMutex sync.RWMutex

	// Trading pair precision cache (symbol -> qtyStep)
	qtyStepCache      map[string]float64
	qtyStepCacheMutex sync.RWMutex

	// Cache duration (15 seconds)
	cacheDuration time.Duration
}

// NewBybitTrader creates a Bybit trader
func NewBybitTrader(apiKey, secretKey string) *BybitTrader {
	const src = "Up000938"

	client := bybit.NewBybitHttpClient(apiKey, secretKey, bybit.WithBaseURL(bybit.MAINNET))

	// Set HTTP transport. Use a dedicated client instead of mutating the
	// SDK default (http.DefaultClient): mutating it would leak the referer
	// header to every other request in the process, and the default client
	// has no timeout, so a hung connection would stall the trading loop.
	if client != nil {
		defaultTransport := http.DefaultTransport
		if client.HTTPClient != nil && client.HTTPClient != http.DefaultClient && client.HTTPClient.Transport != nil {
			defaultTransport = client.HTTPClient.Transport
		}

		client.HTTPClient = &http.Client{
			Timeout: 30 * time.Second,
			Transport: &headerRoundTripper{
				base:      defaultTransport,
				refererID: src,
			},
		}
	}

	trader := &BybitTrader{
		client:        client,
		apiKey:        apiKey,
		secretKey:     secretKey,
		cacheDuration: 15 * time.Second,
		qtyStepCache:  make(map[string]float64),
	}

	logger.Infof("🔵 [Bybit] Trader initialized")

	return trader
}

// headerRoundTripper HTTP RoundTripper for adding custom headers
type headerRoundTripper struct {
	base      http.RoundTripper
	refererID string
}

func (h *headerRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	req.Header.Set("Referer", h.refererID)
	return h.base.RoundTrip(req)
}

// getQtyStep retrieves the quantity step for a trading pair
func (t *BybitTrader) getQtyStep(symbol string) float64 {
	// Check cache first
	t.qtyStepCacheMutex.RLock()
	if step, ok := t.qtyStepCache[symbol]; ok {
		t.qtyStepCacheMutex.RUnlock()
		return step
	}
	t.qtyStepCacheMutex.RUnlock()

	// Call public API directly to get contract information
	url := fmt.Sprintf("https://api.bybit.com/v5/market/instruments-info?category=linear&symbol=%s", symbol)
	resp, err := http.Get(url)
	if err != nil {
		logger.Infof("⚠️ [Bybit] Failed to get precision info for %s: %v", symbol, err)
		return 1 // Default to integer
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return 1
	}

	var result struct {
		RetCode int `json:"retCode"`
		Result  struct {
			List []struct {
				LotSizeFilter struct {
					QtyStep string `json:"qtyStep"`
				} `json:"lotSizeFilter"`
			} `json:"list"`
		} `json:"result"`
	}

	if err := json.Unmarshal(body, &result); err != nil {
		return 1
	}

	if result.RetCode != 0 || len(result.Result.List) == 0 {
		return 1
	}

	qtyStep, _ := strconv.ParseFloat(result.Result.List[0].LotSizeFilter.QtyStep, 64)
	if qtyStep <= 0 {
		qtyStep = 1
	}

	// Cache result
	t.qtyStepCacheMutex.Lock()
	t.qtyStepCache[symbol] = qtyStep
	t.qtyStepCacheMutex.Unlock()

	logger.Infof("🔵 [Bybit] %s qtyStep: %v", symbol, qtyStep)

	return qtyStep
}

// FormatQuantity formats quantity
func (t *BybitTrader) FormatQuantity(symbol string, quantity float64) (string, error) {
	// Get qtyStep for this symbol
	qtyStep := t.getQtyStep(symbol)

	// Align quantity according to qtyStep (round down to nearest step)
	alignedQty := math.Floor(quantity/qtyStep) * qtyStep

	// Calculate required decimal places
	decimals := 0
	if qtyStep < 1 {
		stepStr := strconv.FormatFloat(qtyStep, 'f', -1, 64)
		if idx := strings.Index(stepStr, "."); idx >= 0 {
			decimals = len(stepStr) - idx - 1
		}
	}

	// Format
	format := fmt.Sprintf("%%.%df", decimals)
	formatted := fmt.Sprintf(format, alignedQty)

	return formatted, nil
}

// Helper methods

func (t *BybitTrader) clearCache() {
	t.balanceCacheMutex.Lock()
	t.cachedBalance = nil
	t.balanceCacheMutex.Unlock()

	t.positionsCacheMutex.Lock()
	t.cachedPositions = nil
	t.positionsCacheMutex.Unlock()
}

func (t *BybitTrader) parseOrderResult(result *bybit.ServerResponse) (map[string]interface{}, error) {
	if result.RetCode != 0 {
		return nil, fmt.Errorf("order placement failed: %s", result.RetMsg)
	}

	resultData, ok := result.Result.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("return format error")
	}

	orderId, _ := resultData["orderId"].(string)

	return map[string]interface{}{
		"orderId": orderId,
		"status":  "NEW",
	}, nil
}
