package cbor_test

import (
	"encoding/json"
	"reflect"
	"testing"

	"github.com/fxamacker/cbor/v2"
)

// TestStdlibJSONCompatibility tests compatibility as a drop-in replacement for the standard library
// encoding/json package on a round trip encoding from Go object to interface{}.
func TestStdlibJSONCompatibility(t *testing.T) {
	// TODO: With better coverage and compatibility, it could be useful to expose these option
	// configurations to users.

	enc, err := cbor.EncOptions{
		ByteSliceLaterFormat: cbor.ByteSliceLaterFormatBase64,
		String:               cbor.StringToByteString,
		ByteArray:            cbor.ByteArrayToArray,
	}.EncMode()
	if err != nil {
		t.Fatal(err)
	}

	dec, err := cbor.DecOptions{
		DefaultByteStringType:    reflect.TypeOf(""),
		ByteStringToString:       cbor.ByteStringToStringAllowedWithExpectedLaterEncoding,
		ByteStringExpectedFormat: cbor.ByteStringExpectedBase64,
	}.DecMode()
	if err != nil {
		t.Fatal(err)
	}

	for _, tc := range []struct {
		name       string
		original   interface{}
		ifaceEqual bool // require equal intermediate interface{} values from both protocols
	}{
		{
			name:       "byte slice to base64-encoded string",
			original:   []byte("hello world"),
			ifaceEqual: true,
		},
		{
			name:       "byte array to array of integers",
			original:   [11]byte{'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'},
			ifaceEqual: false, // encoding/json decodes the array elements to float64
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			t.Logf("original: %#v", tc.original)

			j1, err := json.Marshal(tc.original)
			if err != nil {
				t.Fatal(err)
			}
			t.Logf("original to json: %s", string(j1))

			c1, err := enc.Marshal(tc.original)
			if err != nil {
				t.Fatal(err)
			}
			diag1, err := cbor.Diagnose(c1)
			if err != nil {
				t.Fatal(err)
			}
			t.Logf("original to cbor: %s", diag1)

			var jintf interface{}
			err = json.Unmarshal(j1, &jintf)
			if err != nil {
				t.Fatal(err)
			}
			t.Logf("json to interface{} (%T): %#v", jintf, jintf)

			var cintf interface{}
			err = dec.Unmarshal(c1, &cintf)
			if err != nil {
				t.Fatal(err)
			}
			t.Logf("cbor to interface{} (%T): %#v", cintf, cintf)

			j2, err := json.Marshal(jintf)
			if err != nil {
				t.Fatal(err)
			}
			t.Logf("interface{} to json: %s", string(j2))

			c2, err := enc.Marshal(cintf)
			if err != nil {
				t.Fatal(err)
			}
			diag2, err := cbor.Diagnose(c2)
			if err != nil {
				t.Fatal(err)
			}
			t.Logf("interface{} to cbor: %s", diag2)

			if !reflect.DeepEqual(jintf, cintf) {
				if tc.ifaceEqual {
					t.Errorf("native-to-interface{} via cbor differed from native-to-interface{} via json")
				} else {
					t.Logf("native-to-interface{} via cbor differed from native-to-interface{} via json")
				}
			}

			jfinalValue := reflect.New(reflect.TypeOf(tc.original))
			err = json.Unmarshal(j2, jfinalValue.Interface())
			if err != nil {
				t.Fatal(err)
			}
			jfinal := jfinalValue.Elem().Interface()
			t.Logf("json to native: %#v", jfinal)
			if !reflect.DeepEqual(tc.original, jfinal) {
				t.Error("diff in json roundtrip")
			}

			cfinalValue := reflect.New(reflect.TypeOf(tc.original))
			err = dec.Unmarshal(c2, cfinalValue.Interface())
			if err != nil {
				t.Fatal(err)
			}
			cfinal := cfinalValue.Elem().Interface()
			t.Logf("cbor to native: %#v", cfinal)
			if !reflect.DeepEqual(tc.original, cfinal) {
				t.Error("diff in cbor roundtrip")
			}

		})
	}
}
