// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

package bson

import (
	"math/rand"
	"reflect"
	"testing"

	"go.mongodb.org/mongo-driver/bson/bsoncodec"
	"go.mongodb.org/mongo-driver/bson/bsonrw"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/internal/assert"
	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)

func TestUnmarshal(t *testing.T) {
	for _, tc := range unmarshalingTestCases() {
		t.Run(tc.name, func(t *testing.T) {
			// Make a copy of the test data so we can modify it later.
			data := make([]byte, len(tc.data))
			copy(data, tc.data)

			// Assert that unmarshaling the input data results in the expected value.
			got := reflect.New(tc.sType).Interface()
			err := Unmarshal(data, got)
			noerr(t, err)
			assert.Equal(t, tc.want, got, "Did not unmarshal as expected.")

			// Fill the input data slice with random bytes and then assert that the result still
			// matches the expected value.
			_, err = rand.Read(data)
			noerr(t, err)
			assert.Equal(t, tc.want, got, "unmarshaled value does not match expected after modifying the input bytes")
		})
	}
}

func TestUnmarshalWithRegistry(t *testing.T) {
	for _, tc := range unmarshalingTestCases() {
		t.Run(tc.name, func(t *testing.T) {
			// Make a copy of the test data so we can modify it later.
			data := make([]byte, len(tc.data))
			copy(data, tc.data)

			// Assert that unmarshaling the input data results in the expected value.
			got := reflect.New(tc.sType).Interface()
			err := UnmarshalWithRegistry(DefaultRegistry, data, got)
			noerr(t, err)
			assert.Equal(t, tc.want, got, "Did not unmarshal as expected.")

			// Fill the input data slice with random bytes and then assert that the result still
			// matches the expected value.
			_, err = rand.Read(data)
			noerr(t, err)
			assert.Equal(t, tc.want, got, "unmarshaled value does not match expected after modifying the input bytes")
		})
	}
}

func TestUnmarshalWithContext(t *testing.T) {
	for _, tc := range unmarshalingTestCases() {
		t.Run(tc.name, func(t *testing.T) {
			// Make a copy of the test data so we can modify it later.
			data := make([]byte, len(tc.data))
			copy(data, tc.data)

			// Assert that unmarshaling the input data results in the expected value.
			dc := bsoncodec.DecodeContext{Registry: DefaultRegistry}
			got := reflect.New(tc.sType).Interface()
			err := UnmarshalWithContext(dc, data, got)
			noerr(t, err)
			assert.Equal(t, tc.want, got, "Did not unmarshal as expected.")

			// Fill the input data slice with random bytes and then assert that the result still
			// matches the expected value.
			_, err = rand.Read(data)
			noerr(t, err)
			assert.Equal(t, tc.want, got, "unmarshaled value does not match expected after modifying the input bytes")
		})
	}
}

func TestUnmarshalExtJSONWithRegistry(t *testing.T) {
	t.Run("UnmarshalExtJSONWithContext", func(t *testing.T) {
		type teststruct struct{ Foo int }
		var got teststruct
		data := []byte("{\"foo\":1}")
		err := UnmarshalExtJSONWithRegistry(DefaultRegistry, data, true, &got)
		noerr(t, err)
		want := teststruct{1}
		assert.Equal(t, want, got, "Did not unmarshal as expected.")
	})

	t.Run("UnmarshalExtJSONInvalidInput", func(t *testing.T) {
		data := []byte("invalid")
		err := UnmarshalExtJSONWithRegistry(DefaultRegistry, data, true, &M{})
		if err != bsonrw.ErrInvalidJSON {
			t.Fatalf("wanted ErrInvalidJSON, got %v", err)
		}
	})
}

func TestUnmarshalExtJSONWithContext(t *testing.T) {
	type fooInt struct {
		Foo int
	}

	type fooString struct {
		Foo string
	}

	type fooBytes struct {
		Foo []byte
	}

	var cases = []struct {
		name  string
		sType reflect.Type
		want  interface{}
		data  []byte
	}{
		{
			name:  "Small struct",
			sType: reflect.TypeOf(fooInt{}),
			data:  []byte(`{"foo":1}`),
			want:  &fooInt{Foo: 1},
		},
		{
			name:  "Valid surrogate pair",
			sType: reflect.TypeOf(fooString{}),
			data:  []byte(`{"foo":"\uD834\uDd1e"}`),
			want:  &fooString{Foo: "𝄞"},
		},
		{
			name:  "Valid surrogate pair with other values",
			sType: reflect.TypeOf(fooString{}),
			data:  []byte(`{"foo":"abc \uD834\uDd1e 123"}`),
			want:  &fooString{Foo: "abc 𝄞 123"},
		},
		{
			name:  "High surrogate value with no following low surrogate value",
			sType: reflect.TypeOf(fooString{}),
			data:  []byte(`{"foo":"abc \uD834 123"}`),
			want:  &fooString{Foo: "abc � 123"},
		},
		{
			name:  "High surrogate value at end of string",
			sType: reflect.TypeOf(fooString{}),
			data:  []byte(`{"foo":"\uD834"}`),
			want:  &fooString{Foo: "�"},
		},
		{
			name:  "Low surrogate value with no preceding high surrogate value",
			sType: reflect.TypeOf(fooString{}),
			data:  []byte(`{"foo":"abc \uDd1e 123"}`),
			want:  &fooString{Foo: "abc � 123"},
		},
		{
			name:  "Low surrogate value at end of string",
			sType: reflect.TypeOf(fooString{}),
			data:  []byte(`{"foo":"\uDd1e"}`),
			want:  &fooString{Foo: "�"},
		},
		{
			name:  "High surrogate value with non-surrogate unicode value",
			sType: reflect.TypeOf(fooString{}),
			data:  []byte(`{"foo":"\uD834\u00BF"}`),
			want:  &fooString{Foo: "�¿"},
		},
		// GODRIVER-2311
		// Test that ExtJSON-encoded binary unmarshals correctly to a bson.D and that the
		// unmarshaled value does not reference the same underlying byte array as the input.
		{
			name:  "bson.D with binary",
			sType: reflect.TypeOf(D{}),
			data:  []byte(`{"foo": {"$binary": {"subType": "0", "base64": "AAECAwQF"}}}`),
			want:  &D{{"foo", primitive.Binary{Subtype: 0, Data: []byte{0, 1, 2, 3, 4, 5}}}},
		},
		// GODRIVER-2311
		// Test that ExtJSON-encoded binary unmarshals correctly to a struct and that the
		// unmarshaled value does not reference thesame  underlying byte array as the input.
		{
			name:  "struct with binary",
			sType: reflect.TypeOf(fooBytes{}),
			data:  []byte(`{"foo": {"$binary": {"subType": "0", "base64": "AAECAwQF"}}}`),
			want:  &fooBytes{Foo: []byte{0, 1, 2, 3, 4, 5}},
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			// Make a copy of the test data so we can modify it later.
			data := make([]byte, len(tc.data))
			copy(data, tc.data)

			// Assert that unmarshaling the input data results in the expected value.
			got := reflect.New(tc.sType).Interface()
			dc := bsoncodec.DecodeContext{Registry: DefaultRegistry}
			err := UnmarshalExtJSONWithContext(dc, data, true, got)
			noerr(t, err)
			assert.Equal(t, tc.want, got, "Did not unmarshal as expected.")

			// Fill the input data slice with random bytes and then assert that the result still
			// matches the expected value.
			_, err = rand.Read(data)
			noerr(t, err)
			assert.Equal(t, tc.want, got, "unmarshaled value does not match expected after modifying the input bytes")
		})
	}
}

func TestCachingDecodersNotSharedAcrossRegistries(t *testing.T) {
	// Decoders that have caches for recursive decoder lookup should not be shared across Registry instances. Otherwise,
	// the first DecodeValue call would cache an decoder and a subsequent call would see that decoder even if a
	// different Registry is used.

	// Create a custom Registry that negates BSON int32 values when decoding.
	var decodeInt32 bsoncodec.ValueDecoderFunc = func(_ bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
		i32, err := vr.ReadInt32()
		if err != nil {
			return err
		}

		val.SetInt(int64(-1 * i32))
		return nil
	}
	customReg := NewRegistryBuilder().
		RegisterTypeDecoder(tInt32, decodeInt32).
		Build()

	docBytes := bsoncore.BuildDocumentFromElements(
		nil,
		bsoncore.AppendInt32Element(nil, "x", 1),
	)

	// For all sub-tests, unmarshal docBytes into a struct and assert that value for "x" is 1 when using the default
	// registry and -1 when using the custom registry.
	t.Run("struct", func(t *testing.T) {
		type Struct struct {
			X int32
		}

		var first Struct
		err := Unmarshal(docBytes, &first)
		assert.Nil(t, err, "Unmarshal error: %v", err)
		assert.Equal(t, int32(1), first.X, "expected X value to be 1, got %v", first.X)

		var second Struct
		err = UnmarshalWithRegistry(customReg, docBytes, &second)
		assert.Nil(t, err, "Unmarshal error: %v", err)
		assert.Equal(t, int32(-1), second.X, "expected X value to be -1, got %v", second.X)
	})
	t.Run("pointer", func(t *testing.T) {
		type Struct struct {
			X *int32
		}

		var first Struct
		err := Unmarshal(docBytes, &first)
		assert.Nil(t, err, "Unmarshal error: %v", err)
		assert.Equal(t, int32(1), *first.X, "expected X value to be 1, got %v", *first.X)

		var second Struct
		err = UnmarshalWithRegistry(customReg, docBytes, &second)
		assert.Nil(t, err, "Unmarshal error: %v", err)
		assert.Equal(t, int32(-1), *second.X, "expected X value to be -1, got %v", *second.X)
	})
}

func TestUnmarshalExtJSONWithUndefinedField(t *testing.T) {
	// When unmarshalling extJSON, fields that are undefined in the destination struct are skipped.
	// This process must not skip other, defined fields and must not raise errors.
	type expectedResponse struct {
		DefinedField interface{}
	}

	unmarshalExpectedResponse := func(t *testing.T, extJSON string) *expectedResponse {
		t.Helper()
		responseDoc := expectedResponse{}
		err := UnmarshalExtJSON([]byte(extJSON), false, &responseDoc)
		assert.Nil(t, err, "UnmarshalExtJSON error: %v", err)
		return &responseDoc
	}

	testCases := []struct {
		name          string
		testJSON      string
		expectedValue interface{}
	}{
		{
			"no array",
			`{
				"UndefinedField": {"key": 1},
				"DefinedField": "value"
			}`,
			"value",
		},
		{
			"outer array",
			`{
				"UndefinedField": [{"key": 1}],
				"DefinedField": "value"
			}`,
			"value",
		},
		{
			"embedded array",
			`{
				"UndefinedField": {"keys": [2]},
				"DefinedField": "value"
			}`,
			"value",
		},
		{
			"outer array and embedded array",
			`{
				"UndefinedField": [{"keys": [2]}],
				"DefinedField": "value"
			}`,
			"value",
		},
		{
			"embedded document",
			`{
				"UndefinedField": {"key": {"one": "two"}},
				"DefinedField": "value"
			}`,
			"value",
		},
		{
			"doubly embedded document",
			`{
				"UndefinedField": {"key": {"one": {"two": "three"}}},
				"DefinedField": "value"
			}`,
			"value",
		},
		{
			"embedded document and embedded array",
			`{
				"UndefinedField": {"key": {"one": {"two": [3]}}},
				"DefinedField": "value"
			}`,
			"value",
		},
		{
			"embedded document and embedded array in outer array",
			`{
				"UndefinedField": [{"key": {"one": [3]}}],
				"DefinedField": "value"
			}`,
			"value",
		},
		{
			"code with scope",
			`{
				"UndefinedField": {"logic": {"$code": "foo", "$scope": {"bar": 1}}},
				"DefinedField": "value"
			}`,
			"value",
		},
		{
			"embedded array of code with scope",
			`{
				"UndefinedField": {"logic": [{"$code": "foo", "$scope": {"bar": 1}}]},
				"DefinedField": "value"
			}`,
			"value",
		},
		{
			"type definition embedded document",
			`{
				"UndefinedField": {"myDouble": {"$numberDouble": "1.24"}},
				"DefinedField": "value"
			}`,
			"value",
		},
		{
			"empty embedded document",
			`{
				"UndefinedField": {"empty": {}, "key": 1},
				"DefinedField": "value"
			}`,
			"value",
		},
		{
			"empty object before",
			`{
				"UndefinedField": {},
				"DefinedField": {"value": "a"}
			}`,
			D{{"value", "a"}},
		},
		{
			"empty object after",
			`{
				"DefinedField": {"value": "a"},
				"UndefinedField": {}
			}`,
			D{{"value", "a"}},
		},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			responseDoc := unmarshalExpectedResponse(t, tc.testJSON)
			assert.Equal(t, tc.expectedValue, responseDoc.DefinedField, "expected DefinedField to be %v, got %q",
				tc.expectedValue, responseDoc.DefinedField)
		})
	}
}

func TestUnmarshalBSONWithUndefinedField(t *testing.T) {
	// When unmarshalling BSON, fields that are undefined in the destination struct are skipped.
	// This process must not skip other, defined fields and must not raise errors.
	type expectedResponse struct {
		DefinedField string `bson:"DefinedField"`
	}

	createExpectedResponse := func(t *testing.T, doc D) *expectedResponse {
		t.Helper()

		marshalledBSON, err := Marshal(doc)
		assert.Nil(t, err, "error marshalling BSON: %v", err)

		responseDoc := expectedResponse{}
		err = Unmarshal(marshalledBSON, &responseDoc)
		assert.Nil(t, err, "error unmarshalling BSON: %v", err)
		return &responseDoc
	}

	testCases := []struct {
		name     string
		testBSON D
	}{
		{
			"no array",
			D{
				{"UndefinedField", D{
					{"key", 1},
				}},
				{"DefinedField", "value"},
			},
		},
		{
			"outer array",
			D{
				{"UndefinedField", A{D{
					{"key", 1},
				}}},
				{"DefinedField", "value"},
			},
		},
		{
			"embedded array",
			D{
				{"UndefinedField", D{
					{"key", A{1}},
				}},
				{"DefinedField", "value"},
			},
		},
		{
			"outer array and embedded array",
			D{
				{"UndefinedField", A{D{
					{"key", A{1}},
				}}},
				{"DefinedField", "value"},
			},
		},
		{
			"embedded document",
			D{
				{"UndefinedField", D{
					{"key", D{
						{"one", "two"},
					}},
				}},
				{"DefinedField", "value"},
			},
		},
		{
			"doubly embedded document",
			D{
				{"UndefinedField", D{
					{"key", D{
						{"one", D{
							{"two", "three"},
						}},
					}},
				}},
				{"DefinedField", "value"},
			},
		},
		{
			"embedded document and embedded array",
			D{
				{"UndefinedField", D{
					{"key", D{
						{"one", D{
							{"two", A{3}},
						}},
					}},
				}},
				{"DefinedField", "value"},
			},
		},
		{
			"embedded document and embedded array in outer array",
			D{
				{"UndefinedField", A{D{
					{"key", D{
						{"one", A{3}},
					}},
				}}},
				{"DefinedField", "value"},
			},
		},
		{
			"code with scope",
			D{
				{"UndefinedField", D{
					{"logic", D{
						{"$code", "foo"},
						{"$scope", D{
							{"bar", 1},
						}},
					}},
				}},
				{"DefinedField", "value"},
			},
		},
		{
			"embedded array of code with scope",
			D{
				{"UndefinedField", D{
					{"logic", A{D{
						{"$code", "foo"},
						{"$scope", D{
							{"bar", 1},
						}},
					}}},
				}},
				{"DefinedField", "value"},
			},
		},
		{
			"empty embedded document",
			D{
				{"UndefinedField", D{}},
				{"DefinedField", "value"},
			},
		},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			responseDoc := createExpectedResponse(t, tc.testBSON)
			assert.Equal(t, "value", responseDoc.DefinedField, "expected DefinedField to be 'value', got %q", responseDoc.DefinedField)
		})
	}
}

// GODRIVER-2311
// Assert that unmarshaled values containing byte slices do not reference the same underlying byte
// array as the BSON input data byte slice.
func TestUnmarshalByteSlicesUseDistinctArrays(t *testing.T) {
	type fooBytes struct {
		Foo []byte
	}

	type myBytes []byte
	type fooMyBytes struct {
		Foo myBytes
	}

	type fooBinary struct {
		Foo primitive.Binary
	}

	type fooObjectID struct {
		Foo primitive.ObjectID
	}

	type fooDBPointer struct {
		Foo primitive.DBPointer
	}

	testCases := []struct {
		description string
		data        []byte
		sType       reflect.Type
		want        interface{}

		// getByteSlice returns the byte slice from the unmarshaled value, allowing the test to
		// inspect the addresses of the underlying byte array.
		getByteSlice func(interface{}) []byte
	}{
		{
			description: "struct with byte slice",
			data: docToBytes(fooBytes{
				Foo: []byte{0, 1, 2, 3, 4, 5},
			}),
			sType: reflect.TypeOf(fooBytes{}),
			want: &fooBytes{
				Foo: []byte{0, 1, 2, 3, 4, 5},
			},
			getByteSlice: func(val interface{}) []byte {
				return (*(val.(*fooBytes))).Foo
			},
		},
		{
			description: "bson.D with byte slice",
			data: docToBytes(D{
				{"foo", []byte{0, 1, 2, 3, 4, 5}},
			}),
			sType: reflect.TypeOf(D{}),
			want: &D{
				{"foo", primitive.Binary{Subtype: 0, Data: []byte{0, 1, 2, 3, 4, 5}}},
			},
			getByteSlice: func(val interface{}) []byte {
				return (*(val.(*D)))[0].Value.(primitive.Binary).Data
			},
		},
		{
			description: "struct with custom byte slice type",
			data: docToBytes(fooMyBytes{
				Foo: myBytes{0, 1, 2, 3, 4, 5},
			}),
			sType: reflect.TypeOf(fooMyBytes{}),
			want: &fooMyBytes{
				Foo: myBytes{0, 1, 2, 3, 4, 5},
			},
			getByteSlice: func(val interface{}) []byte {
				return (*(val.(*fooMyBytes))).Foo
			},
		},
		{
			description: "bson.D with custom byte slice type",
			data: docToBytes(D{
				{"foo", myBytes{0, 1, 2, 3, 4, 5}},
			}),
			sType: reflect.TypeOf(D{}),
			want: &D{
				{"foo", primitive.Binary{Subtype: 0, Data: myBytes{0, 1, 2, 3, 4, 5}}},
			},
			getByteSlice: func(val interface{}) []byte {
				return (*(val.(*D)))[0].Value.(primitive.Binary).Data
			},
		},
		{
			description: "struct with primitive.Binary",
			data: docToBytes(fooBinary{
				Foo: primitive.Binary{Subtype: 0, Data: []byte{0, 1, 2, 3, 4, 5}},
			}),
			sType: reflect.TypeOf(fooBinary{}),
			want: &fooBinary{
				Foo: primitive.Binary{Subtype: 0, Data: []byte{0, 1, 2, 3, 4, 5}},
			},
			getByteSlice: func(val interface{}) []byte {
				return (*(val.(*fooBinary))).Foo.Data
			},
		},
		{
			description: "bson.D with primitive.Binary",
			data: docToBytes(D{
				{"foo", primitive.Binary{Subtype: 0, Data: []byte{0, 1, 2, 3, 4, 5}}},
			}),
			sType: reflect.TypeOf(D{}),
			want: &D{
				{"foo", primitive.Binary{Subtype: 0, Data: []byte{0, 1, 2, 3, 4, 5}}},
			},
			getByteSlice: func(val interface{}) []byte {
				return (*(val.(*D)))[0].Value.(primitive.Binary).Data
			},
		},
		{
			description: "struct with primitive.ObjectID",
			data: docToBytes(fooObjectID{
				Foo: primitive.ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
			}),
			sType: reflect.TypeOf(fooObjectID{}),
			want: &fooObjectID{
				Foo: primitive.ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
			},
			getByteSlice: func(val interface{}) []byte {
				return (*(val.(*fooObjectID))).Foo[:]
			},
		},
		{
			description: "bson.D with primitive.ObjectID",
			data: docToBytes(D{
				{"foo", primitive.ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}},
			}),
			sType: reflect.TypeOf(D{}),
			want: &D{
				{"foo", primitive.ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}},
			},
			getByteSlice: func(val interface{}) []byte {
				oid := (*(val.(*D)))[0].Value.(primitive.ObjectID)
				return oid[:]
			},
		},
		{
			description: "struct with primitive.DBPointer",
			data: docToBytes(fooDBPointer{
				Foo: primitive.DBPointer{
					DB:      "test",
					Pointer: primitive.ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
				},
			}),
			sType: reflect.TypeOf(fooDBPointer{}),
			want: &fooDBPointer{
				Foo: primitive.DBPointer{
					DB:      "test",
					Pointer: primitive.ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
				},
			},
			getByteSlice: func(val interface{}) []byte {
				return (*(val.(*fooDBPointer))).Foo.Pointer[:]
			},
		},
		{
			description: "bson.D with primitive.DBPointer",
			data: docToBytes(D{
				{"foo", primitive.DBPointer{
					DB:      "test",
					Pointer: primitive.ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
				}},
			}),
			sType: reflect.TypeOf(D{}),
			want: &D{
				{"foo", primitive.DBPointer{
					DB:      "test",
					Pointer: primitive.ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
				}},
			},
			getByteSlice: func(val interface{}) []byte {
				oid := (*(val.(*D)))[0].Value.(primitive.DBPointer).Pointer
				return oid[:]
			},
		},
	}

	for _, tc := range testCases {
		tc := tc // Capture range variable.
		t.Run(tc.description, func(t *testing.T) {
			t.Parallel()

			// Make a copy of the test data so we can modify it later.
			data := make([]byte, len(tc.data))
			copy(data, tc.data)

			// Assert that unmarshaling the input data results in the expected value.
			got := reflect.New(tc.sType).Interface()
			err := Unmarshal(data, got)
			noerr(t, err)
			assert.Equal(t, tc.want, got, "unmarshaled value does not match the expected value")

			// Fill the input data slice with random bytes and then assert that the result still
			// matches the expected value.
			_, err = rand.Read(data)
			noerr(t, err)
			assert.Equal(t, tc.want, got, "unmarshaled value does not match expected after modifying the input bytes")

			// Assert that the byte slice in the unmarshaled value does not share any memory
			// addresses with the input byte slice.
			assert.DifferentAddressRanges(t, data, tc.getByteSlice(got))
		})
	}
}
