package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"math/big"
	"strconv"
	"strings"
	"time"
)

// Vault modes (IDeltaNeutralVault.Mode).
const (
	Flat   uint8 = 0
	Hedged uint8 = 1
)

// Print is one funding-rate print. Key identifies the source record (Arkiv entity key or gateway block:logIndex).
type Print struct {
	Key  string `json:"key"`
	Time int64  `json:"time"`
	Rate string `json:"rate"`
}

// Decide is the rebalance rule. prints = the window, oldest first.
func Decide(mode uint8, prints []Print) uint8 {
	if len(prints) == 0 {
		return mode
	}
	if mode == Hedged {
		for _, p := range prints {
			if sign(p.Rate) >= 0 {
				return mode
			}
		}
		return Flat
	}
	if mode == Flat && sign(prints[len(prints)-1].Rate) > 0 {
		return Hedged
	}
	return mode
}

func sign(rate string) int {
	r, _ := new(big.Rat).SetString(rate) // validated by the parsers
	return r.Sign()
}

func checkRate(s string) error {
	if _, ok := new(big.Rat).SetString(s); !ok {
		return fmt.Errorf("bad rate %q", s)
	}
	return nil
}

// gatewayURL is GET /funding/{market}?window= on our gateway.
func gatewayURL(base string, market int, window time.Duration) string {
	return fmt.Sprintf("%s/funding/%d?window=%s", strings.TrimRight(base, "/"), market, window)
}

// parseGateway reads the gateway's [{time, rate, block, logIndex}], oldest first; the gateway applies the window.
func parseGateway(body []byte) ([]Print, error) {
	var rows []struct {
		Time     int64  `json:"time"`
		Rate     string `json:"rate"`
		Block    uint64 `json:"block"`
		LogIndex uint32 `json:"logIndex"`
	}
	if err := json.Unmarshal(body, &rows); err != nil {
		return nil, fmt.Errorf("gateway: %w", err)
	}
	out := []Print{}
	for _, r := range rows {
		if err := checkRate(r.Rate); err != nil {
			return nil, err
		}
		out = append(out, Print{Key: fmt.Sprintf("%d:%d", r.Block, r.LogIndex), Time: r.Time, Rate: r.Rate})
	}
	return out, nil
}

// arkivLimit is arkiv_query's max page size.
const arkivLimit = 100

// symbols are the recorder's market strings by vault market id.
var symbols = [markets]string{"BTC", "ETH", "HYPE"}

// arkivBody is the arkiv_query request for one market's FundingRate entities since `since`, newest first.
// Only entities owned by the recorder count: anyone can write any attributes.
func arkivBody(market int, since int64, owner string) []byte {
	q := fmt.Sprintf("arkiv.schema = str('perpvault.FundingRate') AND market = str('%s') AND ts >= u64(%d) AND $owner = addr(%s) ORDER BY ts:u64 DESC",
		symbols[market], since, strings.ToLower(owner))
	b, _ := json.Marshal(map[string]any{
		"jsonrpc": "2.0", "id": 1, "method": "arkiv_query",
		"params": []any{q, map[string]any{"select": map[string]bool{"key": true, "attributes": true}, "limit": fmt.Sprintf("0x%x", arkivLimit)}},
	})
	return b
}

// decRate turns a 0x dec (int256 two's complement) into a decimal string; decimals pass through.
func decRate(s string) string {
	n, ok := new(big.Int).SetString(strings.TrimPrefix(s, "0x"), 16)
	if !strings.HasPrefix(s, "0x") || !ok {
		return s
	}
	if n.Bit(255) == 1 {
		n.Sub(n, new(big.Int).Lsh(big.NewInt(1), 256))
	}
	return n.String()
}

// parseArkiv reads an arkiv_query response into prints, oldest first.
func parseArkiv(body []byte) ([]Print, error) {
	var res struct {
		Result *struct {
			Data []struct {
				Key        string `json:"key"`
				Attributes []struct {
					Name  string          `json:"name"`
					Value json.RawMessage `json:"value"`
				} `json:"attributes"`
			} `json:"data"`
		} `json:"result"`
		Error *struct {
			Code    int    `json:"code"`
			Message string `json:"message"`
		} `json:"error"`
	}
	if err := json.Unmarshal(body, &res); err != nil {
		return nil, fmt.Errorf("arkiv: %w", err)
	}
	if res.Error != nil {
		return nil, fmt.Errorf("arkiv %d: %s", res.Error.Code, res.Error.Message)
	}
	if res.Result == nil {
		return nil, errors.New("arkiv: no result")
	}
	// ponytail: one page; a full page could hide older prints, so fail rather than judge a partial window
	if len(res.Result.Data) >= arkivLimit {
		return nil, fmt.Errorf("arkiv: window has >= %d prints, shorten window", arkivLimit)
	}
	out := make([]Print, len(res.Result.Data))
	for i, e := range res.Result.Data {
		p := Print{Key: e.Key}
		for _, a := range e.Attributes {
			var s string
			if json.Unmarshal(a.Value, &s) != nil {
				continue // only rate (dec) and ts (u64 hex) are read, both JSON strings
			}
			switch a.Name {
			case "rate":
				p.Rate = decRate(s)
			case "ts":
				t, err := strconv.ParseUint(strings.TrimPrefix(s, "0x"), 16, 63)
				if err != nil {
					return nil, fmt.Errorf("arkiv %s: bad time %q", e.Key, s)
				}
				p.Time = int64(t)
			}
		}
		if err := checkRate(p.Rate); err != nil {
			return nil, fmt.Errorf("arkiv %s: %w", e.Key, err)
		}
		out[len(out)-1-i] = p // newest first -> oldest first
	}
	return out, nil
}
