package main

import (
	"encoding/json"
	"math/big"
	"os"
	"strings"
	"testing"

	"github.com/ethereum/go-ethereum/accounts/abi"
	"github.com/ethereum/go-ethereum/common"
)

func prints(rates ...string) []Print {
	ps := make([]Print, len(rates))
	for i, r := range rates {
		ps[i] = Print{Key: string(rune('a' + i)), Time: int64(i), Rate: r}
	}
	return ps
}

func TestDecide(t *testing.T) {
	for _, c := range []struct {
		name       string
		mode, want uint8
		ps         []Print
	}{
		{"hedged all negative -> flat", Hedged, Flat, prints("-3", "-1", "-0.5")},
		{"hedged mixed -> stay", Hedged, Hedged, prints("-3", "2", "-1")},
		{"hedged zero print -> stay", Hedged, Hedged, prints("-3", "0")},
		{"hedged empty -> stay", Hedged, Hedged, nil},
		{"hedged positive -> stay", Hedged, Hedged, prints("4")},
		{"flat latest positive -> hedged", Flat, Hedged, prints("-5", "-2", "1")},
		{"flat latest negative -> stay", Flat, Flat, prints("5", "-1")},
		{"flat latest zero -> stay", Flat, Flat, prints("5", "0")},
		{"flat empty -> stay", Flat, Flat, nil},
	} {
		if got := Decide(c.mode, c.ps); got != c.want {
			t.Errorf("%s: got %d", c.name, got)
		}
	}
}

func TestParseGateway(t *testing.T) {
	ps, err := parseGateway([]byte(`[{"time":90,"rate":"-1","block":7,"logIndex":2},{"time":100,"rate":"-4","block":9,"logIndex":0}]`))
	if err != nil || len(ps) != 2 || ps[1] != (Print{Key: "9:0", Time: 100, Rate: "-4"}) {
		t.Fatalf("%+v %v", ps, err)
	}
	if ps, err := parseGateway([]byte(`null`)); err != nil || len(ps) != 0 {
		t.Fatalf("null: %+v %v", ps, err)
	}
	if _, err := parseGateway([]byte(`[{"time":1,"rate":"x"}]`)); err == nil {
		t.Fatal("bad rate accepted")
	}
}

func TestArkiv(t *testing.T) {
	var req struct {
		Method string
		Params []json.RawMessage
	}
	if err := json.Unmarshal(arkivBody(2, 1700, "0xAbC0000000000000000000000000000000000001"), &req); err != nil {
		t.Fatal(err)
	}
	var q string
	json.Unmarshal(req.Params[0], &q)
	want := "arkiv.schema = str('perpvault.FundingRate') AND market = str('HYPE') AND ts >= u64(1700) AND $owner = addr(0xabc0000000000000000000000000000000000001) ORDER BY ts:u64 DESC"
	if req.Method != "arkiv_query" || q != want || !strings.Contains(string(req.Params[1]), `"limit":"0x64"`) {
		t.Fatalf("%s %s %s", req.Method, q, req.Params[1])
	}

	body, err := os.ReadFile("../fixtures/arkiv-negative.json")
	if err != nil {
		t.Fatal(err)
	}
	ps, err := parseArkiv(body)
	if err != nil || len(ps) != 3 || ps[0].Time != 0x6900e000 || ps[2].Rate != "-12" || ps[2].Key != "0x03" || ps[0].Rate != "-1" {
		t.Fatalf("%+v %v", ps, err)
	}
	if Decide(Hedged, ps) != Flat {
		t.Fatal("negative fixture should flatten")
	}
	if decRate("0x0de0b6b3a7640000") != "1000000000000000000" {
		t.Fatal("positive hex dec")
	}
	if _, err := parseArkiv([]byte(`{"error":{"code":-32001,"message":"parse"}}`)); err == nil {
		t.Fatal("rpc error ignored")
	}
	full := `{"result":{"data":[` + strings.Repeat(`{"key":"0x1","attributes":[{"name":"rate","type":"dec","value":"-1"}]},`, 99) +
		`{"key":"0x1","attributes":[{"name":"rate","type":"dec","value":"-1"}]}]}}`
	if _, err := parseArkiv([]byte(full)); err == nil {
		t.Fatal("full page accepted")
	}
}

// decodeReport undoes abi.encode(uint8 kind, bytes payload) + the kind's payload, as the vault does.
func decodeReport(t *testing.T, b []byte) (uint8, []any) {
	t.Helper()
	outer, err := reportArgs.Unpack(b)
	if err != nil {
		t.Fatal(err)
	}
	kind, payload := outer[0].(uint8), outer[1].([]byte)
	args := lpArgs
	if kind == 0 {
		u8x3, _ := abi.NewType("uint8[3]", "", nil)
		args = abi.Arguments{{Type: u8x3}, {Type: tB32}}
	}
	vs, err := args.Unpack(payload)
	if err != nil {
		t.Fatal(err)
	}
	return kind, vs
}

func TestEncodeReport(t *testing.T) {
	b, err := encodeReport([markets]uint8{1, 0, 1}, common.Hash{0xaa})
	if err != nil {
		t.Fatal(err)
	}
	// head(kind, offset) + len + payload(3 modes + ref)
	if len(b) != 3*32+4*32 || b[31] != 0 || b[63] != 0x40 || b[95] != 4*32 {
		t.Fatalf("%x", b)
	}
	kind, vs := decodeReport(t, b)
	if kind != 0 || vs[0].([3]uint8) != [3]uint8{1, 0, 1} || vs[1].([32]byte) != [32]byte{0xaa} {
		t.Fatalf("%d %v", kind, vs)
	}
}

func bi(v int64) *big.Int { return big.NewInt(v) }

func TestParseReceived(t *testing.T) {
	body, err := os.ReadFile("../fixtures/lp/funding")
	if err != nil {
		t.Fatal(err)
	}
	// window [1700000000000, 1700003600000): rows 9..12; received = 0.4 + 1.2 + 0.8 (row 10 is paid, row 8 is before)
	r, err := parseReceived(body, 1_700_000_000_000, 1_700_003_600_000)
	if err != nil || r.Int64() != 2_400_000 {
		t.Fatalf("%v %v", r, err)
	}
	if r, err := parseReceived([]byte(`[]`), 0, 1); err != nil || r.Sign() != 0 {
		t.Fatalf("empty: %v %v", r, err)
	}
	full := "[" + strings.Repeat(`{"payment":"-1","time":5},`, 99) + `{"payment":"-1","time":5}]`
	if _, err := parseReceived([]byte(full), 0, 10); err == nil {
		t.Fatal("full page inside window accepted")
	}
	if _, err := parseReceived([]byte(`[{"payment":"x","time":1}]`), 0, 10); err == nil {
		t.Fatal("bad payment accepted")
	}
}

func TestLpSplit(t *testing.T) {
	sum := func(s [markets]*big.Int) int64 { return s[0].Int64() + s[1].Int64() + s[2].Int64() }
	for _, c := range []struct {
		name              string
		vol               [markets]*big.Int
		accrued, received int64
		wantFrom          int64
		wantShares        [markets]int64
	}{
		// cap = 1e9*30*12000/1e8 = 3.6e6; 10% of 2.4e6 = 240k under cap; pot 1.24e6 split 5:3:2
		{"proportional", [markets]*big.Int{bi(500e6), bi(300e6), bi(200e6)}, 1_000_000, 2_400_000, 240_000, [markets]int64{620_000, 372_000, 248_000}},
		// cap = 1e6*30*12000/1e8 = 3600 < 10% of 1e6
		{"capped", [markets]*big.Int{bi(1e6), bi(0), bi(0)}, 0, 1_000_000, 3_600, [markets]int64{3_600, 0, 0}},
		// no volume -> cap 0, accrued split evenly, remainder to market 0
		{"no volume even", [markets]*big.Int{bi(0), bi(0), bi(0)}, 100, 5_000_000, 0, [markets]int64{34, 33, 33}},
		// floors 333333, 333333, 333333 -> +1 to market 0
		{"remainder", [markets]*big.Int{bi(1e9), bi(1e9), bi(1e9)}, 1_000_000, 0, 0, [markets]int64{333_334, 333_333, 333_333}},
		{"empty pot", [markets]*big.Int{bi(0), bi(0), bi(0)}, 0, 0, 0, [markets]int64{0, 0, 0}},
	} {
		s, from := lpSplit(c.vol, bi(c.accrued), bi(c.received), 1000, 30, 12_000)
		got := [markets]int64{s[0].Int64(), s[1].Int64(), s[2].Int64()}
		if from.Int64() != c.wantFrom || got != c.wantShares || sum(s) != c.accrued+from.Int64() {
			t.Errorf("%s: from %v shares %v", c.name, from, got)
		}
	}
}

func TestEncodeLpReport(t *testing.T) {
	shares := [markets]*big.Int{bi(620_000), bi(372_000), bi(248_000)}
	b, err := encodeLpReport(7, shares, bi(240_000), common.Hash{0xbb})
	if err != nil {
		t.Fatal(err)
	}
	if len(b) != 3*32+6*32 || b[31] != 1 {
		t.Fatalf("%x", b)
	}
	kind, vs := decodeReport(t, b)
	got := vs[1].([3]*big.Int)
	if kind != 1 || vs[0].(uint64) != 7 || got[0].Int64() != 620_000 || got[2].Int64() != 248_000 ||
		vs[2].(*big.Int).Int64() != 240_000 || vs[3].([32]byte) != [32]byte{0xbb} {
		t.Fatalf("%d %v", kind, vs)
	}
}
