package zrpc

import (
	"encoding/json"
	"os"
	"strconv"
	"strings"
	"testing"
	"time"

	ini "gopkg.in/ini.v1"
)

// Live tests against a local dragonxd. Skipped unless ZRPC_CONF points at a
// DRAGONX.conf, so `go test ./...` stays hermetic.
func liveClient(t *testing.T, timeout time.Duration) *Client {
	t.Helper()
	conf := os.Getenv("ZRPC_CONF")
	if conf == "" {
		t.Skip("ZRPC_CONF not set; skipping live RPC test")
	}
	cfg, err := ini.Load(conf)
	if err != nil {
		t.Fatalf("load conf: %v", err)
	}
	k := func(n string) string { return cfg.Section("").Key(n).String() }
	return New(k("rpcbind")+":"+k("rpcport"), k("rpcuser"), k("rpcpassword"), timeout, 8)
}

func TestLiveSuccess(t *testing.T) {
	c := liveClient(t, 30*time.Second)
	res, err := c.RawRequest("getblockchaininfo", nil)
	if err != nil {
		t.Fatalf("getblockchaininfo: %v", err)
	}
	var f map[string]interface{}
	if err := json.Unmarshal(res, &f); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}
	if f["chain"] != "main" {
		t.Fatalf("chain = %v, want main", f["chain"])
	}
	t.Logf("ok: chain=%v blocks=%v", f["chain"], f["blocks"])
}

// The error string must stay "<code>: <message>" -- common.GetSaplingInfo
// recovers the numeric code with strings.SplitN(err.Error(), ":", 2).
func TestLiveErrorStringShape(t *testing.T) {
	c := liveClient(t, 30*time.Second)
	p := []json.RawMessage{json.RawMessage(`"99999999"`)}
	_, err := c.RawRequest("getblock", p)
	if err == nil {
		t.Fatal("expected an error for an out-of-range height")
	}
	parts := strings.SplitN(err.Error(), ":", 2)
	code, perr := strconv.ParseInt(parts[0], 10, 32)
	if perr != nil {
		t.Fatalf("error string %q does not start with a numeric code", err.Error())
	}
	if code != -8 {
		t.Logf("note: code %d (expected -8 for a bad height, but any numeric code proves the shape)", code)
	}
	t.Logf("ok: %q -> code %d", err.Error(), code)
}

// A timeout must actually abort the call rather than hanging.
func TestLiveTimeoutFires(t *testing.T) {
	c := liveClient(t, 1*time.Nanosecond)
	start := time.Now()
	_, err := c.RawRequest("getblockchaininfo", nil)
	elapsed := time.Since(start)
	if err == nil {
		t.Fatal("expected a timeout error")
	}
	if elapsed > 5*time.Second {
		t.Fatalf("timeout did not fire promptly: %v", elapsed)
	}
	t.Logf("ok: timed out in %v with %v", elapsed, err)
}

func TestNoMethod(t *testing.T) {
	c := New("127.0.0.1:1", "u", "p", time.Second, 8)
	if _, err := c.RawRequest("", nil); err == nil || err.Error() != "no method" {
		t.Fatalf(`RawRequest("") = %v, want "no method"`, err)
	}
}
