// 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
//
// Based on gopkg.in/mgo.v2/bson by Gustavo Niemeyer
// See THIRD-PARTY-NOTICES for original license terms.

package bson

import (
	"bytes"
	"encoding/binary"
	"encoding/json"
	"errors"
	"net/url"
	"reflect"
	"strconv"
	"strings"
	"testing"
	"time"

	"go.mongodb.org/mongo-driver/v2/internal/assert"
)

// Wrap up the document elements contained in data, prepending the int32
// length of the data, and appending the '\x00' value closing the document.
func wrapInDoc(data string) string {
	result := make([]byte, len(data)+5)
	binary.LittleEndian.PutUint32(result, uint32(len(result)))
	copy(result[4:], []byte(data))
	return string(result)
}

func makeZeroDoc(value any) (zero any) {
	v := reflect.ValueOf(value)
	t := v.Type()
	switch t.Kind() {
	case reflect.Map:
		mv := reflect.MakeMap(t)
		zero = mv.Interface()
	case reflect.Ptr:
		pv := reflect.New(v.Type().Elem())
		zero = pv.Interface()
	case reflect.Slice, reflect.Int, reflect.Int64, reflect.Struct:
		zero = reflect.New(t).Interface()
	default:
		panic("unsupported doc type: " + t.Name())
	}
	return zero
}

func unmarshalWithRegistry(t *testing.T, r *Registry, data []byte, val any) error {
	t.Helper()

	dec := NewDecoder(NewDocumentReader(bytes.NewReader(data)))
	dec.SetRegistry(r)
	return dec.Decode(val)
}

func testUnmarshal(t *testing.T, data string, obj any) {
	zero := makeZeroDoc(obj)
	err := unmarshalWithRegistry(t, NewMgoRegistry(), []byte(data), zero)
	assert.Nil(t, err, "expected nil error, got: %v", err)
	assert.True(t, reflect.DeepEqual(zero, obj), "expected: %v, got: %v", obj, zero)
}

type testItemType struct {
	obj  any
	data string
}

// --------------------------------------------------------------------------
// Samples from bsonspec.org:

var sampleItems = []testItemType{
	{
		M{"hello": "world"},
		"\x16\x00\x00\x00\x02hello\x00\x06\x00\x00\x00world\x00\x00",
	},
	{
		M{"BSON": []any{"awesome", float64(5.05), 1986}},
		"1\x00\x00\x00\x04BSON\x00&\x00\x00\x00\x020\x00\x08\x00\x00\x00" +
			"awesome\x00\x011\x00333333\x14@\x102\x00\xc2\x07\x00\x00\x00\x00",
	},
	{
		M{"slice": []uint8{1, 2}},
		"\x13\x00\x00\x00\x05slice\x00\x02\x00\x00\x00\x00\x01\x02\x00",
	},
	{
		M{"slice": []byte{1, 2}},
		"\x13\x00\x00\x00\x05slice\x00\x02\x00\x00\x00\x00\x01\x02\x00",
	},
}

func TestMarshalSampleItems(t *testing.T) {
	buf := new(bytes.Buffer)
	enc := new(Encoder)
	for i, item := range sampleItems {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			buf.Reset()
			vw := NewDocumentWriter(buf)
			enc.Reset(vw)
			enc.SetRegistry(NewMgoRegistry())
			err := enc.Encode(item.obj)
			assert.Nil(t, err, "expected nil error, got: %v", err)
			str := buf.String()
			assert.Equal(t, str, item.data, "expected: %v, got: %v", item.data, str)
		})
	}
}

func TestUnmarshalSampleItems(t *testing.T) {
	for i, item := range sampleItems {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			value := M{}
			err := unmarshalWithRegistry(t, NewMgoRegistry(), []byte(item.data), &value)
			assert.Nil(t, err, "expected nil error, got: %v", err)
			assert.True(t, reflect.DeepEqual(value, item.obj), "expected: %v, got: %v", item.obj, value)
		})
	}
}

// --------------------------------------------------------------------------
// Every type, ordered by the type flag. These are not wrapped with the
// length and last \x00 from the document. wrapInDoc() computes them.
// Note that all of them should be supported as two-way conversions.

var allItems = []testItemType{
	{
		M{},
		"",
	},
	{
		M{"_": float64(5.05)},
		"\x01_\x00333333\x14@",
	},
	{
		M{"_": "yo"},
		"\x02_\x00\x03\x00\x00\x00yo\x00",
	},
	{
		M{"_": M{"a": true}},
		"\x03_\x00\x09\x00\x00\x00\x08a\x00\x01\x00",
	},
	{
		M{"_": []any{true, false}},
		"\x04_\x00\r\x00\x00\x00\x080\x00\x01\x081\x00\x00\x00",
	},
	{
		M{"_": []byte("yo")},
		"\x05_\x00\x02\x00\x00\x00\x00yo",
	},
	{
		M{"_": Binary{Subtype: 0x80, Data: []byte("udef")}},
		"\x05_\x00\x04\x00\x00\x00\x80udef",
	},
	{M{"_": Undefined{}}, // Obsolete, but still seen in the wild.
		"\x06_\x00"},
	{
		M{"_": ObjectID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B}},
		"\x07_\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B",
	}, // technically this is not the same as the original mgo test
	{
		M{"_": DBPointer{DB: "testnamespace", Pointer: ObjectID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B}}},
		"\x0C_\x00\x0e\x00\x00\x00testnamespace\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B",
	},
	{
		M{"_": false},
		"\x08_\x00\x00",
	},
	{
		M{"_": true},
		"\x08_\x00\x01",
	},
	{M{"_": time.Unix(0, 258e6).UTC()}, // Note the NS <=> MS conversion.
		"\x09_\x00\x02\x01\x00\x00\x00\x00\x00\x00"},
	{
		M{"_": nil},
		"\x0A_\x00",
	},
	{
		M{"_": Regex{Pattern: "ab", Options: "cd"}},
		"\x0B_\x00ab\x00cd\x00",
	},
	{
		M{"_": JavaScript("code")},
		"\x0D_\x00\x05\x00\x00\x00code\x00",
	},
	{
		M{"_": Symbol("sym")},
		"\x0E_\x00\x04\x00\x00\x00sym\x00",
	},
	{
		M{"_": CodeWithScope{Code: "code", Scope: D{{"", nil}}}},
		"\x0F_\x00\x14\x00\x00\x00\x05\x00\x00\x00code\x00" +
			"\x07\x00\x00\x00\x0A\x00\x00",
	},
	{
		M{"_": 258},
		"\x10_\x00\x02\x01\x00\x00",
	},
	{
		M{"_": Timestamp{0, 258}},
		"\x11_\x00\x02\x01\x00\x00\x00\x00\x00\x00",
	},
	{
		M{"_": int64(258)},
		"\x12_\x00\x02\x01\x00\x00\x00\x00\x00\x00",
	},
	{
		M{"_": int64(258 << 32)},
		"\x12_\x00\x00\x00\x00\x00\x02\x01\x00\x00",
	},
	{
		M{"_": MaxKey{}},
		"\x7F_\x00",
	},
	{
		M{"_": MinKey{}},
		"\xFF_\x00",
	},
}

func TestMarshalAllItems(t *testing.T) {
	buf := new(bytes.Buffer)
	enc := new(Encoder)
	for i, item := range allItems {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			buf.Reset()
			vw := NewDocumentWriter(buf)
			enc.Reset(vw)
			enc.SetRegistry(NewMgoRegistry())
			err := enc.Encode(item.obj)
			assert.Nil(t, err, "expected nil error, got: %v", err)
			str := buf.String()
			assert.Equal(t, str, wrapInDoc(item.data), "expected: %v, got: %v", wrapInDoc(item.data), str)
		})
	}
}

func TestUnmarshalAllItems(t *testing.T) {
	for i, item := range allItems {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			value := M{}
			err := unmarshalWithRegistry(t, NewMgoRegistry(), []byte(wrapInDoc(item.data)), &value)
			assert.Nil(t, err, "expected nil error, got: %v", err)
			assert.True(t, reflect.DeepEqual(value, item.obj), "expected: %v, got: %v", item.obj, value)
		})
	}
}

func TestUnmarshalRawAllItems(t *testing.T) {
	for i, item := range allItems {
		if len(item.data) == 0 {
			continue
		}
		value := item.obj.(M)["_"]
		if value == nil {
			continue
		}
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			pv := reflect.New(reflect.ValueOf(value).Type())
			raw := RawValue{Type: Type(item.data[0]), Value: []byte(item.data[3:])}
			err := raw.UnmarshalWithRegistry(NewMgoRegistry(), pv.Interface())
			assert.Nil(t, err, "expected nil error, got: %v", err)
			assert.True(t, reflect.DeepEqual(value, pv.Elem().Interface()), "expected: %v, got: %v", value, pv.Elem().Interface())
		})
	}
}

func TestUnmarshalRawIncompatible(t *testing.T) {
	raw := RawValue{Type: 0x08, Value: []byte{0x01}} // true
	err := raw.UnmarshalWithRegistry(NewMgoRegistry(), &struct{}{})
	assert.NotNil(t, err, "expected an error")
}

func TestUnmarshalZeroesStruct(t *testing.T) {
	buf := new(bytes.Buffer)
	vw := NewDocumentWriter(buf)
	enc := NewEncoder(vw)
	enc.SetRegistry(NewMgoRegistry())
	err := enc.Encode(M{"b": 2})
	assert.Nil(t, err, "expected nil error, got: %v", err)
	type T struct{ A, B int }
	v := T{A: 1}
	err = unmarshalWithRegistry(t, NewMgoRegistry(), buf.Bytes(), &v)
	assert.Nil(t, err, "expected nil error, got: %v", err)
	assert.Equal(t, 0, v.A, "expected: 0, got: %v", v.A)
	assert.Equal(t, 2, v.B, "expected: 2, got: %v", v.B)
}

func TestUnmarshalZeroesMap(t *testing.T) {
	buf := new(bytes.Buffer)
	vw := NewDocumentWriter(buf)
	enc := NewEncoder(vw)
	enc.SetRegistry(NewMgoRegistry())
	err := enc.Encode(M{"b": 2})
	assert.Nil(t, err, "expected nil error, got: %v", err)
	m := M{"a": 1}
	err = unmarshalWithRegistry(t, NewMgoRegistry(), buf.Bytes(), &m)
	assert.Nil(t, err, "expected nil error, got: %v", err)

	want := M{"b": 2}
	assert.True(t, reflect.DeepEqual(want, m), "expected: %v, got: %v", want, m)
}

func TestUnmarshalNonNilInterface(t *testing.T) {
	buf := new(bytes.Buffer)
	vw := NewDocumentWriter(buf)
	enc := NewEncoder(vw)
	enc.SetRegistry(NewMgoRegistry())
	err := enc.Encode(M{"b": 2})
	assert.Nil(t, err, "expected nil error, got: %v", err)
	m := M{"a": 1}
	var i any = m
	err = unmarshalWithRegistry(t, NewMgoRegistry(), buf.Bytes(), &i)
	assert.Nil(t, err, "expected nil error, got: %v", err)
	assert.True(t, reflect.DeepEqual(M{"b": 2}, i), "expected: %v, got: %v", M{"b": 2}, i)
	assert.True(t, reflect.DeepEqual(M{"a": 1}, m), "expected: %v, got: %v", M{"a": 1}, m)
}

func TestPtrInline(t *testing.T) {
	cases := []struct {
		In  any
		Out M
	}{
		{
			In:  InlinePtrStruct{A: 1, MStruct: &MStruct{M: 3}},
			Out: M{"a": 1, "m": 3},
		},
		{ // go deeper
			In:  inlinePtrPtrStruct{B: 10, InlinePtrStruct: &InlinePtrStruct{A: 20, MStruct: &MStruct{M: 30}}},
			Out: M{"b": 10, "a": 20, "m": 30},
		},
		{
			// nil embed struct
			In:  &InlinePtrStruct{A: 3},
			Out: M{"a": 3},
		},
		{
			// nil embed struct
			In:  &inlinePtrPtrStruct{B: 5},
			Out: M{"b": 5},
		},
	}

	buf := new(bytes.Buffer)
	enc := new(Encoder)
	for i, cs := range cases {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			buf.Reset()
			vw := NewDocumentWriter(buf)
			enc.Reset(vw)
			enc.SetRegistry(NewMgoRegistry())
			err := enc.Encode(cs.In)
			assert.Nil(t, err, "expected nil error, got: %v", err)
			var dataBSON M
			err = unmarshalWithRegistry(t, NewMgoRegistry(), buf.Bytes(), &dataBSON)
			assert.Nil(t, err, "expected nil error, got: %v", err)

			assert.True(t, reflect.DeepEqual(cs.Out, dataBSON), "expected: %v, got: %v", cs.Out, dataBSON)
		})
	}
}

// --------------------------------------------------------------------------
// Some one way marshaling operations which would unmarshal differently.

var js = JavaScript("code")

var oneWayMarshalItems = []testItemType{
	// These are being passed as pointers, and will unmarshal as values.
	{
		M{"": &Binary{Subtype: 0x02, Data: []byte("old")}},
		"\x05\x00\x07\x00\x00\x00\x02\x03\x00\x00\x00old",
	},
	{
		M{"": &Binary{Subtype: 0x80, Data: []byte("udef")}},
		"\x05\x00\x04\x00\x00\x00\x80udef",
	},
	{
		M{"": &Regex{Pattern: "ab", Options: "cd"}},
		"\x0B\x00ab\x00cd\x00",
	},
	{
		M{"": &js},
		"\x0D\x00\x05\x00\x00\x00code\x00",
	},
	{
		M{"": &CodeWithScope{Code: "code", Scope: M{"": nil}}},
		"\x0F\x00\x14\x00\x00\x00\x05\x00\x00\x00code\x00" +
			"\x07\x00\x00\x00\x0A\x00\x00",
	},

	// There's no float32 type in BSON.  Will encode as a float64.
	{
		M{"": float32(5.05)},
		"\x01\x00\x00\x00\x00@33\x14@",
	},

	// The array will be unmarshaled as a slice instead.
	{
		M{"": [2]bool{true, false}},
		"\x04\x00\r\x00\x00\x00\x080\x00\x01\x081\x00\x00\x00",
	},

	// The typed slice will be unmarshaled as []any.
	{
		M{"": []bool{true, false}},
		"\x04\x00\r\x00\x00\x00\x080\x00\x01\x081\x00\x00\x00",
	},

	// Will unmarshal as a []byte.
	{
		M{"": Binary{Subtype: 0x00, Data: []byte("yo")}},
		"\x05\x00\x02\x00\x00\x00\x00yo",
	},
	{
		M{"": Binary{Subtype: 0x02, Data: []byte("old")}},
		"\x05\x00\x07\x00\x00\x00\x02\x03\x00\x00\x00old",
	},

	// No way to preserve the type information here. We might encode as a zero
	// value, but this would mean that pointer values in structs wouldn't be
	// able to correctly distinguish between unset and set to the zero value.
	{
		M{"": (*byte)(nil)},
		"\x0A\x00",
	},

	// No int types smaller than int32 in BSON. Could encode this as a char,
	// but it would still be ambiguous, take more, and be awkward in Go when
	// loaded without typing information.
	{
		M{"": byte(8)},
		"\x10\x00\x08\x00\x00\x00",
	},

	// There are no unsigned types in BSON.  Will unmarshal as int32 or int64.
	{
		M{"": uint32(258)},
		"\x10\x00\x02\x01\x00\x00",
	},
	{
		M{"": uint64(258)},
		"\x12\x00\x02\x01\x00\x00\x00\x00\x00\x00",
	},
	{
		M{"": uint64(258 << 32)},
		"\x12\x00\x00\x00\x00\x00\x02\x01\x00\x00",
	},

	// This will unmarshal as int.
	{
		M{"": int32(258)},
		"\x10\x00\x02\x01\x00\x00",
	},

	// That's a special case. The unsigned value is too large for an int32,
	// so an int64 is used instead.
	{
		M{"": uint32(1<<32 - 1)},
		"\x12\x00\xFF\xFF\xFF\xFF\x00\x00\x00\x00",
	},
	{
		M{"": uint(1<<32 - 1)},
		"\x12\x00\xFF\xFF\xFF\xFF\x00\x00\x00\x00",
	},
}

func TestOneWayMarshalItems(t *testing.T) {
	buf := new(bytes.Buffer)
	enc := new(Encoder)
	for i, item := range oneWayMarshalItems {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			buf.Reset()
			vw := NewDocumentWriter(buf)
			enc.Reset(vw)
			enc.SetRegistry(NewMgoRegistry())
			err := enc.Encode(item.obj)
			assert.Nil(t, err, "expected nil error, got: %v", err)

			assert.Equal(t, wrapInDoc(item.data), buf.String(), "expected: %v, got: %v", Raw(wrapInDoc(item.data)), Raw(buf.Bytes()))
		})
	}
}

// --------------------------------------------------------------------------
// Two-way tests for user-defined structures using the samples
// from bsonspec.org.

type specSample1 struct {
	Hello string
}

type specSample2 struct {
	BSON []any `bson:"BSON"`
}

var structSampleItems = []testItemType{
	{
		&specSample1{"world"},
		"\x16\x00\x00\x00\x02hello\x00\x06\x00\x00\x00world\x00\x00",
	},
	{
		&specSample2{[]any{"awesome", float64(5.05), 1986}},
		"1\x00\x00\x00\x04BSON\x00&\x00\x00\x00\x020\x00\x08\x00\x00\x00" +
			"awesome\x00\x011\x00333333\x14@\x102\x00\xc2\x07\x00\x00\x00\x00",
	},
}

func TestMarshalStructSampleItems(t *testing.T) {
	buf := new(bytes.Buffer)
	enc := new(Encoder)
	for i, item := range structSampleItems {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			buf.Reset()
			vw := NewDocumentWriter(buf)
			enc.Reset(vw)
			enc.SetRegistry(NewMgoRegistry())
			err := enc.Encode(item.obj)
			assert.Nil(t, err, "expected nil error, got: %v", err)
			assert.Equal(t, item.data, buf.String(), "expected: %v, got: %v", item.data, buf.String())
		})
	}
}

func TestUnmarshalStructSampleItems(t *testing.T) {
	for i, item := range structSampleItems {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			testUnmarshal(t, item.data, item.obj)
		})
	}
}

func Test64bitInt(t *testing.T) {
	var i int64 = (1 << 31)
	if int(i) > 0 {
		buf := new(bytes.Buffer)
		vw := NewDocumentWriter(buf)
		enc := NewEncoder(vw)
		enc.SetRegistry(NewMgoRegistry())
		err := enc.Encode(M{"i": int(i)})
		assert.Nil(t, err, "expected nil error, got: %v", err)
		want := wrapInDoc("\x12i\x00\x00\x00\x00\x80\x00\x00\x00\x00")
		assert.Equal(t, want, buf.String(), "expected: %v, got: %v", want, buf.String())

		var result struct{ I int }
		err = unmarshalWithRegistry(t, NewMgoRegistry(), buf.Bytes(), &result)
		assert.Nil(t, err, "expected nil error, got: %v", err)
		assert.Equal(t, i, int64(result.I), "expected: %v, got: %v", i, int64(result.I))
	}
}

// --------------------------------------------------------------------------
// Generic two-way struct marshaling tests.

type (
	prefixPtr string
	prefixVal string
)

func (t *prefixPtr) GetBSON() (any, error) {
	if t == nil {
		return nil, nil
	}
	return "foo-" + string(*t), nil
}

func (t *prefixPtr) SetBSON(raw RawValue) error {
	var s string
	if raw.Type == 0x0A {
		return ErrMgoSetZero
	}
	rval := reflect.ValueOf(&s).Elem()
	decoder, err := NewMgoRegistry().LookupDecoder(rval.Type())
	if err != nil {
		return err
	}
	vr := newBufferedValueReader(raw.Type, raw.Value)
	err = decoder.DecodeValue(DecodeContext{Registry: NewMgoRegistry()}, vr, rval)
	if err != nil {
		return err
	}

	if !strings.HasPrefix(s, "foo-") {
		return errors.New("Prefix not found: " + s)
	}
	*t = prefixPtr(s[4:])
	return nil
}

func (t prefixVal) GetBSON() (any, error) {
	return "foo-" + string(t), nil
}

func (t *prefixVal) SetBSON(raw RawValue) error {
	var s string
	if raw.Type == 0x0A {
		return ErrMgoSetZero
	}
	rval := reflect.ValueOf(&s).Elem()
	decoder, err := NewMgoRegistry().LookupDecoder(rval.Type())
	if err != nil {
		return err
	}
	vr := newBufferedValueReader(raw.Type, raw.Value)
	err = decoder.DecodeValue(DecodeContext{Registry: NewMgoRegistry()}, vr, rval)
	if err != nil {
		return err
	}

	if !strings.HasPrefix(s, "foo-") {
		return errors.New("Prefix not found: " + s)
	}
	*t = prefixVal(s[4:])
	return nil
}

var (
	bytevar   = byte(8)
	byteptr   = &bytevar
	prefixptr = prefixPtr("bar")
	prefixval = prefixVal("bar")
)

var structItems = []testItemType{
	{
		&struct{ Ptr *byte }{nil},
		"\x0Aptr\x00",
	},
	{
		&struct{ Ptr *byte }{&bytevar},
		"\x10ptr\x00\x08\x00\x00\x00",
	},
	{
		&struct{ Ptr **byte }{&byteptr},
		"\x10ptr\x00\x08\x00\x00\x00",
	},
	{
		&struct{ Byte byte }{8},
		"\x10byte\x00\x08\x00\x00\x00",
	},
	{
		&struct{ Byte byte }{0},
		"\x10byte\x00\x00\x00\x00\x00",
	},
	{
		&struct {
			V byte `bson:"Tag"`
		}{8},
		"\x10Tag\x00\x08\x00\x00\x00",
	},
	{
		&struct {
			V *struct {
				Byte byte
			}
		}{&struct{ Byte byte }{8}},
		"\x03v\x00" + "\x0f\x00\x00\x00\x10byte\x00\b\x00\x00\x00\x00",
	},
	{&struct{ priv byte }{}, ""},

	// The order of the dumped fields should be the same in the struct.
	{
		&struct{ A, C, B, D, F, E *byte }{},
		"\x0Aa\x00\x0Ac\x00\x0Ab\x00\x0Ad\x00\x0Af\x00\x0Ae\x00",
	},

	{
		&struct{ V RawValue }{RawValue{Type: 0x03, Value: []byte("\x0f\x00\x00\x00\x10byte\x00\b\x00\x00\x00\x00")}},
		"\x03v\x00" + "\x0f\x00\x00\x00\x10byte\x00\b\x00\x00\x00\x00",
	},
	{
		&struct{ V RawValue }{RawValue{Type: 0x10, Value: []byte("\x00\x00\x00\x00")}},
		"\x10v\x00" + "\x00\x00\x00\x00",
	},

	// Byte arrays.
	{
		&struct{ V [2]byte }{[2]byte{'y', 'o'}},
		"\x05v\x00\x02\x00\x00\x00\x00yo",
	},

	{
		&struct{ V prefixPtr }{prefixPtr("buzz")},
		"\x02v\x00\x09\x00\x00\x00foo-buzz\x00",
	},

	{
		&struct{ V *prefixPtr }{&prefixptr},
		"\x02v\x00\x08\x00\x00\x00foo-bar\x00",
	},

	{
		&struct{ V *prefixPtr }{nil},
		"\x0Av\x00",
	},

	{
		&struct{ V prefixVal }{prefixVal("buzz")},
		"\x02v\x00\x09\x00\x00\x00foo-buzz\x00",
	},

	{
		&struct{ V *prefixVal }{&prefixval},
		"\x02v\x00\x08\x00\x00\x00foo-bar\x00",
	},

	{
		&struct{ V *prefixVal }{nil},
		"\x0Av\x00",
	},
}

func TestMarshalStructItems(t *testing.T) {
	buf := new(bytes.Buffer)
	enc := new(Encoder)
	for i, item := range structItems {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			buf.Reset()
			vw := NewDocumentWriter(buf)
			enc.Reset(vw)
			enc.SetRegistry(NewMgoRegistry())
			err := enc.Encode(item.obj)
			assert.Nil(t, err, "expected nil error, got: %v", err)
			assert.Equal(t, wrapInDoc(item.data), buf.String(), "expected: %v, got: %v", wrapInDoc(item.data), buf.String())
		})
	}
}

func TestUnmarshalStructItems(t *testing.T) {
	for i, item := range structItems {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			testUnmarshal(t, wrapInDoc(item.data), item.obj)
		})
	}
}

func TestUnmarshalRawStructItems(t *testing.T) {
	for i, item := range structItems {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			raw := Raw(wrapInDoc(item.data))
			zero := makeZeroDoc(item.obj)
			err := unmarshalWithRegistry(t, NewMgoRegistry(), raw, zero)
			assert.Nil(t, err, "expected nil error, got: %v", err)
			assert.True(t, reflect.DeepEqual(item.obj, zero), "expected: %v, got: %v", item.obj, zero)
		})
	}
}

// func TestUnmarshalRawNil(t *testing.T) {
// 	// Regression test: shouldn't try to nil out the pointer itself,
// 	// as it's not settable.
// 	raw := RawValue{Type: 0x0A, Value: []byte{}}
// 	err := raw.UnmarshalWithRegistry(Registry, &struct{}{})
// 	assert.Nil(t, err, "expected nil error, got: %v", err)
// }

// --------------------------------------------------------------------------
// One-way marshaling tests.

type dOnIface struct {
	D any
}

type ignoreField struct {
	Before string
	Ignore string `bson:"-"`
	After  string
}

var marshalItems = []testItemType{
	// Ordered document dump.  Will unmarshal as a dictionary by default.
	{
		D{{"a", nil}, {"c", nil}, {"b", nil}, {"d", nil}, {"f", nil}, {"e", true}},
		"\x0Aa\x00\x0Ac\x00\x0Ab\x00\x0Ad\x00\x0Af\x00\x08e\x00\x01",
	},
	{
		MyD{{"a", nil}, {"c", nil}, {"b", nil}, {"d", nil}, {"f", nil}, {"e", true}},
		"\x0Aa\x00\x0Ac\x00\x0Ab\x00\x0Ad\x00\x0Af\x00\x08e\x00\x01",
	},
	{
		&dOnIface{D{{"a", nil}, {"c", nil}, {"b", nil}, {"d", true}}},
		"\x03d\x00" + wrapInDoc("\x0Aa\x00\x0Ac\x00\x0Ab\x00\x08d\x00\x01"),
	},

	{
		&ignoreField{"before", "ignore", "after"},
		"\x02before\x00\a\x00\x00\x00before\x00\x02after\x00\x06\x00\x00\x00after\x00",
	},

	// Marshalling a Raw document does nothing.
	// {RawValue{Type: 0x03, Value: []byte(wrapInDoc("anything"))},
	// 	"anything"},
	// {RawValue{Value: []byte(wrapInDoc("anything"))},
	// 	"anything"},
}

func TestMarshalOneWayItems(t *testing.T) {
	buf := new(bytes.Buffer)
	enc := new(Encoder)
	for i, item := range marshalItems {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			buf.Reset()
			vw := NewDocumentWriter(buf)
			enc.Reset(vw)
			enc.SetRegistry(NewMgoRegistry())
			err := enc.Encode(item.obj)
			assert.Nil(t, err, "expected nil error, got: %v", err)
			assert.Equal(t, wrapInDoc(item.data), buf.String(), "expected: %v, got: %v", wrapInDoc(item.data), buf.String())
		})
	}
}

// --------------------------------------------------------------------------
// One-way unmarshaling tests.

type intAlias int

var unmarshalItems = []testItemType{
	// Field is private.  Should not attempt to unmarshal it.
	{
		&struct{ priv byte }{},
		"\x10priv\x00\x08\x00\x00\x00",
	},

	// Ignore non-existing field.
	{
		&struct{ Byte byte }{9},
		"\x10boot\x00\x08\x00\x00\x00" + "\x10byte\x00\x09\x00\x00\x00",
	},

	// Do not unmarshal on ignored field.
	{
		&ignoreField{"before", "", "after"},
		"\x02before\x00\a\x00\x00\x00before\x00" +
			"\x02-\x00\a\x00\x00\x00ignore\x00" +
			"\x02after\x00\x06\x00\x00\x00after\x00",
	},

	// Ordered document.
	{
		&struct{ D }{D{{"a", nil}, {"c", nil}, {"b", nil}, {"d", true}}},
		"\x03d\x00" + wrapInDoc("\x0Aa\x00\x0Ac\x00\x0Ab\x00\x08d\x00\x01"),
	},

	// Decode old binary.
	{
		M{"_": []byte("old")},
		"\x05_\x00\x07\x00\x00\x00\x02\x03\x00\x00\x00old",
	},

	// Decode old binary without length. According to the spec, this shouldn't happen.
	{
		M{"_": []byte("old")},
		"\x05_\x00\x03\x00\x00\x00\x02old",
	},

	// int key maps
	{
		map[int]string{10: "s"},
		"\x0210\x00\x02\x00\x00\x00s\x00",
	},

	//// event if type is alias to int
	{
		map[intAlias]string{10: "s"},
		"\x0210\x00\x02\x00\x00\x00s\x00",
	},
}

func TestUnmarshalOneWayItems(t *testing.T) {
	for i, item := range unmarshalItems {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			testUnmarshal(t, wrapInDoc(item.data), item.obj)
		})
	}
}

func TestUnmarshalNilInStruct(t *testing.T) {
	// Nil is the default value, so we need to ensure it's indeed being set.
	b := byte(1)
	v := &struct{ Ptr *byte }{&b}
	err := unmarshalWithRegistry(t, NewMgoRegistry(), []byte(wrapInDoc("\x0Aptr\x00")), v)
	assert.Nil(t, err, "expected nil error, got: %v", err)

	want := &struct{ Ptr *byte }{nil}
	assert.Equal(t, *want, *v, "expected: %v, got: %v", *want, *v)
}

// --------------------------------------------------------------------------
// Marshalling error cases.

type structWithDupKeys struct {
	Name  byte
	Other byte `bson:"name"` // Tag should precede.
}

var marshalErrorItems = []testItemType{
	{
		M{"": uint64(1 << 63)},
		"BSON has no uint64 type, and value is too large to fit correctly in an int64",
	},
	{
		int64(123),
		"Can't marshal int64 as a BSON document",
	},
	{
		M{"": 1i},
		"Can't marshal complex128 in a BSON document",
	},
	{
		&structWithDupKeys{},
		"Duplicated key 'name' in struct bson_test.structWithDupKeys",
	},
	{
		RawValue{Type: 0xA, Value: []byte{}},
		"Attempted to marshal Raw kind 10 as a document",
	},
	{
		Raw{},
		"Attempted to marshal empty Raw document",
	},
	{
		M{"w": Raw{}},
		"Attempted to marshal empty Raw document",
	},
	{
		&inlineDupName{1, struct{ A, B int }{2, 3}},
		"Duplicated key 'a' in struct bson_test.inlineDupName",
	},
	{
		&inlineDupMap{},
		"Multiple ,inline maps in struct bson_test.inlineDupMap",
	},
	{
		&inlineBadKeyMap{},
		"Option ,inline needs a map with string keys in struct bson_test.inlineBadKeyMap",
	},
	{
		&inlineMap{A: 1, M: map[string]any{"a": 1}},
		`Can't have key "a" in inlined map; conflicts with struct field`,
	},
}

func TestMarshalErrorItems(t *testing.T) {
	buf := new(bytes.Buffer)
	enc := new(Encoder)
	for i, item := range marshalErrorItems {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			buf.Reset()
			vw := NewDocumentWriter(buf)
			enc.Reset(vw)
			enc.SetRegistry(NewMgoRegistry())
			err := enc.Encode(item.obj)

			assert.NotNil(t, err, "expected error")
			assert.Nil(t, buf.Bytes(), " expected nil data, got: %v", buf.Bytes())
		})
	}
}

// --------------------------------------------------------------------------
// Unmarshalling error cases.

type unmarshalErrorType struct {
	obj  any
	data string
}

var unmarshalErrorItems = []unmarshalErrorType{
	// Tag name conflicts with existing parameter.
	{
		obj:  &structWithDupKeys{},
		data: "\x10name\x00\x08\x00\x00\x00",
	},
	{
		obj:  nil,
		data: "\xEEname\x00",
	},
	{
		obj:  struct{ Name bool }{},
		data: "\x10name\x00\x08\x00\x00\x00",
	},
	{
		obj:  123,
		data: "\x10name\x00\x08\x00\x00\x00",
	},
	{
		obj:  nil,
		data: "\x08\x62\x00\x02",
	},
	// Non-string and not numeric map key.
	{
		obj:  map[bool]any{true: 1},
		data: "\x10true\x00\x01\x00\x00\x00",
	},
}

func TestUnmarshalErrorItems(t *testing.T) {
	for i, item := range unmarshalErrorItems {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			data := []byte(wrapInDoc(item.data))
			var value any
			switch reflect.ValueOf(item.obj).Kind() {
			case reflect.Map, reflect.Ptr:
				value = makeZeroDoc(item.obj)
			case reflect.Invalid:
				value = M{}
			default:
				value = item.obj
			}
			err := unmarshalWithRegistry(t, NewMgoRegistry(), data, value)
			assert.NotNil(t, err, "expected error")
		})
	}
}

type unmarshalRawErrorType struct {
	obj any
	raw RawValue
}

var unmarshalRawErrorItems = []unmarshalRawErrorType{
	// Tag name conflicts with existing parameter.
	{
		obj: &structWithDupKeys{},
		raw: RawValue{Type: 0x03, Value: []byte("\x10byte\x00\x08\x00\x00\x00")},
	},
	{
		obj: &struct{}{},
		raw: RawValue{Type: 0xEE, Value: []byte{}},
	},
	{
		obj: struct{ Name bool }{},
		raw: RawValue{Type: 0x10, Value: []byte("\x08\x00\x00\x00")},
	},
	{
		obj: 123,
		raw: RawValue{Type: 0x10, Value: []byte("\x08\x00\x00\x00")},
	},
}

func TestUnmarshalRawErrorItems(t *testing.T) {
	for i, item := range unmarshalRawErrorItems {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			err := item.raw.UnmarshalWithRegistry(NewMgoRegistry(), item.obj)
			assert.NotNil(t, err, "expected error")
		})
	}
}

var corruptedData = []string{
	"\x04\x00\x00\x00\x00",         // Document shorter than minimum
	"\x06\x00\x00\x00\x00",         // Not enough data
	"\x05\x00\x00",                 // Broken length
	"\x05\x00\x00\x00\xff",         // Corrupted termination
	"\x0A\x00\x00\x00\x0Aooop\x00", // Unfinished C string

	// Array end past end of string (s[2]=0x07 is correct)
	wrapInDoc("\x04\x00\x09\x00\x00\x00\x0A\x00\x00"),

	// Array end within string, but past acceptable.
	wrapInDoc("\x04\x00\x08\x00\x00\x00\x0A\x00\x00"),

	// Document end within string, but past acceptable.
	wrapInDoc("\x03\x00\x08\x00\x00\x00\x0A\x00\x00"),

	// // String with corrupted end.
	// wrapInDoc("\x02\x00\x03\x00\x00\x00yo\xFF"),

	// String with negative length (issue #116).
	"\x0c\x00\x00\x00\x02x\x00\xff\xff\xff\xff\x00",

	// // String with zero length (must include trailing '\x00')
	// "\x0c\x00\x00\x00\x02x\x00\x00\x00\x00\x00\x00",

	// Binary with negative length.
	"\r\x00\x00\x00\x05x\x00\xff\xff\xff\xff\x00\x00",
}

func TestUnmarshalMapDocumentTooShort(t *testing.T) {
	for i, data := range corruptedData {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			err := unmarshalWithRegistry(t, NewMgoRegistry(), []byte(data), M{})
			assert.NotNil(t, err, "expected error, got nil")

			err = unmarshalWithRegistry(t, NewMgoRegistry(), []byte(data), &struct{}{})
			assert.NotNil(t, err, "expected error, got nil")
		})
	}
}

// --------------------------------------------------------------------------
// Setter test cases.

var setterResult = map[string]error{}

type setterType struct {
	Received any
}

func (o *setterType) SetBSON(raw RawValue) error {
	rval := reflect.ValueOf(o).Elem().Field(0)
	decoder, err := NewMgoRegistry().LookupDecoder(rval.Type())
	if err != nil {
		return err
	}
	if raw.Type == 0x00 {
		raw.Type = TypeEmbeddedDocument
	}
	vr := newBufferedValueReader(raw.Type, raw.Value)
	err = decoder.DecodeValue(DecodeContext{Registry: NewMgoRegistry()}, vr, rval)
	if err != nil {
		return err
	}

	if s, ok := o.Received.(string); ok {
		if result, ok := setterResult[s]; ok {
			return result
		}
	}
	return nil
}

type ptrSetterDoc struct {
	Field *setterType `bson:"_"`
}

type valSetterDoc struct {
	Field setterType `bson:"_"`
}

func TestUnmarshalAllItemsWithPtrSetter(t *testing.T) {
	for ind, item := range allItems {
		if ind == 3 {
			continue
		}
		t.Run(strconv.Itoa(ind), func(t *testing.T) {
			for i := 0; i != 2; i++ {
				var field *setterType
				if i == 0 {
					obj := &ptrSetterDoc{}
					err := unmarshalWithRegistry(t, NewMgoRegistry(), []byte(wrapInDoc(item.data)), obj)
					assert.Nil(t, err, "expected nil error, got: %v", err)
					field = obj.Field
				} else {
					obj := &valSetterDoc{}
					err := unmarshalWithRegistry(t, NewMgoRegistry(), []byte(wrapInDoc(item.data)), obj)
					assert.Nil(t, err, "expected nil error, got: %v", err)
					field = &obj.Field
				}
				if item.data == "" {
					// Nothing to unmarshal. Should be untouched.
					if i == 0 {
						assert.Nil(t, field, "expected field to be nil, got: %v", field)
					} else {
						assert.Nil(t, field.Received, "expected field.received to be nil, got: %v", field.Received)
					}
				} else {
					expected := item.obj.(M)["_"]
					assert.NotNil(t, field, "Pointer not initialized (%#v)", expected)

					assert.True(t, reflect.DeepEqual(expected, field.Received), "expected field.received to be: %v, got: %v", expected, field.Received)
				}
			}
		})
	}
}

func TestUnmarshalWholeDocumentWithSetter(t *testing.T) {
	obj := &setterType{}
	err := unmarshalWithRegistry(t, NewMgoRegistry(), []byte(sampleItems[0].data), obj)
	assert.Nil(t, err, "expected nil error, got: %v", err)
	assert.True(t, reflect.DeepEqual(M{"hello": "world"}, obj.Received), "expected obj.received to be: %v, got: %v", M{"hello": "world"}, obj.Received)
}

func TestUnmarshalSetterErrors(t *testing.T) {
	boom := errors.New("BOOM")
	setterResult["2"] = boom
	defer delete(setterResult, "2")

	m := map[string]*setterType{}
	data := wrapInDoc("\x02abc\x00\x02\x00\x00\x001\x00" +
		"\x02def\x00\x02\x00\x00\x002\x00" +
		"\x02ghi\x00\x02\x00\x00\x003\x00")
	err := unmarshalWithRegistry(t, NewMgoRegistry(), []byte(data), m)
	assert.NotNil(t, err, "expected unmarshal error %v, got nil", boom)

	// It's not possible to generate the actual expected error here because it's an *UnmarshalError, which is defined
	// in bsoncodec and only contains unexported fields.
	expectedErr := errors.New("error decoding key def: BOOM")
	assert.Equal(t, expectedErr.Error(), err.Error(), "expected unmarshal error %v, got %v", expectedErr, err)

	assert.NotNil(t, m["abc"], "expected value not to be nil")
	assert.Nil(t, m["def"], "expected value to be nil, got: %v", m["def"])
	assert.Nil(t, m["ghi"], "expected value to be nil, got: %v", m["ghi"])

	assert.Equal(t, "1", m["abc"].Received, "expected m[\"abc\"].Received to be: %v, got: %v", "1", m["abc"].Received)
}

func TestUnmarshalSetterErrSetZero(t *testing.T) {
	setterResult["foo"] = ErrMgoSetZero
	defer delete(setterResult, "field")

	buf := new(bytes.Buffer)
	vw := NewDocumentWriter(buf)
	enc := NewEncoder(vw)
	enc.SetRegistry(NewMgoRegistry())
	err := enc.Encode(M{"field": "foo"})
	assert.Nil(t, err, "expected nil error, got: %v", err)

	m := map[string]*setterType{}
	err = unmarshalWithRegistry(t, NewMgoRegistry(), buf.Bytes(), m)
	assert.Nil(t, err, "expected nil error, got: %v", err)

	value, ok := m["field"]
	assert.True(t, reflect.DeepEqual(true, ok), "expected ok to be: %v, got: %v", true, ok)
	assert.Nil(t, value, "expected nil value, got: %v", value)
}

// --------------------------------------------------------------------------
// Getter test cases.

type typeWithGetter struct {
	result any
	err    error
}

func (t *typeWithGetter) GetBSON() (any, error) {
	if t == nil {
		return "<value is nil>", nil
	}
	return t.result, t.err
}

type docWithGetterField struct {
	Field *typeWithGetter `bson:"_"`
}

func TestMarshalAllItemsWithGetter(t *testing.T) {
	buf := new(bytes.Buffer)
	enc := new(Encoder)
	for i, item := range allItems {
		if item.data == "" {
			continue
		}
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			buf.Reset()
			obj := &docWithGetterField{}
			obj.Field = &typeWithGetter{result: item.obj.(M)["_"]}
			vw := NewDocumentWriter(buf)
			enc.Reset(vw)
			enc.SetRegistry(NewMgoRegistry())
			err := enc.Encode(obj)
			assert.Nil(t, err, "expected nil error, got: %v", err)
			assert.Equal(t, wrapInDoc(item.data), buf.String(),
				"expected value at %v to be: %v, got: %v", i, wrapInDoc(item.data), buf.String())
		})
	}
}

func TestMarshalWholeDocumentWithGetter(t *testing.T) {
	obj := &typeWithGetter{result: sampleItems[0].obj}
	buf := new(bytes.Buffer)
	vw := NewDocumentWriter(buf)
	enc := NewEncoder(vw)
	enc.SetRegistry(NewMgoRegistry())
	err := enc.Encode(obj)
	assert.Nil(t, err, "expected nil error, got: %v", err)
	assert.Equal(t, sampleItems[0].data, buf.String(),
		"expected: %v, got: %v", sampleItems[0].data, buf.String())
}

func TestGetterErrors(t *testing.T) {
	e := errors.New("oops")

	obj1 := &docWithGetterField{}
	obj1.Field = &typeWithGetter{sampleItems[0].obj, e}
	buf := new(bytes.Buffer)
	vw := NewDocumentWriter(buf)
	enc := NewEncoder(vw)
	enc.SetRegistry(NewMgoRegistry())
	err := enc.Encode(obj1)
	assert.Equal(t, e, err, "expected error: %v, got: %v", e, err)
	assert.Nil(t, buf.Bytes(), "expected nil data, got: %v", buf.Bytes())

	obj2 := &typeWithGetter{sampleItems[0].obj, e}
	buf.Reset()
	vw = NewDocumentWriter(buf)
	enc = NewEncoder(vw)
	enc.SetRegistry(NewMgoRegistry())
	err = enc.Encode(obj2)
	assert.Equal(t, e, err, "expected error: %v, got: %v", e, err)
	assert.Nil(t, buf.Bytes(), "expected nil data, got: %v", buf.Bytes())
}

type intGetter int64

func (t intGetter) GetBSON() (any, error) {
	return int64(t), nil
}

type typeWithIntGetter struct {
	V intGetter `bson:",minsize"`
}

func TestMarshalShortWithGetter(t *testing.T) {
	obj := typeWithIntGetter{42}
	buf := new(bytes.Buffer)
	vw := NewDocumentWriter(buf)
	enc := NewEncoder(vw)
	enc.SetRegistry(NewMgoRegistry())
	err := enc.Encode(obj)
	assert.Nil(t, err, "expected nil error, got: %v", err)
	m := M{}
	err = unmarshalWithRegistry(t, NewMgoRegistry(), buf.Bytes(), &m)
	assert.Nil(t, err, "expected nil error, got: %v", err)
	assert.Equal(t, 42, m["v"], "expected m[\"v\"] to be: %v, got: %v", 42, m["v"])
}

func TestMarshalWithGetterNil(t *testing.T) {
	obj := docWithGetterField{}
	buf := new(bytes.Buffer)
	vw := NewDocumentWriter(buf)
	enc := NewEncoder(vw)
	enc.SetRegistry(NewMgoRegistry())
	err := enc.Encode(obj)
	assert.Nil(t, err, "expected nil error, got: %v", err)
	m := M{}
	err = unmarshalWithRegistry(t, NewMgoRegistry(), buf.Bytes(), &m)
	assert.Nil(t, err, "expected nil error, got: %v", err)
	want := M{"_": "<value is nil>"}
	assert.Equal(t, want, m, "expected m[\"v\"] to be: %v, got: %v", want, m)
}

// --------------------------------------------------------------------------
// Cross-type conversion tests.

type crossTypeItem struct {
	obj1 any
	obj2 any
}

type condStr struct {
	V string `bson:",omitempty"`
}
type condStrNS struct {
	V string `a:"A" bson:",omitempty" b:"B"`
}
type condBool struct {
	V bool `bson:",omitempty"`
}
type condInt struct {
	V int `bson:",omitempty"`
}
type condUInt struct {
	V uint `bson:",omitempty"`
}
type condFloat struct {
	V float64 `bson:",omitempty"`
}
type condIface struct {
	V any `bson:",omitempty"`
}
type condPtr struct {
	V *bool `bson:",omitempty"`
}
type condSlice struct {
	V []string `bson:",omitempty"`
}
type condMap struct {
	V map[string]int `bson:",omitempty"`
}
type namedCondStr struct {
	V string `bson:"myv,omitempty"`
}
type condTime struct {
	V time.Time `bson:",omitempty"`
}
type condStruct struct {
	V struct{ A []int } `bson:",omitempty"`
}

type shortInt struct {
	V int64 `bson:",minsize"`
}
type shortUint struct {
	V uint64 `bson:",minsize"`
}
type shortIface struct {
	V any `bson:",minsize"`
}
type shortPtr struct {
	V *int64 `bson:",minsize"`
}
type shortNonEmptyInt struct {
	V int64 `bson:",minsize,omitempty"`
}

type inlineInt struct {
	V struct{ A, B int } `bson:",inline"`
}
type inlineDupName struct {
	A int
	V struct{ A, B int } `bson:",inline"`
}
type inlineMap struct {
	A int
	M map[string]any `bson:",inline"`
}
type inlineMapInt struct {
	A int
	M map[string]int `bson:",inline"`
}
type inlineMapMyM struct {
	A int
	M MyM `bson:",inline"`
}
type inlineDupMap struct {
	M1 map[string]any `bson:",inline"`
	M2 map[string]any `bson:",inline"`
}
type inlineBadKeyMap struct {
	M map[int]int `bson:",inline"`
}
type inlineUnexported struct {
	M          map[string]any `bson:",inline"`
	unexported `bson:",inline"`
}
type MStruct struct {
	M int `bson:"m,omitempty"`
}
type InlinePtrStruct struct {
	A        int
	*MStruct `bson:",inline"`
}
type inlinePtrPtrStruct struct {
	B                int
	*InlinePtrStruct `bson:",inline"`
}
type unexported struct {
	A int
}

type getterSetterD D

func (s getterSetterD) GetBSON() (any, error) {
	if len(s) == 0 {
		return D{}, nil
	}
	return D(s[:len(s)-1]), nil
}

func (s *getterSetterD) SetBSON(raw RawValue) error {
	var doc D
	rval := reflect.ValueOf(&doc).Elem()
	decoder, err := NewMgoRegistry().LookupDecoder(rval.Type())
	if err != nil {
		return err
	}
	if raw.Type == 0x00 {
		raw.Type = TypeEmbeddedDocument
	}
	vr := newBufferedValueReader(raw.Type, raw.Value)
	err = decoder.DecodeValue(DecodeContext{Registry: NewMgoRegistry()}, vr, rval)
	if err != nil {
		return err
	}
	doc = append(doc, E{"suffix", true})
	*s = getterSetterD(doc)
	return err
}

type getterSetterInt int

func (i getterSetterInt) GetBSON() (any, error) {
	return D{{"a", int(i)}}, nil
}

func (i *getterSetterInt) SetBSON(raw RawValue) error {
	var doc struct{ A int }
	rval := reflect.ValueOf(&doc).Elem()
	decoder, err := NewMgoRegistry().LookupDecoder(rval.Type())
	if err != nil {
		return err
	}
	if raw.Type == 0x00 {
		raw.Type = TypeEmbeddedDocument
	}
	vr := newBufferedValueReader(raw.Type, raw.Value)
	err = decoder.DecodeValue(DecodeContext{Registry: NewMgoRegistry()}, vr, rval)
	if err != nil {
		return err
	}
	*i = getterSetterInt(doc.A)
	return err
}

type ifaceType interface {
	Hello()
}

type ifaceSlice []ifaceType

func (s *ifaceSlice) SetBSON(raw RawValue) error {
	var ns []int
	rval := reflect.ValueOf(&ns).Elem()
	decoder, err := NewMgoRegistry().LookupDecoder(rval.Type())
	if err != nil {
		return err
	}
	vr := newBufferedValueReader(raw.Type, raw.Value)
	err = decoder.DecodeValue(DecodeContext{Registry: NewMgoRegistry()}, vr, rval)
	if err != nil {
		return err
	}
	*s = make(ifaceSlice, ns[0])
	return nil
}

func (s ifaceSlice) GetBSON() (any, error) {
	return []int{len(s)}, nil
}

type (
	MyString string
	MyBytes  []byte
	MyBool   bool
	MyD      D
	MyRawD   Raw
	MyM      map[string]any
)

var (
	truevar  = true
	falsevar = false

	int64var = int64(42)
	int64ptr = &int64var
	intvar   = int(42)

	gsintvar = getterSetterInt(42)
)

func parseURL(s string) *url.URL {
	u, err := url.Parse(s)
	if err != nil {
		panic(err)
	}
	return u
}

// That's a pretty fun test.  It will dump the first item, generate a zero
// value equivalent to the second one, load the dumped data onto it, and then
// verify that the resulting value is deep-equal to the untouched second value.
// Then, it will do the same in the *opposite* direction!
var twoWayCrossItems = []crossTypeItem{
	// int<=>int
	{&struct{ I int }{42}, &struct{ I int8 }{42}},
	{&struct{ I int }{42}, &struct{ I int32 }{42}},
	{&struct{ I int }{42}, &struct{ I int64 }{42}},
	{&struct{ I int8 }{42}, &struct{ I int32 }{42}},
	{&struct{ I int8 }{42}, &struct{ I int64 }{42}},
	{&struct{ I int32 }{42}, &struct{ I int64 }{42}},

	// uint<=>uint
	{&struct{ I uint }{42}, &struct{ I uint8 }{42}},
	{&struct{ I uint }{42}, &struct{ I uint32 }{42}},
	{&struct{ I uint }{42}, &struct{ I uint64 }{42}},
	{&struct{ I uint8 }{42}, &struct{ I uint32 }{42}},
	{&struct{ I uint8 }{42}, &struct{ I uint64 }{42}},
	{&struct{ I uint32 }{42}, &struct{ I uint64 }{42}},

	// float32<=>float64
	{&struct{ I float32 }{42}, &struct{ I float64 }{42}},

	// int<=>uint
	{&struct{ I uint }{42}, &struct{ I int }{42}},
	{&struct{ I uint }{42}, &struct{ I int8 }{42}},
	{&struct{ I uint }{42}, &struct{ I int32 }{42}},
	{&struct{ I uint }{42}, &struct{ I int64 }{42}},
	{&struct{ I uint8 }{42}, &struct{ I int }{42}},
	{&struct{ I uint8 }{42}, &struct{ I int8 }{42}},
	{&struct{ I uint8 }{42}, &struct{ I int32 }{42}},
	{&struct{ I uint8 }{42}, &struct{ I int64 }{42}},
	{&struct{ I uint32 }{42}, &struct{ I int }{42}},
	{&struct{ I uint32 }{42}, &struct{ I int8 }{42}},
	{&struct{ I uint32 }{42}, &struct{ I int32 }{42}},
	{&struct{ I uint32 }{42}, &struct{ I int64 }{42}},
	{&struct{ I uint64 }{42}, &struct{ I int }{42}},
	{&struct{ I uint64 }{42}, &struct{ I int8 }{42}},
	{&struct{ I uint64 }{42}, &struct{ I int32 }{42}},
	{&struct{ I uint64 }{42}, &struct{ I int64 }{42}},

	// int <=> float
	{&struct{ I int }{42}, &struct{ I float64 }{42}},

	// int <=> bool
	{&struct{ I int }{1}, &struct{ I bool }{true}},
	{&struct{ I int }{0}, &struct{ I bool }{false}},

	// uint <=> float64
	{&struct{ I uint }{42}, &struct{ I float64 }{42}},

	// uint <=> bool
	{&struct{ I uint }{1}, &struct{ I bool }{true}},
	{&struct{ I uint }{0}, &struct{ I bool }{false}},

	// float64 <=> bool
	{&struct{ I float64 }{1}, &struct{ I bool }{true}},
	{&struct{ I float64 }{0}, &struct{ I bool }{false}},

	// string <=> string and string <=> []byte
	{&struct{ S []byte }{[]byte("abc")}, &struct{ S string }{"abc"}},
	{&struct{ S []byte }{[]byte("def")}, &struct{ S Symbol }{"def"}},
	{&struct{ S string }{"ghi"}, &struct{ S Symbol }{"ghi"}},

	{
		&struct{ S string }{"0123456789ab"},
		&struct{ S ObjectID }{ObjectID{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62}},
	},

	// map <=> struct
	{
		&struct {
			A struct {
				B, C int
			}
		}{struct{ B, C int }{1, 2}},
		map[string]map[string]int{"a": {"b": 1, "c": 2}},
	},

	{&struct{ A Symbol }{"abc"}, map[string]string{"a": "abc"}},
	{&struct{ A Symbol }{"abc"}, map[string][]byte{"a": []byte("abc")}},
	{&struct{ A []byte }{[]byte("abc")}, map[string]string{"a": "abc"}},
	{&struct{ A uint }{42}, map[string]int{"a": 42}},
	{&struct{ A uint }{42}, map[string]float64{"a": 42}},
	{&struct{ A uint }{1}, map[string]bool{"a": true}},
	{&struct{ A int }{42}, map[string]uint{"a": 42}},
	{&struct{ A int }{42}, map[string]float64{"a": 42}},
	{&struct{ A int }{1}, map[string]bool{"a": true}},
	{&struct{ A float64 }{42}, map[string]float32{"a": 42}},
	{&struct{ A float64 }{42}, map[string]int{"a": 42}},
	{&struct{ A float64 }{42}, map[string]uint{"a": 42}},
	{&struct{ A float64 }{1}, map[string]bool{"a": true}},
	{&struct{ A bool }{true}, map[string]int{"a": 1}},
	{&struct{ A bool }{true}, map[string]uint{"a": 1}},
	{&struct{ A bool }{true}, map[string]float64{"a": 1}},
	{&struct{ A **byte }{&byteptr}, map[string]byte{"a": 8}},

	// url.URL <=> string
	{&struct{ URL *url.URL }{parseURL("h://e.c/p")}, map[string]string{"url": "h://e.c/p"}},
	{&struct{ URL url.URL }{*parseURL("h://e.c/p")}, map[string]string{"url": "h://e.c/p"}},

	// Slices
	{&struct{ S []int }{[]int{1, 2, 3}}, map[string][]int{"s": {1, 2, 3}}},
	{&struct{ S *[]int }{&[]int{1, 2, 3}}, map[string][]int{"s": {1, 2, 3}}},

	// Conditionals
	{&condBool{true}, map[string]bool{"v": true}},
	{&condBool{}, map[string]bool{}},
	{&condInt{1}, map[string]int{"v": 1}},
	{&condInt{}, map[string]int{}},
	{&condUInt{1}, map[string]uint{"v": 1}},
	{&condUInt{}, map[string]uint{}},
	{&condFloat{}, map[string]int{}},
	{&condStr{"yo"}, map[string]string{"v": "yo"}},
	{&condStr{}, map[string]string{}},
	{&condStrNS{"yo"}, map[string]string{"v": "yo"}},
	{&condStrNS{}, map[string]string{}},
	{&condSlice{[]string{"yo"}}, map[string][]string{"v": {"yo"}}},
	{&condSlice{}, map[string][]string{}},
	{&condMap{map[string]int{"k": 1}}, M{"v": M{"k": 1}}},
	{&condMap{}, map[string][]string{}},
	{&condIface{"yo"}, map[string]string{"v": "yo"}},
	{&condIface{""}, map[string]string{"v": ""}},
	{&condIface{}, map[string]string{}},
	{&condPtr{&truevar}, map[string]bool{"v": true}},
	{&condPtr{&falsevar}, map[string]bool{"v": false}},
	{&condPtr{}, map[string]string{}},

	{&condTime{time.Unix(123456789, 123e6).UTC()}, map[string]time.Time{"v": time.Unix(123456789, 123e6).UTC()}},
	{&condTime{}, map[string]string{}},

	{&condStruct{struct{ A []int }{[]int{1}}}, M{"v": M{"a": []any{1}}}},
	{&condStruct{struct{ A []int }{}}, M{}},

	{&namedCondStr{"yo"}, map[string]string{"myv": "yo"}},
	{&namedCondStr{}, map[string]string{}},

	{&shortInt{1}, map[string]any{"v": 1}},
	{&shortInt{1 << 30}, map[string]any{"v": 1 << 30}},
	{&shortInt{1 << 31}, map[string]any{"v": int64(1 << 31)}},
	{&shortUint{1 << 30}, map[string]any{"v": 1 << 30}},
	{&shortUint{1 << 31}, map[string]any{"v": int64(1 << 31)}},
	{&shortIface{int64(1) << 31}, map[string]any{"v": int64(1 << 31)}},
	{&shortPtr{int64ptr}, map[string]any{"v": intvar}},

	{&shortNonEmptyInt{1}, map[string]any{"v": 1}},
	{&shortNonEmptyInt{1 << 31}, map[string]any{"v": int64(1 << 31)}},
	{&shortNonEmptyInt{}, map[string]any{}},

	{&inlineInt{struct{ A, B int }{1, 2}}, map[string]any{"a": 1, "b": 2}},
	{&inlineMap{A: 1, M: map[string]any{"b": 2}}, map[string]any{"a": 1, "b": 2}},
	{&inlineMap{A: 1, M: nil}, map[string]any{"a": 1}},
	{&inlineMapInt{A: 1, M: map[string]int{"b": 2}}, map[string]int{"a": 1, "b": 2}},
	{&inlineMapInt{A: 1, M: nil}, map[string]int{"a": 1}},
	{&inlineUnexported{M: map[string]any{"b": 1}, unexported: unexported{A: 2}}, map[string]any{"b": 1, "a": 2}},

	// []byte <=> Binary
	{&struct{ B []byte }{[]byte("abc")}, map[string]Binary{"b": {Data: []byte("abc")}}},

	// []byte <=> MyBytes
	{&struct{ B MyBytes }{[]byte("abc")}, &map[string]string{"b": "abc"}},
	{&struct{ B MyBytes }{[]byte{}}, &map[string]string{"b": ""}},
	// {&struct{ B MyBytes }{}, &map[string]bool{}},
	{&struct{ B []byte }{[]byte("abc")}, &map[string]MyBytes{"b": []byte("abc")}},

	// bool <=> MyBool
	{&struct{ B MyBool }{true}, map[string]bool{"b": true}},
	{&struct{ B MyBool }{}, map[string]bool{"b": false}},
	// {&struct{ B MyBool }{}, map[string]string{}},
	{&struct{ B bool }{}, map[string]MyBool{"b": false}},

	// arrays
	{&struct{ V [2]int }{[...]int{1, 2}}, map[string][2]int{"v": {1, 2}}},
	{&struct{ V [2]byte }{[...]byte{1, 2}}, map[string][2]byte{"v": {1, 2}}},

	// zero time
	{&struct{ V time.Time }{}, map[string]any{"v": time.Time{}}},

	// zero time + 1 second + 1 millisecond; overflows int64 as nanoseconds
	{
		&struct{ V time.Time }{time.Unix(-62135596799, 1e6).UTC()},
		map[string]any{"v": time.Unix(-62135596799, 1e6).UTC()},
	},

	// json.Number <=> int64, float64
	{&struct{ N json.Number }{"5"}, map[string]any{"n": int64(5)}},
	{&struct{ N json.Number }{"5.05"}, map[string]any{"n": 5.05}},
	{&struct{ N json.Number }{"9223372036854776000"}, map[string]any{"n": float64(1 << 63)}},

	// D <=> non-struct getter/setter
	{&D{{"a", 1}}, &getterSetterD{{"a", 1}, {"suffix", true}}},
	{&D{{"a", 42}}, &gsintvar},

	// Interface slice setter.
	{&struct{ V ifaceSlice }{ifaceSlice{nil, nil, nil}}, M{"v": []any{3}}},
}

// Same thing, but only one way (obj1 => obj2).
var oneWayCrossItems = []crossTypeItem{
	// Would get decoded into a int32 too in the opposite direction.
	{&shortIface{int64(1) << 30}, map[string]any{"v": 1 << 30}},

	// Ensure omitempty on struct with private fields works properly.
	{&struct {
		V struct{ v time.Time } `bson:",omitempty"`
	}{}, map[string]any{}},

	{&inlineMapMyM{A: 1, M: MyM{"b": MyM{"c": 3}}}, map[string]any{"a": 1, "b": M{"c": 3}}},
	{map[string]any{"a": 1, "b": map[string]any{"c": 3}}, &inlineMapMyM{A: 1, M: MyM{"b": M{"c": 3}}}},

	{&D{{"a", D{{"b", 1}, {"c", 2}}}}, &D{{"a", M{"b": 1, "c": 2}}}},

	{&D{{"a", D{{"b", 1}, {"c", 2}}}}, &MyD{{"a", M{"b": 1, "c": 2}}}},
	{&MyD{{"a", MyD{{"b", 1}, {"c", 2}}}}, &D{{"a", M{"b": 1, "c": 2}}}},

	{&struct{ V MyD }{MyD{{"a", 1}}}, &D{{"v", M{"a": 1}}}},
	{&D{{"v", D{{"a", 1}}}}, &struct{ V MyD }{MyD{{"a", 1}}}},

	{&M{"a": M{"b": 1, "c": 2}}, MyM{"a": M{"b": 1, "c": 2}}},
	{MyM{"a": MyM{"b": 1, "c": 2}}, &M{"a": M{"b": 1, "c": 2}}},

	{map[string]any{"a": map[string]any{"b": 1, "c": 2}}, &M{"a": M{"b": 1, "c": 2}}},

	{&M{"a": M{"b": 1, "c": 2}}, map[MyString]any{"a": M{"b": 1, "c": 2}}},
	{map[MyString]any{"a": map[MyString]any{"b": 1, "c": 2}}, &M{"a": M{"b": 1, "c": 2}}},
}

func testCrossPair(t *testing.T, dump any, load any) {
	zero := makeZeroDoc(load)
	buf := new(bytes.Buffer)
	vw := NewDocumentWriter(buf)
	enc := NewEncoder(vw)
	enc.SetRegistry(NewMgoRegistry())
	err := enc.Encode(dump)
	assert.Nil(t, err, "expected nil error, got: %v", err)
	err = unmarshalWithRegistry(t, NewMgoRegistry(), buf.Bytes(), zero)
	assert.Nil(t, err, "expected nil error, got: %v", err)

	assert.True(t, reflect.DeepEqual(load, zero), "expected: %v, got: %v", load, zero)
}

func TestTwoWayCrossPairs(t *testing.T) {
	for i, item := range twoWayCrossItems {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			testCrossPair(t, item.obj1, item.obj2)
			testCrossPair(t, item.obj2, item.obj1)
		})
	}
}

func TestOneWayCrossPairs(t *testing.T) {
	for i, item := range oneWayCrossItems {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			testCrossPair(t, item.obj1, item.obj2)
		})
	}
}

// --------------------------------------------------------------------------
// ObjectId JSON marshalling.

type jsonType struct {
	ID ObjectID
}

func objectIDHex(s string) ObjectID {
	oid, _ := ObjectIDFromHex(s)
	return oid
}

var jsonIDTests = []struct {
	value     jsonType
	json      string
	marshal   bool
	unmarshal bool
	error     string
}{{
	value:     jsonType{ID: objectIDHex("4d88e15b60f486e428412dc9")},
	json:      `{"ID":"4d88e15b60f486e428412dc9"}`,
	marshal:   true,
	unmarshal: true,
}, {
	// 	value:     jsonType{},
	// 	json:      `{"Id":""}`,
	// 	marshal:   true,
	// 	unmarshal: true,
	// }, {
	// 	value:     jsonType{},
	// 	json:      `{"Id":null}`,
	// 	marshal:   false,
	// 	unmarshal: true,
	// }, {
	json:      `{"Id":"4d88e15b60f486e428412dc9A"}`,
	error:     `invalid ObjectId in JSON: "4d88e15b60f486e428412dc9A"`,
	marshal:   false,
	unmarshal: true,
}, {
	json:      `{"Id":"4d88e15b60f486e428412dcZ"}`,
	error:     `invalid ObjectId in JSON: "4d88e15b60f486e428412dcZ" .*`,
	marshal:   false,
	unmarshal: true,
}}

func TestObjectIdJSONMarshaling(t *testing.T) {
	for i, test := range jsonIDTests {
		test := test // Capture range variable.

		t.Run(strconv.Itoa(i), func(t *testing.T) {
			if test.marshal {
				data, err := json.Marshal(&test.value)
				if test.error == "" {
					assert.Nil(t, err, "expected nil error, got: %v", err)
					assert.Equal(t, test.json, string(data), "expected: %v, got: %v", test.json, string(data))
				} else {
					assert.NotNil(t, err, "expected a marshal error")
				}
			}

			if test.unmarshal {
				var value jsonType
				err := json.Unmarshal([]byte(test.json), &value)
				if test.error == "" {
					assert.Nil(t, err, "expected nil error, got: %v", err)
					assert.True(t, reflect.DeepEqual(test.value, value), "expected: %v, got: %v", test.value, value)
				} else {
					assert.NotNil(t, err, "expected a unmarshal error")
				}
			}
		})
	}
}

func TestMarshalNotRespectNil(t *testing.T) {
	type T struct {
		Slice  []int
		BSlice []byte
		Map    map[string]any
	}

	testStruct1 := T{}

	assert.Nil(t, testStruct1.Slice, "expected nil slice, got: %v", testStruct1.Slice)
	assert.Nil(t, testStruct1.BSlice, "expected nil byte slice, got: %v", testStruct1.BSlice)
	assert.Nil(t, testStruct1.Map, "expected nil map, got: %v", testStruct1.Map)

	buf := new(bytes.Buffer)
	vw := NewDocumentWriter(buf)
	enc := NewEncoder(vw)
	enc.SetRegistry(NewMgoRegistry())
	err := enc.Encode(testStruct1)
	assert.Nil(t, err, "expected nil error, got: %v", err)

	testStruct2 := T{}

	_ = unmarshalWithRegistry(t, NewMgoRegistry(), buf.Bytes(), &testStruct2)

	assert.NotNil(t, testStruct2.Slice, "expected non-nil slice")
	assert.NotNil(t, testStruct2.BSlice, "expected non-nil byte slice")
	assert.NotNil(t, testStruct2.Map, "expected non-nil map")
}

func TestMarshalRespectNil(t *testing.T) {
	type T struct {
		Slice    []int
		SlicePtr *[]int
		Ptr      *int
		Map      map[string]any
		MapPtr   *map[string]any
	}

	testStruct1 := T{}

	assert.Nil(t, testStruct1.Slice, "expected nil slice, got: %v", testStruct1.Slice)
	assert.Nil(t, testStruct1.SlicePtr, "expected nil slice ptr, got: %v", testStruct1.SlicePtr)
	assert.Nil(t, testStruct1.Map, "expected nil map, got: %v", testStruct1.Map)
	assert.Nil(t, testStruct1.MapPtr, "expected nil map ptr, got: %v", testStruct1.MapPtr)
	assert.Nil(t, testStruct1.Ptr, "expected nil ptr, got: %v", testStruct1.Ptr)

	buf := new(bytes.Buffer)
	vw := NewDocumentWriter(buf)
	enc := NewEncoder(vw)
	enc.SetRegistry(NewMgoRegistry())
	err := enc.Encode(testStruct1)
	assert.Nil(t, err, "expected nil error, got: %v", err)

	testStruct2 := T{}

	_ = unmarshalWithRegistry(t, NewRespectNilValuesMgoRegistry(), buf.Bytes(), &testStruct2)

	assert.Len(t, testStruct2.Slice, 0, "expected empty slice, got: %v", testStruct2.Slice)
	assert.Nil(t, testStruct2.SlicePtr, "expected nil slice ptr, got: %v", testStruct2.SlicePtr)
	assert.Len(t, testStruct2.Map, 0, "expected empty map, got: %v", testStruct2.Map)
	assert.Nil(t, testStruct2.MapPtr, "expected nil map ptr, got: %v", testStruct2.MapPtr)
	assert.Nil(t, testStruct2.Ptr, "expected nil ptr, got: %v", testStruct2.Ptr)

	testStruct1 = T{
		Slice:    []int{},
		SlicePtr: &[]int{},
		Map:      map[string]any{},
		MapPtr:   &map[string]any{},
	}

	assert.NotNil(t, testStruct1.Slice, "expected non-nil slice")
	assert.NotNil(t, testStruct1.SlicePtr, "expected non-nil slice ptr")
	assert.NotNil(t, testStruct1.Map, "expected non-nil map")
	assert.NotNil(t, testStruct1.MapPtr, "expected non-nil map ptr")

	buf.Reset()
	vw = NewDocumentWriter(buf)
	enc = NewEncoder(vw)
	enc.SetRegistry(NewMgoRegistry())
	err = enc.Encode(testStruct1)
	assert.Nil(t, err, "expected nil error, got: %v", err)

	testStruct2 = T{}

	_ = unmarshalWithRegistry(t, NewRespectNilValuesMgoRegistry(), buf.Bytes(), &testStruct2)

	assert.NotNil(t, testStruct2.Slice, "expected non-nil slice")
	assert.NotNil(t, testStruct2.SlicePtr, "expected non-nil slice ptr")
	assert.NotNil(t, testStruct2.Map, "expected non-nil map")
	assert.NotNil(t, testStruct2.MapPtr, "expected non-nil map ptr")
}

// Our mgocompat.Registry tests
type Inner struct {
	ID string
}

type InlineLoop struct {
	Inner `bson:",inline"`
	Value string
	Draft *InlineLoop `bson:",omitempty"`
}

func TestInlineWithPointerToSelf(t *testing.T) {
	x1 := InlineLoop{
		Inner: Inner{
			ID: "1",
		},
		Value: "",
	}

	buf := new(bytes.Buffer)
	vw := NewDocumentWriter(buf)
	enc := NewEncoder(vw)
	enc.SetRegistry(NewMgoRegistry())
	err := enc.Encode(x1)
	assert.Nil(t, err, "expected nil error, got: %v", err)

	var x2 InlineLoop
	err = unmarshalWithRegistry(t, NewMgoRegistry(), buf.Bytes(), &x2)
	assert.Nil(t, err, "expected nil error, got: %v", err)
	assert.Equal(t, x1, x2, "Expected %v, got %v", x1, x2)
}
