package hyperliquid

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"nofx/logger"
	"strings"
	"time"
)

// refreshMetaIfNeeded refreshes meta information when invalid (triggered when Asset ID is 0).
//
// NOTE: go-hyperliquid v0.27 renamed `NameToAsset(coin) int` to
// `CoinToAsset(coin) (int, bool)` — the second return is `ok` for whether
// the coin exists in the meta cache. We treat `!ok` (or zero asset for a
// perp) as "need refresh" the same way the old code did.
func (t *HyperliquidTrader) refreshMetaIfNeeded(coin string) error {
	if assetID, ok := t.exchange.Info().CoinToAsset(coin); ok && assetID != 0 {
		return nil // Meta is normal, no refresh needed
	}

	logger.Infof("⚠️  Asset ID for %s is 0, attempting to refresh Meta information...", coin)

	// Refresh Meta information
	meta, err := t.exchange.Info().Meta(t.ctx)
	if err != nil {
		return fmt.Errorf("failed to refresh Meta information: %w", err)
	}

	// Concurrency safe: Use write lock to protect meta field update
	t.metaMutex.Lock()
	t.meta = meta
	t.metaMutex.Unlock()

	logger.Infof("✅ Meta information refreshed, contains %d assets", len(meta.Universe))

	// Verify Asset ID after refresh
	assetID, ok := t.exchange.Info().CoinToAsset(coin)
	if !ok || assetID == 0 {
		return fmt.Errorf("❌ Even after refreshing Meta, Asset ID for %s is still 0. Possible reasons:\n"+
			"  1. This coin is not listed on Hyperliquid\n"+
			"  2. Coin name is incorrect (should be BTC not BTCUSDT)\n"+
			"  3. API connection issue", coin)
	}

	logger.Infof("✅ Asset ID check passed after refresh: %s -> %d", coin, assetID)
	return nil
}

// fetchXyzMeta fetches metadata for xyz dex assets (stocks, forex, commodities)
func (t *HyperliquidTrader) fetchXyzMeta() error {
	// Build request for xyz dex meta
	reqBody := map[string]string{
		"type": "meta",
		"dex":  "xyz",
	}

	jsonBody, err := json.Marshal(reqBody)
	if err != nil {
		return fmt.Errorf("failed to marshal request: %w", err)
	}

	apiURL := "https://api.hyperliquid.xyz/info"

	req, err := http.NewRequestWithContext(t.ctx, "POST", apiURL, bytes.NewBuffer(jsonBody))
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 30 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to execute request: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("failed to read response: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("xyz dex meta API error (status %d): %s", resp.StatusCode, string(body))
	}

	var meta xyzDexMeta
	if err := json.Unmarshal(body, &meta); err != nil {
		return fmt.Errorf("failed to parse response: %w", err)
	}

	t.xyzMetaMutex.Lock()
	t.xyzMeta = &meta
	t.xyzMetaMutex.Unlock()

	logger.Infof("✅ xyz dex meta fetched, contains %d assets", len(meta.Universe))
	return nil
}

// getXyzSzDecimals gets quantity precision for xyz dex asset
func (t *HyperliquidTrader) getXyzSzDecimals(coin string) int {
	t.xyzMetaMutex.RLock()
	defer t.xyzMetaMutex.RUnlock()

	if t.xyzMeta == nil {
		logger.Infof("⚠️  xyz meta information is empty, using default precision 2")
		return 2 // Default precision for stocks/forex
	}

	// The meta API returns names with xyz: prefix, so ensure we match correctly
	lookupName := coin
	if !strings.HasPrefix(lookupName, "xyz:") {
		lookupName = "xyz:" + lookupName
	}

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

	logger.Infof("⚠️  Precision information not found for %s, using default precision 2", lookupName)
	return 2 // Default precision for stocks/forex
}

// getXyzAssetIndex gets the asset index for an xyz dex asset
func (t *HyperliquidTrader) getXyzAssetIndex(baseCoin string) int {
	t.xyzMetaMutex.RLock()
	defer t.xyzMetaMutex.RUnlock()

	if t.xyzMeta == nil {
		return -1
	}

	// The meta API returns names with xyz: prefix, so ensure we match correctly
	lookupName := baseCoin
	if !strings.HasPrefix(lookupName, "xyz:") {
		lookupName = "xyz:" + lookupName
	}

	for i, asset := range t.xyzMeta.Universe {
		if asset.Name == lookupName {
			return i
		}
	}
	return -1
}
