package pb

// mirrored in JavaScript @ https://github.com/ipld/js-dag-pb/blob/master/test/test-compat.js

import (
	"encoding/hex"
	"encoding/json"
	"testing"

	"google.golang.org/protobuf/proto"
)

var (
	dataZero   []byte = []byte{}
	dataSome   []byte = []byte{0, 1, 2, 3, 4}
	cidBytes   []byte = []byte{1, 85, 0, 5, 0, 1, 2, 3, 4}
	zeroName   string = ""
	someName   string = "some name"
	zeroTsize  uint64 = 0
	someTsize  uint64 = 1010
	largeTsize uint64 = 9007199254740991 // JavaScript Number.MAX_SAFE_INTEGER
)

type testCase struct {
	name          string
	node          *PBNode
	expectedBytes string
	expectedForm  string
}

var testCases = []testCase{
	{
		name:          "empty",
		node:          &PBNode{},
		expectedBytes: "",
		expectedForm:  "{}",
	},
	{
		name:          "Data zero",
		node:          &PBNode{Data: dataZero},
		expectedBytes: "0a00",
		expectedForm: `{
	"Data": ""
}`,
	},
	{
		name:          "Data some",
		node:          &PBNode{Data: dataSome},
		expectedBytes: "0a050001020304",
		expectedForm: `{
	"Data": "0001020304"
}`,
	},
	{
		name:          "Links zero",
		node:          &PBNode{Links: make([]*PBLink, 0)},
		expectedBytes: "",
		expectedForm:  "{}",
	},
	{
		name:          "Data some Links zero",
		node:          &PBNode{Data: dataSome, Links: make([]*PBLink, 0)},
		expectedBytes: "0a050001020304",
		expectedForm: `{
	"Data": "0001020304"
}`,
	},
	{
		name:          "Links empty",
		node:          &PBNode{Links: []*PBLink{{}}},
		expectedBytes: "1200",
		expectedForm: `{
	"Links": [
		{}
	]
}`,
	},
	{
		name:          "Data some Links empty",
		node:          &PBNode{Data: dataSome, Links: []*PBLink{{}}},
		expectedBytes: "0a0500010203041200",
		expectedForm: `{
	"Data": "0001020304",
	"Links": [
		{}
	]
}`,
	},
	{
		name:          "Links Hash zero",
		node:          &PBNode{Links: []*PBLink{{Hash: dataZero}}},
		expectedBytes: "12020a00",
		expectedForm: `{
	"Links": [
		{
			"Hash": ""
		}
	]
}`,
	},
	{
		name:          "Links Hash some",
		node:          &PBNode{Links: []*PBLink{{Hash: cidBytes}}},
		expectedBytes: "120b0a09015500050001020304",
		expectedForm: `{
	"Links": [
		{
			"Hash": "015500050001020304"
		}
	]
}`,
	},
	{
		name:          "Links Name zero",
		node:          &PBNode{Links: []*PBLink{{Name: &zeroName}}},
		expectedBytes: "12021200",
		expectedForm: `{
	"Links": [
		{
			"Name": ""
		}
	]
}`,
	},
	{
		name:          "Links Hash some Name zero",
		node:          &PBNode{Links: []*PBLink{{Hash: cidBytes, Name: &zeroName}}},
		expectedBytes: "120d0a090155000500010203041200",
		expectedForm: `{
	"Links": [
		{
			"Hash": "015500050001020304",
			"Name": ""
		}
	]
}`,
	},
	{
		name:          "Links Name some",
		node:          &PBNode{Links: []*PBLink{{Name: &someName}}},
		expectedBytes: "120b1209736f6d65206e616d65",
		expectedForm: `{
	"Links": [
		{
			"Name": "some name"
		}
	]
}`,
	},
	{
		name:          "Links Hash some Name some",
		node:          &PBNode{Links: []*PBLink{{Hash: cidBytes, Name: &someName}}},
		expectedBytes: "12160a090155000500010203041209736f6d65206e616d65",
		expectedForm: `{
	"Links": [
		{
			"Hash": "015500050001020304",
			"Name": "some name"
		}
	]
}`,
	},
	{
		name:          "Links Tsize zero",
		node:          &PBNode{Links: []*PBLink{{Tsize: &zeroTsize}}},
		expectedBytes: "12021800",
		expectedForm: `{
	"Links": [
		{
			"Tsize": 0
		}
	]
}`,
	},
	{
		name:          "Links Hash some Tsize zero",
		node:          &PBNode{Links: []*PBLink{{Hash: cidBytes, Tsize: &zeroTsize}}},
		expectedBytes: "120d0a090155000500010203041800",
		expectedForm: `{
	"Links": [
		{
			"Hash": "015500050001020304",
			"Tsize": 0
		}
	]
}`,
	},
	{
		name:          "Links Tsize some",
		node:          &PBNode{Links: []*PBLink{{Tsize: &someTsize}}},
		expectedBytes: "120318f207",
		expectedForm: `{
	"Links": [
		{
			"Tsize": 1010
		}
	]
}`,
	},
	{
		name:          "Links Hash some Tsize some",
		node:          &PBNode{Links: []*PBLink{{Hash: cidBytes, Tsize: &largeTsize}}},
		expectedBytes: "12140a0901550005000102030418ffffffffffffff0f",
		expectedForm: `{
	"Links": [
		{
			"Hash": "015500050001020304",
			"Tsize": 9007199254740991
		}
	]
}`,
	},
}

func TestCompat(t *testing.T) {
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			verifyRoundTrip(t, tc)
		})
	}
}

func verifyRoundTrip(t *testing.T, tc testCase) {
	actualBytes, actualForm, err := nodeRoundTripToString(t, tc.node)
	if err != nil {
		t.Fatal(err)
	}

	if actualBytes != tc.expectedBytes {
		t.Logf(
			"Expected bytes: [%v]\nGot: [%v]\n",
			tc.expectedBytes,
			actualBytes)
		t.Error("Did not match")
	}

	if actualForm != tc.expectedForm {
		t.Logf(
			"Expected form: [%v]\nGot: [%v]\n",
			tc.expectedForm,
			actualForm)
		t.Error("Did not match")
	}
}

func nodeRoundTripToString(t *testing.T, n *PBNode) (string, string, error) {
	bytes, err := proto.Marshal(n)
	if err != nil {
		return "", "", err
	}
	t.Logf("[%v]\n", hex.EncodeToString(bytes))
	rt := new(PBNode)
	if err := proto.Unmarshal(bytes, rt); err != nil {
		return "", "", err
	}
	str, err := json.MarshalIndent(cleanPBNode(t, rt), "", "\t")
	if err != nil {
		return "", "", err
	}
	return hex.EncodeToString(bytes), string(str), nil
}

// convert a PBLink into a map for clean JSON marshalling
func cleanPBLink(t *testing.T, link *PBLink) map[string]any {
	if link == nil {
		return nil
	}
	nl := make(map[string]any)
	if link.Hash != nil {
		nl["Hash"] = hex.EncodeToString(link.Hash)
	}
	if link.Name != nil {
		nl["Name"] = link.Name
	}
	if link.Tsize != nil {
		nl["Tsize"] = link.Tsize
	}
	return nl
}

// convert a PBNode into a map for clean JSON marshalling
func cleanPBNode(t *testing.T, node *PBNode) map[string]any {
	nn := make(map[string]any)
	if node.Data != nil {
		nn["Data"] = hex.EncodeToString(node.Data)
	}
	if node.Links != nil {
		links := make([]map[string]any, len(node.Links))
		for i, l := range node.Links {
			links[i] = cleanPBLink(t, l)
		}
		nn["Links"] = links
	}
	return nn
}
