// 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 (
	"bytes"
	"math/rand"
	"reflect"
	"sync"
	"testing"

	"gitea.psichedelico.com/go/bson/internal/assert"
	"gitea.psichedelico.com/go/bson/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) {
	t.Parallel()

	testCases := []struct {
		name     string
		val      interface{}
		bsontype Type
		bytes    []byte
	}{
		{
			name:     "SliceCodec binary",
			val:      []byte("hello world"),
			bsontype: TypeBinary,
			bytes:    bsoncore.AppendBinary(nil, TypeBinaryGeneric, []byte("hello world")),
		},
		{
			name:     "SliceCodec string",
			val:      []byte("hello world"),
			bsontype: TypeString,
			bytes:    bsoncore.AppendString(nil, "hello world"),
		},
	}
	reg := NewRegistry()
	reg.RegisterTypeDecoder(reflect.TypeOf([]byte{}), &sliceCodec{})
	for _, tc := range testCases {
		tc := tc

		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			// Assert that unmarshaling the input data results in the expected value.
			gotValue := reflect.New(reflect.TypeOf(tc.val))
			dec := NewDecoder(newValueReader(tc.bsontype, bytes.NewReader(tc.bytes)))
			dec.SetRegistry(reg)
			err := dec.Decode(gotValue.Interface())
			noerr(t, err)
			assert.Equal(t, tc.val, gotValue.Elem().Interface(), "value mismatch; expected %s, got %s", tc.val, gotValue.Elem())

			// Fill the input data slice with random bytes and then assert that the result still
			// matches the expected value.
			_, err = rand.Read(tc.bytes)
			noerr(t, err)
			assert.Equal(t, tc.val, gotValue.Elem().Interface(), "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 ValueDecoderFunc = func(_ DecodeContext, vr ValueReader, val reflect.Value) error {
		i32, err := vr.ReadInt32()
		if err != nil {
			return err
		}

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

	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
		dec := NewDecoder(NewDocumentReader(bytes.NewReader(docBytes)))
		dec.SetRegistry(customReg)
		err = dec.Decode(&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
		dec := NewDecoder(NewDocumentReader(bytes.NewReader(docBytes)))
		dec.SetRegistry(customReg)
		err = dec.Decode(&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 TestUnmarshalInterface(t *testing.T) {
	t.Parallel()

	type testCase struct {
		name string
		stub func() ([]byte, interface{}, func(*testing.T))
	}
	testCases := []testCase{
		{
			name: "struct with interface containing a concrete value",
			stub: func() ([]byte, interface{}, func(*testing.T)) {
				type testStruct struct {
					Value interface{}
				}
				var value string

				data := docToBytes(struct {
					Value string
				}{
					Value: "foo",
				})

				receiver := testStruct{&value}

				check := func(t *testing.T) {
					t.Helper()
					assert.Equal(t, "foo", value)
				}

				return data, &receiver, check
			},
		},
		{
			name: "struct with interface containing a struct",
			stub: func() ([]byte, interface{}, func(*testing.T)) {
				type demo struct {
					Data string
				}

				type testStruct struct {
					Value interface{}
				}
				var value demo

				data := docToBytes(struct {
					Value demo
				}{
					Value: demo{"foo"},
				})

				receiver := testStruct{&value}

				check := func(t *testing.T) {
					t.Helper()
					assert.Equal(t, "foo", value.Data)
				}

				return data, &receiver, check
			},
		},
		{
			name: "struct with interface containing a slice",
			stub: func() ([]byte, interface{}, func(*testing.T)) {
				type testStruct struct {
					Values interface{}
				}
				var values []string

				data := docToBytes(struct {
					Values []string
				}{
					Values: []string{"foo", "bar"},
				})

				receiver := testStruct{&values}

				check := func(t *testing.T) {
					t.Helper()
					assert.Equal(t, []string{"foo", "bar"}, values)
				}

				return data, &receiver, check
			},
		},
		{
			name: "struct with interface containing an array",
			stub: func() ([]byte, interface{}, func(*testing.T)) {
				type testStruct struct {
					Values interface{}
				}
				var values [2]string

				data := docToBytes(struct {
					Values []string
				}{
					Values: []string{"foo", "bar"},
				})

				receiver := testStruct{&values}

				check := func(t *testing.T) {
					t.Helper()
					assert.Equal(t, [2]string{"foo", "bar"}, values)
				}

				return data, &receiver, check
			},
		},
		{
			name: "struct with interface array containing concrete values",
			stub: func() ([]byte, interface{}, func(*testing.T)) {
				type testStruct struct {
					Values [3]interface{}
				}
				var str string
				var i, j int

				data := docToBytes(struct {
					Values []interface{}
				}{
					Values: []interface{}{"foo", 42, nil},
				})

				receiver := testStruct{[3]interface{}{&str, &i, &j}}

				check := func(t *testing.T) {
					t.Helper()
					assert.Equal(t, "foo", str)
					assert.Equal(t, 42, i)
					assert.Equal(t, 0, j)
					assert.Equal(t, testStruct{[3]interface{}{&str, &i, nil}}, receiver)
				}

				return data, &receiver, check
			},
		},
	}
	for _, tc := range testCases {
		tc := tc

		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			data, receiver, check := tc.stub()
			err := Unmarshal(data, receiver)
			noerr(t, err)
			check(t)
		})
	}
}

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 Binary
	}

	type fooObjectID struct {
		Foo ObjectID
	}

	type fooDBPointer struct {
		Foo 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", Binary{Subtype: 0, Data: []byte{0, 1, 2, 3, 4, 5}}},
			},
			getByteSlice: func(val interface{}) []byte {
				return (*(val.(*D)))[0].Value.(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", Binary{Subtype: 0, Data: myBytes{0, 1, 2, 3, 4, 5}}},
			},
			getByteSlice: func(val interface{}) []byte {
				return (*(val.(*D)))[0].Value.(Binary).Data
			},
		},
		{
			description: "struct with Binary",
			data: docToBytes(fooBinary{
				Foo: Binary{Subtype: 0, Data: []byte{0, 1, 2, 3, 4, 5}},
			}),
			sType: reflect.TypeOf(fooBinary{}),
			want: &fooBinary{
				Foo: 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 Binary",
			data: docToBytes(D{
				{"foo", Binary{Subtype: 0, Data: []byte{0, 1, 2, 3, 4, 5}}},
			}),
			sType: reflect.TypeOf(D{}),
			want: &D{
				{"foo", Binary{Subtype: 0, Data: []byte{0, 1, 2, 3, 4, 5}}},
			},
			getByteSlice: func(val interface{}) []byte {
				return (*(val.(*D)))[0].Value.(Binary).Data
			},
		},
		{
			description: "struct with ObjectID",
			data: docToBytes(fooObjectID{
				Foo: ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
			}),
			sType: reflect.TypeOf(fooObjectID{}),
			want: &fooObjectID{
				Foo: 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 ObjectID",
			data: docToBytes(D{
				{"foo", ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}},
			}),
			sType: reflect.TypeOf(D{}),
			want: &D{
				{"foo", ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}},
			},
			getByteSlice: func(val interface{}) []byte {
				oid := (*(val.(*D)))[0].Value.(ObjectID)
				return oid[:]
			},
		},
		{
			description: "struct with DBPointer",
			data: docToBytes(fooDBPointer{
				Foo: DBPointer{
					DB:      "test",
					Pointer: ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
				},
			}),
			sType: reflect.TypeOf(fooDBPointer{}),
			want: &fooDBPointer{
				Foo: DBPointer{
					DB:      "test",
					Pointer: 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 DBPointer",
			data: docToBytes(D{
				{"foo", DBPointer{
					DB:      "test",
					Pointer: ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
				}},
			}),
			sType: reflect.TypeOf(D{}),
			want: &D{
				{"foo", DBPointer{
					DB:      "test",
					Pointer: ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
				}},
			},
			getByteSlice: func(val interface{}) []byte {
				oid := (*(val.(*D)))[0].Value.(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))
		})
	}
}

func TestUnmarshalConcurrently(t *testing.T) {
	t.Parallel()

	const size = 10_000

	data := []byte{16, 0, 0, 0, 10, 108, 97, 115, 116, 101, 114, 114, 111, 114, 0, 0}
	wg := sync.WaitGroup{}
	wg.Add(size)
	for i := 0; i < size; i++ {
		go func() {
			defer wg.Done()
			var res struct{ LastError error }
			_ = Unmarshal(data, &res)
		}()
	}
	wg.Wait()
}
