// Package coinswitchfutures — single-file Go client for the
// CoinSwitch PRO Futures REST API.
//
// Exchange is always "EXCHANGE_2" (kept opaque). Symbols are unseparated
// (BTCUSDT, DOGEUSDT).
package coinswitchfutures

import (
	"bytes"
	"crypto/ed25519"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/google/uuid"
)

const (
	DefaultBaseURL = "https://coinswitch.co"
	Exchange       = "EXCHANGE_2"
)

type Client struct {
	APIKey, SecretKey, BaseURL string
	HTTP                       *http.Client
	priv                       ed25519.PrivateKey
}

func NewClient(apiKey, secretKey string) *Client {
	seed, err := hex.DecodeString(secretKey)
	if err != nil {
		panic(err)
	}
	return &Client{
		APIKey: apiKey, SecretKey: secretKey,
		BaseURL: DefaultBaseURL,
		HTTP:    &http.Client{Timeout: 10 * time.Second},
		priv:    ed25519.NewKeyFromSeed(seed),
	}
}

func (c *Client) sign(method, path string, params map[string]string) (
	headers map[string]string, decodedPath string, err error) {
	method = strings.ToUpper(method)
	if len(params) > 0 {
		q := url.Values{}
		for k, v := range params {
			q.Set(k, v)
		}
		sep := "?"
		if strings.Contains(path, "?") {
			sep = "&"
		}
		path += sep + q.Encode()
	}
	decodedPath, err = url.QueryUnescape(path)
	if err != nil {
		return nil, "", err
	}
	epoch := strconv.FormatInt(time.Now().UnixMilli(), 10)
	signature := hex.EncodeToString(ed25519.Sign(c.priv, []byte(method+decodedPath+epoch)))
	return map[string]string{
		"Content-Type": "application/json",
		"X-AUTH-APIKEY":    c.APIKey,
		"X-AUTH-SIGNATURE": signature,
		"X-AUTH-EPOCH":     epoch,
	}, decodedPath, nil
}

func (c *Client) request(method, path string,
	params map[string]string, body any) ([]byte, error) {
	headers, decoded, err := c.sign(method, path, params)
	if err != nil {
		return nil, err
	}
	var bodyReader io.Reader
	if body != nil {
		bodyJSON, _ := json.Marshal(body)
		bodyReader = bytes.NewReader(bodyJSON)
	}
	req, _ := http.NewRequest(strings.ToUpper(method), c.BaseURL+decoded, bodyReader)
	for k, v := range headers {
		req.Header.Set(k, v)
	}
	resp, err := c.HTTP.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	out, _ := io.ReadAll(resp.Body)
	if resp.StatusCode >= 400 {
		return out, fmt.Errorf("http %d: %s", resp.StatusCode, string(out))
	}
	return out, nil
}

func withExchange(p map[string]string) map[string]string {
	if p == nil {
		p = map[string]string{}
	}
	p["exchange"] = Exchange
	return p
}

// ---- Account ----
func (c *Client) WalletBalance() ([]byte, error) {
	return c.request("GET", "/trade/api/v2/futures/wallet_balance", nil, nil)
}
func (c *Client) Transactions(filters map[string]string) ([]byte, error) {
	return c.request("GET", "/trade/api/v2/futures/transactions",
		withExchange(filters), nil)
}

// ---- Reference data ----
func (c *Client) InstrumentInfo(symbol string) ([]byte, error) {
	p := map[string]string{"exchange": Exchange}
	if symbol != "" {
		p["symbol"] = symbol
	}
	return c.request("GET", "/trade/api/v2/futures/instrument_info", p, nil)
}
func (c *Client) OrderBook(symbol string) ([]byte, error) {
	return c.request("GET", "/trade/api/v2/futures/order_book",
		map[string]string{"exchange": Exchange, "symbol": symbol}, nil)
}
func (c *Client) Ticker(symbol string) ([]byte, error) {
	return c.request("GET", "/trade/api/v2/futures/ticker",
		map[string]string{"exchange": Exchange, "symbol": symbol}, nil)
}
func (c *Client) AllPairsTicker() ([]byte, error) {
	return c.request("GET", "/trade/api/v2/futures/all-pairs/ticker",
		map[string]string{"exchange": Exchange}, nil)
}
func (c *Client) Klines(symbol, interval string,
	startTime, endTime int64) ([]byte, error) {
	return c.request("GET", "/trade/api/v2/futures/klines",
		map[string]string{
			"exchange": Exchange, "symbol": symbol,
			"interval":   interval,
			"start_time": strconv.FormatInt(startTime, 10),
			"end_time":   strconv.FormatInt(endTime, 10),
		}, nil)
}
func (c *Client) MarketTrades(symbol string) ([]byte, error) {
	return c.request("GET", "/trade/api/v2/futures/trades",
		map[string]string{"exchange": Exchange, "symbol": symbol}, nil)
}

// ---- Orders ----
// PlaceOrder body must include symbol, side, order_type, quantity. We
// auto-mint client_order_id if missing (idempotency key for retries).
func (c *Client) PlaceOrder(body map[string]any) ([]byte, error) {
	if _, ok := body["exchange"]; !ok {
		body["exchange"] = Exchange
	}
	if _, ok := body["client_order_id"]; !ok {
		body["client_order_id"] = uuid.NewString()
	}
	return c.request("POST", "/trade/api/v2/futures/order", nil, body)
}
func (c *Client) CancelOrder(orderID string) ([]byte, error) {
	return c.request("DELETE", "/trade/api/v2/futures/order", nil,
		map[string]any{"exchange": Exchange, "order_id": orderID})
}
func (c *Client) CancelAllOpenOrders(symbol string) ([]byte, error) {
	body := map[string]any{"exchange": Exchange}
	if symbol != "" {
		body["symbol"] = symbol
	}
	return c.request("POST", "/trade/api/v2/futures/cancel-all", nil, body)
}
func (c *Client) GetOrderStatus(orderID string) ([]byte, error) {
	return c.request("GET", "/trade/api/v2/futures/order",
		map[string]string{"exchange": Exchange, "order_id": orderID}, nil)
}
func (c *Client) OpenOrders(symbol string) ([]byte, error) {
	body := map[string]any{"exchange": Exchange}
	if symbol != "" {
		body["symbol"] = symbol
	}
	return c.request("POST", "/trade/api/v2/futures/orders/open", nil, body)
}
func (c *Client) ClosedOrders(symbol string) ([]byte, error) {
	body := map[string]any{"exchange": Exchange}
	if symbol != "" {
		body["symbol"] = symbol
	}
	return c.request("POST", "/trade/api/v2/futures/orders/closed", nil, body)
}

// ---- Positions, leverage, margin ----
func (c *Client) Positions(symbol string) ([]byte, error) {
	p := map[string]string{"exchange": Exchange}
	if symbol != "" {
		p["symbol"] = symbol
	}
	return c.request("GET", "/trade/api/v2/futures/positions", p, nil)
}
func (c *Client) GetLeverage(symbol string) ([]byte, error) {
	return c.request("GET", "/trade/api/v2/futures/leverage",
		map[string]string{"exchange": Exchange, "symbol": symbol}, nil)
}
func (c *Client) UpdateLeverage(symbol string, leverage int) ([]byte, error) {
	return c.request("POST", "/trade/api/v2/futures/leverage", nil,
		map[string]any{"exchange": Exchange, "symbol": symbol,
			"leverage": leverage})
}
func (c *Client) AddMargin(symbol string, margin float64) ([]byte, error) {
	return c.request("POST", "/trade/api/v2/futures/add_margin", nil,
		map[string]any{"exchange": Exchange, "symbol": symbol,
			"margin": margin})
}
