package ipld

import (
	"encoding/json"
	"fmt"
	"os"
	"regexp"
	"testing"
)

/*
  Block INTERFACE
*/
func init() {
	if os.Getenv("MODE") != "statediff" {
		fmt.Println("Skipping statediff test")
		os.Exit(0)
	}
}

func TestAccountSnapshotBlockElements(t *testing.T) {
	eas := prepareEthAccountSnapshot(t)

	if fmt.Sprintf("%x", eas.RawData())[:10] != "f84e808a03" {
		t.Fatal("Wrong Data")
	}

	if eas.Cid().String() !=
		"baglqcgzasckx2alxk43cksshnztjvhfyvbbh6bkp376gtcndm5cg4fkrkhsa" {
		t.Fatal("Wrong Cid")
	}
}

func TestAccountSnapshotString(t *testing.T) {
	eas := prepareEthAccountSnapshot(t)

	if eas.String() !=
		"<EthereumAccountSnapshot baglqcgzasckx2alxk43cksshnztjvhfyvbbh6bkp376gtcndm5cg4fkrkhsa>" {
		t.Fatalf("Wrong String()")
	}
}

func TestAccountSnapshotLoggable(t *testing.T) {
	eas := prepareEthAccountSnapshot(t)

	l := eas.Loggable()
	if _, ok := l["type"]; !ok {
		t.Fatal("Loggable map expected the field 'type'")
	}

	if l["type"] != "eth-account-snapshot" {
		t.Fatalf("Wrong Loggable 'type' value\r\nexpected %s\r\ngot %s", "eth-account-snapshot", l["type"])
	}
}

/*
  Node INTERFACE
*/
func TestAccountSnapshotResolve(t *testing.T) {
	eas := prepareEthAccountSnapshot(t)

	// Empty path
	obj, rest, err := eas.Resolve([]string{})
	reas, ok := obj.(*EthAccountSnapshot)
	if !ok {
		t.Fatalf("Wrong type of returned object\r\nexpected %T\r\ngot %T", &EthAccountSnapshot{}, reas)
	}
	if reas.Cid() != eas.Cid() {
		t.Fatalf("wrong returned CID\r\nexpected %s\r\ngot %s", eas.Cid().String(), reas.Cid().String())
	}
	if rest != nil {
		t.Fatal("rest should be nil")
	}
	if err != nil {
		t.Fatal("err should be nil")
	}

	// len(p) > 1
	badCases := [][]string{
		{"two", "elements"},
		{"here", "three", "elements"},
		{"and", "here", "four", "elements"},
	}

	for _, bc := range badCases {
		obj, rest, err = eas.Resolve(bc)
		if obj != nil {
			t.Fatal("obj should be nil")
		}
		if rest != nil {
			t.Fatal("rest should be nil")
		}
		if err.Error() != fmt.Sprintf("unexpected path elements past %s", bc[0]) {
			t.Fatal("wrong error")
		}
	}

	moreBadCases := []string{
		"i",
		"am",
		"not",
		"an",
		"account",
		"field",
	}
	for _, mbc := range moreBadCases {
		obj, rest, err = eas.Resolve([]string{mbc})
		if obj != nil {
			t.Fatal("obj should be nil")
		}
		if rest != nil {
			t.Fatal("rest should be nil")
		}
		if err != ErrInvalidLink {
			t.Fatal("wrong error")
		}
	}

	goodCases := []string{
		"balance",
		"codeHash",
		"nonce",
		"root",
	}
	for _, gc := range goodCases {
		_, _, err = eas.Resolve([]string{gc})
		if err != nil {
			t.Fatalf("error should be nil %v", gc)
		}
	}
}

func TestAccountSnapshotTree(t *testing.T) {
	eas := prepareEthAccountSnapshot(t)

	// Bad cases
	tree := eas.Tree("non-empty-string", 0)
	if tree != nil {
		t.Fatal("Expected nil to be returned")
	}

	tree = eas.Tree("non-empty-string", 1)
	if tree != nil {
		t.Fatal("Expected nil to be returned")
	}

	tree = eas.Tree("", 0)
	if tree != nil {
		t.Fatal("Expected nil to be returned")
	}

	// Good cases
	tree = eas.Tree("", 1)
	lookupElements := map[string]interface{}{
		"balance":  nil,
		"codeHash": nil,
		"nonce":    nil,
		"root":     nil,
	}

	if len(tree) != len(lookupElements) {
		t.Fatalf("Wrong number of elements\r\nexpected %d\r\ngot %d", len(lookupElements), len(tree))
	}

	for _, te := range tree {
		if _, ok := lookupElements[te]; !ok {
			t.Fatalf("Unexpected Element: %v", te)
		}
	}
}

func TestAccountSnapshotResolveLink(t *testing.T) {
	eas := prepareEthAccountSnapshot(t)

	// bad case
	obj, rest, err := eas.ResolveLink([]string{"supercalifragilist"})
	if obj != nil {
		t.Fatalf("Expected obj to be nil")
	}
	if rest != nil {
		t.Fatal("Expected rest to be nil")
	}
	if err != ErrInvalidLink {
		t.Fatal("Wrong error")
	}

	// good case
	obj, rest, err = eas.ResolveLink([]string{"nonce"})
	if obj != nil {
		t.Fatalf("Expected obj to be nil")
	}
	if rest != nil {
		t.Fatal("Expected rest to be nil")
	}
	if err.Error() != "resolved item was not a link" {
		t.Fatal("Wrong error")
	}
}

func TestAccountSnapshotCopy(t *testing.T) {
	eas := prepareEthAccountSnapshot(t)

	defer func() {
		r := recover()
		if r == nil {
			t.Fatal("Expected panic")
		}
		if r != "implement me" {
			t.Fatalf("Wrong panic message\r\n expected %s\r\ngot %s", "'implement me'", r)
		}
	}()

	_ = eas.Copy()
}

func TestAccountSnapshotLinks(t *testing.T) {
	eas := prepareEthAccountSnapshot(t)

	if eas.Links() != nil {
		t.Fatal("Links() expected to return nil")
	}
}

func TestAccountSnapshotStat(t *testing.T) {
	eas := prepareEthAccountSnapshot(t)

	obj, err := eas.Stat()
	if obj == nil {
		t.Fatal("Expected a not null object node.NodeStat")
	}

	if err != nil {
		t.Fatal("Expected a nil error")
	}
}

func TestAccountSnapshotSize(t *testing.T) {
	eas := prepareEthAccountSnapshot(t)

	size, err := eas.Size()
	if size != uint64(0) {
		t.Fatalf("Wrong size\r\nexpected %d\r\ngot %d", 0, size)
	}

	if err != nil {
		t.Fatal("Expected a nil error")
	}
}

/*
  EthAccountSnapshot functions
*/

func TestAccountSnapshotMarshalJSON(t *testing.T) {
	eas := prepareEthAccountSnapshot(t)

	jsonOutput, err := eas.MarshalJSON()
	checkError(err, t)

	var data map[string]interface{}
	err = json.Unmarshal(jsonOutput, &data)
	checkError(err, t)

	balanceExpression := regexp.MustCompile(`{"balance":16011846000000000000000,`)
	if !balanceExpression.MatchString(string(jsonOutput)) {
		t.Fatal("Balance expression not found")
	}

	code, _ := data["codeHash"].(map[string]interface{})
	if fmt.Sprintf("%s", code["/"]) !=
		"bafkrwigf2jdadbxxem6je7t5wlomoa6a4ualmu6kqittw6723acf3bneoa" {
		t.Fatalf("Wrong Marshaled Value\r\nexpected %s\r\ngot %s", "bafkrwigf2jdadbxxem6je7t5wlomoa6a4ualmu6kqittw6723acf3bneoa", fmt.Sprintf("%s", code["/"]))
	}

	if fmt.Sprintf("%v", data["nonce"]) != "0" {
		t.Fatalf("Wrong Marshaled Value\r\nexpected %s\r\ngot %s", "0", fmt.Sprintf("%v", data["nonce"]))
	}

	root, _ := data["root"].(map[string]interface{})
	if fmt.Sprintf("%s", root["/"]) !=
		"bagmacgzak3ub6fy3zrk2n74dixtjfqhynznurya3tfwk3qabmix3ly3dwqqq" {
		t.Fatalf("Wrong Marshaled Value\r\nexpected %s\r\ngot %s", "bagmacgzak3ub6fy3zrk2n74dixtjfqhynznurya3tfwk3qabmix3ly3dwqqq", fmt.Sprintf("%s", root["/"]))
	}
}

/*
  AUXILIARS
*/
func prepareEthAccountSnapshot(t *testing.T) *EthAccountSnapshot {
	fi, err := os.Open("test_data/eth-state-trie-rlp-c9070d")
	checkError(err, t)

	output, err := FromStateTrieRLPFile(fi)
	checkError(err, t)

	return output.elements[1].(*EthAccountSnapshot)
}