// 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 (
	"bufio"
	"bytes"
	_ "embed"
	"errors"
	"fmt"
	"io"
	"math"
	"testing"

	"gitea.psichedelico.com/go/bson/internal/assert"
	"gitea.psichedelico.com/go/bson/x/bsonx/bsoncore"
	"github.com/google/go-cmp/cmp"
)

//go:embed testdata/lorem.txt
var lorem []byte

var testcstring = append(lorem, []byte{0x00}...)

func TestValueReader(t *testing.T) {
	t.Run("ReadBinary", func(t *testing.T) {
		testCases := []struct {
			name  string
			data  []byte
			btype byte
			b     []byte
			err   error
			vType Type
		}{
			{
				"incorrect type",
				[]byte{},
				0,
				nil,
				(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeBinary),
				TypeEmbeddedDocument,
			},
			{
				"length too short",
				[]byte{},
				0,
				nil,
				io.EOF,
				TypeBinary,
			},
			{
				"no byte available",
				[]byte{0x00, 0x00, 0x00, 0x00},
				0,
				nil,
				io.EOF,
				TypeBinary,
			},
			{
				"not enough bytes for binary",
				[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
				0,
				nil,
				io.EOF,
				TypeBinary,
			},
			{
				"success",
				[]byte{0x03, 0x00, 0x00, 0x00, 0xEA, 0x01, 0x02, 0x03},
				0xEA,
				[]byte{0x01, 0x02, 0x03},
				nil,
				TypeBinary,
			},
		}

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				vr := &valueReader{
					r: bufio.NewReader(bytes.NewReader(tc.data)),
					stack: []vrState{
						{mode: mTopLevel},
						{
							mode:  mElement,
							vType: tc.vType,
						},
					},
					frame: 1,
				}

				b, btype, err := vr.ReadBinary()
				if !errequal(t, err, tc.err) {
					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
				}
				if btype != tc.btype {
					t.Errorf("Incorrect binary type returned. got %v; want %v", btype, tc.btype)
				}
				if !bytes.Equal(b, tc.b) {
					t.Errorf("Binary data does not match. got %v; want %v", b, tc.b)
				}
			})
		}
	})
	t.Run("ReadBoolean", func(t *testing.T) {
		testCases := []struct {
			name    string
			data    []byte
			boolean bool
			err     error
			vType   Type
		}{
			{
				"incorrect type",
				[]byte{},
				false,
				(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeBoolean),
				TypeEmbeddedDocument,
			},
			{
				"no byte available",
				[]byte{},
				false,
				io.EOF,
				TypeBoolean,
			},
			{
				"invalid byte for boolean",
				[]byte{0x03},
				false,
				fmt.Errorf("invalid byte for boolean, %b", 0x03),
				TypeBoolean,
			},
			{
				"success",
				[]byte{0x01},
				true,
				nil,
				TypeBoolean,
			},
		}

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				vr := &valueReader{
					r: bufio.NewReader(bytes.NewReader(tc.data)),
					stack: []vrState{
						{mode: mTopLevel},
						{
							mode:  mElement,
							vType: tc.vType,
						},
					},
					frame: 1,
				}

				boolean, err := vr.ReadBoolean()
				if !errequal(t, err, tc.err) {
					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
				}
				if boolean != tc.boolean {
					t.Errorf("Incorrect boolean returned. got %v; want %v", boolean, tc.boolean)
				}
			})
		}
	})
	t.Run("ReadDocument", func(t *testing.T) {
		t.Run("TopLevel", func(t *testing.T) {
			doc := []byte{0x05, 0x00, 0x00, 0x00, 0x00}
			vr := &valueReader{
				stack: []vrState{{mode: mTopLevel}},
				frame: 0,
			}

			// invalid length
			vr.r = bufio.NewReader(bytes.NewReader([]byte{0x00, 0x00}))
			_, err := vr.ReadDocument()
			if !errors.Is(err, io.ErrUnexpectedEOF) {
				t.Errorf("Expected io.ErrUnexpectedEOF with document length too small. got %v; want %v", err, io.EOF)
			}
			if vr.offset != 0 {
				t.Errorf("Expected 0 offset. got %d", vr.offset)
			}

			vr.r = bufio.NewReader(bytes.NewReader(doc))
			_, err = vr.ReadDocument()
			noerr(t, err)
			if vr.stack[vr.frame].end != 5 {
				t.Errorf("Incorrect end for document. got %d; want %d", vr.stack[vr.frame].end, 5)
			}
		})
		t.Run("EmbeddedDocument", func(t *testing.T) {
			vr := &valueReader{
				stack: []vrState{
					{mode: mTopLevel},
					{mode: mElement, vType: TypeBoolean},
				},
				frame: 1,
			}

			var wanterr = (&valueReader{stack: []vrState{{mode: mElement, vType: TypeBoolean}}}).typeError(TypeEmbeddedDocument)
			_, err := vr.ReadDocument()
			if err == nil || err.Error() != wanterr.Error() {
				t.Errorf("Incorrect returned error. got %v; want %v", err, wanterr)
			}

			vr.stack[1].mode = mArray
			wanterr = vr.invalidTransitionErr(mDocument, "ReadDocument", []mode{mTopLevel, mElement, mValue})
			_, err = vr.ReadDocument()
			if err == nil || err.Error() != wanterr.Error() {
				t.Errorf("Incorrect returned error. got %v; want %v", err, wanterr)
			}

			vr.stack[1].mode, vr.stack[1].vType = mElement, TypeEmbeddedDocument
			vr.offset = 4
			vr.r = bufio.NewReader(bytes.NewReader([]byte{0x05, 0x00, 0x00, 0x00, 0x00, 0x00}))
			_, err = vr.ReadDocument()
			noerr(t, err)
			if len(vr.stack) != 3 {
				t.Errorf("Incorrect number of stack frames. got %d; want %d", len(vr.stack), 3)
			}
			if vr.stack[2].mode != mDocument {
				t.Errorf("Incorrect mode set. got %v; want %v", vr.stack[2].mode, mDocument)
			}
			if vr.stack[2].end != 9 {
				t.Errorf("End of embedded document is not correct. got %d; want %d", vr.stack[2].end, 9)
			}
			if vr.offset != 8 {
				t.Errorf("Offset not incremented correctly. got %d; want %d", vr.offset, 8)
			}

			vr.frame--
			_, err = vr.ReadDocument()
			if !errors.Is(err, io.ErrUnexpectedEOF) {
				t.Errorf("Should return error when attempting to read length with not enough bytes. got %v; want %v", err, io.EOF)
			}
		})
	})
	t.Run("ReadCodeWithScope", func(t *testing.T) {
		codeWithScope := []byte{
			0x11, 0x00, 0x00, 0x00, // total length
			0x4, 0x00, 0x00, 0x00, // string length
			'f', 'o', 'o', 0x00, // string
			0x05, 0x00, 0x00, 0x00, 0x00, // document
		}
		mismatchCodeWithScope := []byte{
			0x11, 0x00, 0x00, 0x00, // total length
			0x4, 0x00, 0x00, 0x00, // string length
			'f', 'o', 'o', 0x00, // string
			0x07, 0x00, 0x00, 0x00, // document
			0x0A, 0x00, // null element, empty key
			0x00, // document end
		}
		invalidCodeWithScope := []byte{
			0x7, 0x00, 0x00, 0x00, // total length
			0x0, 0x00, 0x00, 0x00, // string length = 0
			0x05, 0x00, 0x00, 0x00, 0x00, // document
		}
		testCases := []struct {
			name  string
			data  []byte
			err   error
			vType Type
		}{
			{
				"incorrect type",
				[]byte{},
				(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeCodeWithScope),
				TypeEmbeddedDocument,
			},
			{
				"total length not enough bytes",
				[]byte{},
				io.EOF,
				TypeCodeWithScope,
			},
			{
				"string length not enough bytes",
				codeWithScope[:4],
				io.EOF,
				TypeCodeWithScope,
			},
			{
				"not enough string bytes",
				codeWithScope[:8],
				io.EOF,
				TypeCodeWithScope,
			},
			{
				"document length not enough bytes",
				codeWithScope[:12],
				io.EOF,
				TypeCodeWithScope,
			},
			{
				"length mismatch",
				mismatchCodeWithScope,
				fmt.Errorf("length of CodeWithScope does not match lengths of components; total: %d; components: %d", 17, 19),
				TypeCodeWithScope,
			},
			{
				"invalid strLength",
				invalidCodeWithScope,
				fmt.Errorf("invalid string length: %d", 0),
				TypeCodeWithScope,
			},
		}

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				vr := &valueReader{
					r: bufio.NewReader(bytes.NewReader(tc.data)),
					stack: []vrState{
						{mode: mTopLevel},
						{
							mode:  mElement,
							vType: tc.vType,
						},
					},
					frame: 1,
				}

				_, _, err := vr.ReadCodeWithScope()
				if !errequal(t, err, tc.err) {
					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
				}
			})
		}

		t.Run("success", func(t *testing.T) {
			vr := &valueReader{
				offset: 4,
				r:      bufio.NewReader(bytes.NewReader(codeWithScope)),
				stack: []vrState{
					{mode: mTopLevel},
					{mode: mElement, vType: TypeCodeWithScope},
				},
				frame: 1,
			}

			code, _, err := vr.ReadCodeWithScope()
			noerr(t, err)
			if code != "foo" {
				t.Errorf("Code does not match. got %s; want %s", code, "foo")
			}
			if len(vr.stack) != 3 {
				t.Errorf("Incorrect number of stack frames. got %d; want %d", len(vr.stack), 3)
			}
			if vr.stack[2].mode != mCodeWithScope {
				t.Errorf("Incorrect mode set. got %v; want %v", vr.stack[2].mode, mDocument)
			}
			if vr.stack[2].end != 21 {
				t.Errorf("End of scope is not correct. got %d; want %d", vr.stack[2].end, 21)
			}
			if vr.offset != 20 {
				t.Errorf("Offset not incremented correctly. got %d; want %d", vr.offset, 20)
			}
		})
	})
	t.Run("ReadDBPointer", func(t *testing.T) {
		testCases := []struct {
			name  string
			data  []byte
			ns    string
			oid   ObjectID
			err   error
			vType Type
		}{
			{
				"incorrect type",
				[]byte{},
				"",
				ObjectID{},
				(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeDBPointer),
				TypeEmbeddedDocument,
			},
			{
				"length too short",
				[]byte{},
				"",
				ObjectID{},
				io.EOF,
				TypeDBPointer,
			},
			{
				"not enough bytes for namespace",
				[]byte{0x04, 0x00, 0x00, 0x00},
				"",
				ObjectID{},
				io.EOF,
				TypeDBPointer,
			},
			{
				"not enough bytes for objectID",
				[]byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00},
				"",
				ObjectID{},
				io.EOF,
				TypeDBPointer,
			},
			{
				"success",
				[]byte{
					0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00,
					0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
				},
				"foo",
				ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
				nil,
				TypeDBPointer,
			},
		}

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				vr := &valueReader{
					r: bufio.NewReader(bytes.NewReader(tc.data)),
					stack: []vrState{
						{mode: mTopLevel},
						{
							mode:  mElement,
							vType: tc.vType,
						},
					},
					frame: 1,
				}

				ns, oid, err := vr.ReadDBPointer()
				if !errequal(t, err, tc.err) {
					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
				}
				if ns != tc.ns {
					t.Errorf("Incorrect namespace returned. got %v; want %v", ns, tc.ns)
				}
				if oid != tc.oid {
					t.Errorf("ObjectIDs did not match. got %v; want %v", oid, tc.oid)
				}
			})
		}
	})
	t.Run("ReadDateTime", func(t *testing.T) {
		testCases := []struct {
			name  string
			data  []byte
			dt    int64
			err   error
			vType Type
		}{
			{
				"incorrect type",
				[]byte{},
				0,
				(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeDateTime),
				TypeEmbeddedDocument,
			},
			{
				"length too short",
				[]byte{},
				0,
				io.EOF,
				TypeDateTime,
			},
			{
				"success",
				[]byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
				255,
				nil,
				TypeDateTime,
			},
		}

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				vr := &valueReader{
					r: bufio.NewReader(bytes.NewReader(tc.data)),
					stack: []vrState{
						{mode: mTopLevel},
						{
							mode:  mElement,
							vType: tc.vType,
						},
					},
					frame: 1,
				}

				dt, err := vr.ReadDateTime()
				if !errequal(t, err, tc.err) {
					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
				}
				if dt != tc.dt {
					t.Errorf("Incorrect datetime returned. got %d; want %d", dt, tc.dt)
				}
			})
		}
	})
	t.Run("ReadDecimal128", func(t *testing.T) {
		testCases := []struct {
			name  string
			data  []byte
			dc128 Decimal128
			err   error
			vType Type
		}{
			{
				"incorrect type",
				[]byte{},
				Decimal128{},
				(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeDecimal128),
				TypeEmbeddedDocument,
			},
			{
				"length too short",
				[]byte{},
				Decimal128{},
				io.EOF,
				TypeDecimal128,
			},
			{
				"success",
				[]byte{
					0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Low
					0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // High
				},
				NewDecimal128(65280, 255),
				nil,
				TypeDecimal128,
			},
		}

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				vr := &valueReader{
					r: bufio.NewReader(bytes.NewReader(tc.data)),
					stack: []vrState{
						{mode: mTopLevel},
						{
							mode:  mElement,
							vType: tc.vType,
						},
					},
					frame: 1,
				}

				dc128, err := vr.ReadDecimal128()
				if !errequal(t, err, tc.err) {
					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
				}
				gotHigh, gotLow := dc128.GetBytes()
				wantHigh, wantLow := tc.dc128.GetBytes()
				if gotHigh != wantHigh {
					t.Errorf("Retuired high byte does not match. got %d; want %d", gotHigh, wantHigh)
				}
				if gotLow != wantLow {
					t.Errorf("Returned low byte does not match. got %d; want %d", gotLow, wantLow)
				}
			})
		}
	})
	t.Run("ReadDouble", func(t *testing.T) {
		testCases := []struct {
			name   string
			data   []byte
			double float64
			err    error
			vType  Type
		}{
			{
				"incorrect type",
				[]byte{},
				0,
				(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeDouble),
				TypeEmbeddedDocument,
			},
			{
				"length too short",
				[]byte{},
				0,
				io.EOF,
				TypeDouble,
			},
			{
				"success",
				[]byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
				math.Float64frombits(255),
				nil,
				TypeDouble,
			},
		}

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				vr := &valueReader{
					r: bufio.NewReader(bytes.NewReader(tc.data)),
					stack: []vrState{
						{mode: mTopLevel},
						{
							mode:  mElement,
							vType: tc.vType,
						},
					},
					frame: 1,
				}

				double, err := vr.ReadDouble()
				if !errequal(t, err, tc.err) {
					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
				}
				if double != tc.double {
					t.Errorf("Incorrect double returned. got %f; want %f", double, tc.double)
				}
			})
		}
	})
	t.Run("ReadInt32", func(t *testing.T) {
		testCases := []struct {
			name  string
			data  []byte
			i32   int32
			err   error
			vType Type
		}{
			{
				"incorrect type",
				[]byte{},
				0,
				(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeInt32),
				TypeEmbeddedDocument,
			},
			{
				"length too short",
				[]byte{},
				0,
				io.EOF,
				TypeInt32,
			},
			{
				"success",
				[]byte{0xFF, 0x00, 0x00, 0x00},
				255,
				nil,
				TypeInt32,
			},
		}

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				vr := &valueReader{
					r: bufio.NewReader(bytes.NewReader(tc.data)),
					stack: []vrState{
						{mode: mTopLevel},
						{
							mode:  mElement,
							vType: tc.vType,
						},
					},
					frame: 1,
				}

				i32, err := vr.ReadInt32()
				if !errequal(t, err, tc.err) {
					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
				}
				if i32 != tc.i32 {
					t.Errorf("Incorrect int32 returned. got %d; want %d", i32, tc.i32)
				}
			})
		}
	})
	t.Run("ReadInt64", func(t *testing.T) {
		testCases := []struct {
			name  string
			data  []byte
			i64   int64
			err   error
			vType Type
		}{
			{
				"incorrect type",
				[]byte{},
				0,
				(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeInt64),
				TypeEmbeddedDocument,
			},
			{
				"length too short",
				[]byte{},
				0,
				io.EOF,
				TypeInt64,
			},
			{
				"success",
				[]byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
				255,
				nil,
				TypeInt64,
			},
		}

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				vr := &valueReader{
					r: bufio.NewReader(bytes.NewReader(tc.data)),
					stack: []vrState{
						{mode: mTopLevel},
						{
							mode:  mElement,
							vType: tc.vType,
						},
					},
					frame: 1,
				}

				i64, err := vr.ReadInt64()
				if !errequal(t, err, tc.err) {
					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
				}
				if i64 != tc.i64 {
					t.Errorf("Incorrect int64 returned. got %d; want %d", i64, tc.i64)
				}
			})
		}
	})
	t.Run("ReadJavascript/ReadString/ReadSymbol", func(t *testing.T) {
		testCases := []struct {
			name  string
			data  []byte
			fn    func(*valueReader) (string, error)
			css   string // code, string, symbol :P
			err   error
			vType Type
		}{
			{
				"ReadJavascript/incorrect type",
				[]byte{},
				(*valueReader).ReadJavascript,
				"",
				(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeJavaScript),
				TypeEmbeddedDocument,
			},
			{
				"ReadString/incorrect type",
				[]byte{},
				(*valueReader).ReadString,
				"",
				(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeString),
				TypeEmbeddedDocument,
			},
			{
				"ReadSymbol/incorrect type",
				[]byte{},
				(*valueReader).ReadSymbol,
				"",
				(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeSymbol),
				TypeEmbeddedDocument,
			},
			{
				"ReadJavascript/length too short",
				[]byte{},
				(*valueReader).ReadJavascript,
				"",
				io.EOF,
				TypeJavaScript,
			},
			{
				"ReadString/length too short",
				[]byte{},
				(*valueReader).ReadString,
				"",
				io.EOF,
				TypeString,
			},
			{
				"ReadString/long - length too short",
				append([]byte{0x40, 0x27, 0x00, 0x00}, testcstring...),
				(*valueReader).ReadString,
				"",
				io.ErrUnexpectedEOF,
				TypeString,
			},
			{
				"ReadSymbol/length too short",
				[]byte{},
				(*valueReader).ReadSymbol,
				"",
				io.EOF,
				TypeSymbol,
			},
			{
				"ReadJavascript/incorrect end byte",
				[]byte{0x01, 0x00, 0x00, 0x00, 0x05},
				(*valueReader).ReadJavascript,
				"",
				fmt.Errorf("string does not end with null byte, but with %v", 0x05),
				TypeJavaScript,
			},
			{
				"ReadString/incorrect end byte",
				[]byte{0x01, 0x00, 0x00, 0x00, 0x05},
				(*valueReader).ReadString,
				"",
				fmt.Errorf("string does not end with null byte, but with %v", 0x05),
				TypeString,
			},
			{
				"ReadString/long - incorrect end byte",
				append([]byte{0x35, 0x27, 0x00, 0x00}, testcstring[:len(testcstring)-1]...),
				(*valueReader).ReadString,
				"",
				fmt.Errorf("string does not end with null byte, but with %v", 0x20),
				TypeString,
			},
			{
				"ReadSymbol/incorrect end byte",
				[]byte{0x01, 0x00, 0x00, 0x00, 0x05},
				(*valueReader).ReadSymbol,
				"",
				fmt.Errorf("string does not end with null byte, but with %v", 0x05),
				TypeSymbol,
			},
			{
				"ReadJavascript/success",
				[]byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00},
				(*valueReader).ReadJavascript,
				"foo",
				nil,
				TypeJavaScript,
			},
			{
				"ReadString/success",
				[]byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00},
				(*valueReader).ReadString,
				"foo",
				nil,
				TypeString,
			},
			{
				"ReadString/long - success",
				append([]byte{0x36, 0x27, 0x00, 0x00}, testcstring...),
				(*valueReader).ReadString,
				string(testcstring[:len(testcstring)-1]),
				nil,
				TypeString,
			},
			{
				"ReadSymbol/success",
				[]byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00},
				(*valueReader).ReadSymbol,
				"foo",
				nil,
				TypeSymbol,
			},
		}

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				vr := &valueReader{
					r: bufio.NewReader(bytes.NewReader(tc.data)),
					stack: []vrState{
						{mode: mTopLevel},
						{
							mode:  mElement,
							vType: tc.vType,
						},
					},
					frame: 1,
				}

				css, err := tc.fn(vr)
				if !errequal(t, err, tc.err) {
					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
				}
				if css != tc.css {
					t.Errorf("Incorrect (JavaScript,String,Symbol) returned. got %s; want %s", css, tc.css)
				}
			})
		}
	})
	t.Run("ReadMaxKey/ReadMinKey/ReadNull/ReadUndefined", func(t *testing.T) {
		testCases := []struct {
			name  string
			fn    func(*valueReader) error
			err   error
			vType Type
		}{
			{
				"ReadMaxKey/incorrect type",
				(*valueReader).ReadMaxKey,
				(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeMaxKey),
				TypeEmbeddedDocument,
			},
			{
				"ReadMaxKey/success",
				(*valueReader).ReadMaxKey,
				nil,
				TypeMaxKey,
			},
			{
				"ReadMinKey/incorrect type",
				(*valueReader).ReadMinKey,
				(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeMinKey),
				TypeEmbeddedDocument,
			},
			{
				"ReadMinKey/success",
				(*valueReader).ReadMinKey,
				nil,
				TypeMinKey,
			},
			{
				"ReadNull/incorrect type",
				(*valueReader).ReadNull,
				(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeNull),
				TypeEmbeddedDocument,
			},
			{
				"ReadNull/success",
				(*valueReader).ReadNull,
				nil,
				TypeNull,
			},
			{
				"ReadUndefined/incorrect type",
				(*valueReader).ReadUndefined,
				(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeUndefined),
				TypeEmbeddedDocument,
			},
			{
				"ReadUndefined/success",
				(*valueReader).ReadUndefined,
				nil,
				TypeUndefined,
			},
		}

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				vr := &valueReader{
					stack: []vrState{
						{mode: mTopLevel},
						{
							mode:  mElement,
							vType: tc.vType,
						},
					},
					frame: 1,
				}

				err := tc.fn(vr)
				if !errequal(t, err, tc.err) {
					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
				}
			})
		}
	})
	t.Run("ReadObjectID", func(t *testing.T) {
		testCases := []struct {
			name  string
			data  []byte
			oid   ObjectID
			err   error
			vType Type
		}{
			{
				"incorrect type",
				[]byte{},
				ObjectID{},
				(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeObjectID),
				TypeEmbeddedDocument,
			},
			{
				"not enough bytes for objectID",
				[]byte{},
				ObjectID{},
				io.EOF,
				TypeObjectID,
			},
			{
				"success",
				[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
				ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
				nil,
				TypeObjectID,
			},
		}

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				vr := &valueReader{
					r: bufio.NewReader(bytes.NewReader(tc.data)),
					stack: []vrState{
						{mode: mTopLevel},
						{
							mode:  mElement,
							vType: tc.vType,
						},
					},
					frame: 1,
				}

				oid, err := vr.ReadObjectID()
				if !errequal(t, err, tc.err) {
					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
				}
				if oid != tc.oid {
					t.Errorf("ObjectIDs did not match. got %v; want %v", oid, tc.oid)
				}
			})
		}
	})
	t.Run("ReadRegex", func(t *testing.T) {
		testCases := []struct {
			name    string
			data    []byte
			pattern string
			options string
			err     error
			vType   Type
		}{
			{
				"incorrect type",
				[]byte{},
				"",
				"",
				(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeRegex),
				TypeEmbeddedDocument,
			},
			{
				"length too short",
				[]byte{},
				"",
				"",
				io.EOF,
				TypeRegex,
			},
			{
				"not enough bytes for options",
				[]byte{'f', 'o', 'o', 0x00},
				"",
				"",
				io.EOF,
				TypeRegex,
			},
			{
				"success",
				[]byte{'f', 'o', 'o', 0x00, 'b', 'a', 'r', 0x00},
				"foo",
				"bar",
				nil,
				TypeRegex,
			},
		}

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				vr := &valueReader{
					r: bufio.NewReader(bytes.NewReader(tc.data)),
					stack: []vrState{
						{mode: mTopLevel},
						{
							mode:  mElement,
							vType: tc.vType,
						},
					},
					frame: 1,
				}

				pattern, options, err := vr.ReadRegex()
				if !errequal(t, err, tc.err) {
					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
				}
				if pattern != tc.pattern {
					t.Errorf("Incorrect pattern returned. got %s; want %s", pattern, tc.pattern)
				}
				if options != tc.options {
					t.Errorf("Incorrect options returned. got %s; want %s", options, tc.options)
				}
			})
		}
	})
	t.Run("ReadTimestamp", func(t *testing.T) {
		testCases := []struct {
			name  string
			data  []byte
			ts    uint32
			incr  uint32
			err   error
			vType Type
		}{
			{
				"incorrect type",
				[]byte{},
				0,
				0,
				(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeTimestamp),
				TypeEmbeddedDocument,
			},
			{
				"not enough bytes for increment",
				[]byte{},
				0,
				0,
				io.EOF,
				TypeTimestamp,
			},
			{
				"not enough bytes for timestamp",
				[]byte{0x01, 0x02, 0x03, 0x04},
				0,
				0,
				io.EOF,
				TypeTimestamp,
			},
			{
				"success",
				[]byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00},
				256,
				255,
				nil,
				TypeTimestamp,
			},
		}

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				vr := &valueReader{
					r: bufio.NewReader(bytes.NewReader(tc.data)),
					stack: []vrState{
						{mode: mTopLevel},
						{
							mode:  mElement,
							vType: tc.vType,
						},
					},
					frame: 1,
				}

				ts, incr, err := vr.ReadTimestamp()
				if !errequal(t, err, tc.err) {
					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
				}
				if ts != tc.ts {
					t.Errorf("Incorrect timestamp returned. got %d; want %d", ts, tc.ts)
				}
				if incr != tc.incr {
					t.Errorf("Incorrect increment returned. got %d; want %d", incr, tc.incr)
				}
			})
		}
	})

	t.Run("ReadBytes & Skip", func(t *testing.T) {
		index, docb := bsoncore.ReserveLength(nil)
		docb = bsoncore.AppendNullElement(docb, "foobar")
		docb = append(docb, 0x00)
		docb = bsoncore.UpdateLength(docb, index, int32(len(docb)))
		cwsbytes := bsoncore.AppendCodeWithScope(nil, "var hellow = world;", docb)
		strbytes := []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}
		testCases := []struct {
			name           string
			t              Type
			data           []byte
			err            error
			offset         int64
			startingOffset int64
		}{
			{
				"Array/invalid length",
				TypeArray,
				[]byte{0x01, 0x02, 0x03},
				io.EOF, 0, 0,
			},
			{
				"Array/not enough bytes",
				TypeArray,
				[]byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03},
				io.EOF, 0, 0,
			},
			{
				"Array/success",
				TypeArray,
				[]byte{0x08, 0x00, 0x00, 0x00, 0x0A, '1', 0x00, 0x00},
				nil, 8, 0,
			},
			{
				"EmbeddedDocument/invalid length",
				TypeEmbeddedDocument,
				[]byte{0x01, 0x02, 0x03},
				io.EOF, 0, 0,
			},
			{
				"EmbeddedDocument/not enough bytes",
				TypeEmbeddedDocument,
				[]byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03},
				io.EOF, 0, 0,
			},
			{
				"EmbeddedDocument/success",
				TypeEmbeddedDocument,
				[]byte{0x08, 0x00, 0x00, 0x00, 0x0A, 'A', 0x00, 0x00},
				nil, 8, 0,
			},
			{
				"CodeWithScope/invalid length",
				TypeCodeWithScope,
				[]byte{0x01, 0x02, 0x03},
				io.EOF, 0, 0,
			},
			{
				"CodeWithScope/not enough bytes",
				TypeCodeWithScope,
				[]byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03},
				io.EOF, 0, 0,
			},
			{
				"CodeWithScope/success",
				TypeCodeWithScope,
				cwsbytes,
				nil, 41, 0,
			},
			{
				"Binary/invalid length",
				TypeBinary,
				[]byte{0x01, 0x02, 0x03},
				io.EOF, 0, 0,
			},
			{
				"Binary/not enough bytes",
				TypeBinary,
				[]byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03},
				io.EOF, 0, 0,
			},
			{
				"Binary/success",
				TypeBinary,
				[]byte{0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03},
				nil, 8, 0,
			},
			{
				"Boolean/invalid length",
				TypeBoolean,
				[]byte{},
				io.EOF, 0, 0,
			},
			{
				"Boolean/success",
				TypeBoolean,
				[]byte{0x01},
				nil, 1, 0,
			},
			{
				"DBPointer/invalid length",
				TypeDBPointer,
				[]byte{0x01, 0x02, 0x03},
				io.EOF, 0, 0,
			},
			{
				"DBPointer/not enough bytes",
				TypeDBPointer,
				[]byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03},
				io.EOF, 0, 0,
			},
			{
				"DBPointer/success",
				TypeDBPointer,
				[]byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
				nil, 17, 0,
			},
			{"DBPointer/not enough bytes", TypeDateTime, []byte{0x01, 0x02, 0x03, 0x04}, io.EOF, 0, 0},
			{"DBPointer/success", TypeDateTime, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, nil, 8, 0},
			{"Double/not enough bytes", TypeDouble, []byte{0x01, 0x02, 0x03, 0x04}, io.EOF, 0, 0},
			{"Double/success", TypeDouble, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, nil, 8, 0},
			{"Int64/not enough bytes", TypeInt64, []byte{0x01, 0x02, 0x03, 0x04}, io.EOF, 0, 0},
			{"Int64/success", TypeInt64, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, nil, 8, 0},
			{"Timestamp/not enough bytes", TypeTimestamp, []byte{0x01, 0x02, 0x03, 0x04}, io.EOF, 0, 0},
			{"Timestamp/success", TypeTimestamp, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, nil, 8, 0},
			{
				"Decimal128/not enough bytes",
				TypeDecimal128,
				[]byte{0x01, 0x02, 0x03, 0x04},
				io.EOF, 0, 0,
			},
			{
				"Decimal128/success",
				TypeDecimal128,
				[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10},
				nil, 16, 0,
			},
			{"Int32/not enough bytes", TypeInt32, []byte{0x01, 0x02}, io.EOF, 0, 0},
			{"Int32/success", TypeInt32, []byte{0x01, 0x02, 0x03, 0x04}, nil, 4, 0},
			{"Javascript/invalid length", TypeJavaScript, strbytes[:2], io.EOF, 0, 0},
			{"Javascript/not enough bytes", TypeJavaScript, strbytes[:5], io.EOF, 0, 0},
			{"Javascript/success", TypeJavaScript, strbytes, nil, 8, 0},
			{"String/invalid length", TypeString, strbytes[:2], io.EOF, 0, 0},
			{"String/not enough bytes", TypeString, strbytes[:5], io.EOF, 0, 0},
			{"String/success", TypeString, strbytes, nil, 8, 0},
			{"Symbol/invalid length", TypeSymbol, strbytes[:2], io.EOF, 0, 0},
			{"Symbol/not enough bytes", TypeSymbol, strbytes[:5], io.EOF, 0, 0},
			{"Symbol/success", TypeSymbol, strbytes, nil, 8, 0},
			{"MaxKey/success", TypeMaxKey, []byte{}, nil, 0, 0},
			{"MinKey/success", TypeMinKey, []byte{}, nil, 0, 0},
			{"Null/success", TypeNull, []byte{}, nil, 0, 0},
			{"Undefined/success", TypeUndefined, []byte{}, nil, 0, 0},
			{
				"ObjectID/not enough bytes",
				TypeObjectID,
				[]byte{0x01, 0x02, 0x03, 0x04},
				io.EOF, 0, 0,
			},
			{
				"ObjectID/success",
				TypeObjectID,
				[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
				nil, 12, 0,
			},
			{
				"Regex/not enough bytes (first string)",
				TypeRegex,
				[]byte{'f', 'o', 'o'},
				io.EOF, 0, 0,
			},
			{
				"Regex/not enough bytes (second string)",
				TypeRegex,
				[]byte{'f', 'o', 'o', 0x00, 'b', 'a', 'r'},
				io.EOF, 0, 0,
			},
			{
				"Regex/success",
				TypeRegex,
				[]byte{0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00, 'i', 0x00},
				nil, 9, 3,
			},
			{
				"Unknown Type",
				Type(0),
				nil,
				fmt.Errorf("attempted to read bytes of unknown BSON type %v", Type(0)), 0, 0,
			},
		}

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				const startingEnd = 64
				t.Run("Skip", func(t *testing.T) {
					vr := &valueReader{
						r: bufio.NewReader(bytes.NewReader(tc.data[tc.startingOffset:tc.offset])),
						stack: []vrState{
							{mode: mTopLevel, end: startingEnd},
							{mode: mElement, vType: tc.t},
						},
						frame:  1,
						offset: tc.startingOffset,
					}

					err := vr.Skip()
					if !errequal(t, err, tc.err) {
						t.Errorf("Did not receive expected error; got %v; want %v", err, tc.err)
					}
					if tc.err == nil {
						offset := startingEnd - vr.stack[0].end
						if offset != tc.offset {
							t.Errorf("Offset not set at correct position; got %d; want %d", offset, tc.offset)
						}
					}
				})
				t.Run("ReadBytes", func(t *testing.T) {
					vr := &valueReader{
						r: bufio.NewReader(bytes.NewReader(tc.data[tc.startingOffset:tc.offset])),
						stack: []vrState{
							{mode: mTopLevel, end: startingEnd},
							{mode: mElement, vType: tc.t},
						},
						frame:  1,
						offset: tc.startingOffset,
					}

					_, got, err := vr.readValueBytes(nil)
					if !errequal(t, err, tc.err) {
						t.Errorf("Did not receive expected error; got %v; want %v", err, tc.err)
					}
					if tc.err == nil {
						offset := startingEnd - vr.stack[0].end
						if offset != tc.offset {
							t.Errorf("Offset not set at correct position; got %d; want %d", vr.offset, tc.offset)
						}
					}
					if tc.err == nil && !bytes.Equal(got, tc.data[tc.startingOffset:]) {
						t.Errorf("Did not receive expected bytes. got %v; want %v", got, tc.data[tc.startingOffset:])
					}
				})
			})
		}
		t.Run("ReadValueBytes/Top Level Doc", func(t *testing.T) {
			testCases := []struct {
				name     string
				want     []byte
				wantType Type
				wantErr  error
			}{
				{
					"success",
					bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159)),
					Type(0),
					nil,
				},
				{
					"wrong length",
					[]byte{0x01, 0x02, 0x03},
					Type(0),
					io.EOF,
				},
				{
					"append bytes",
					[]byte{0x01, 0x02, 0x03, 0x04},
					Type(0),
					io.ErrUnexpectedEOF,
				},
			}

			for _, tc := range testCases {
				tc := tc
				t.Run(tc.name, func(t *testing.T) {
					t.Parallel()
					vr := &valueReader{
						r: bufio.NewReader(bytes.NewReader(tc.want)),
						stack: []vrState{
							{mode: mTopLevel},
						},
						frame: 0,
					}
					gotType, got, gotErr := vr.readValueBytes(nil)
					if !errors.Is(gotErr, tc.wantErr) {
						t.Errorf("Did not receive expected error. got %v; want %v", gotErr, tc.wantErr)
					}
					if tc.wantErr == nil && gotType != tc.wantType {
						t.Errorf("Did not receive expected type. got %v; want %v", gotType, tc.wantType)
					}
					if tc.wantErr == nil && !bytes.Equal(got, tc.want) {
						t.Errorf("Did not receive expected bytes. got %v; want %v", got, tc.want)
					}
				})
			}
		})
	})

	t.Run("invalid transition", func(t *testing.T) {
		t.Run("Skip", func(t *testing.T) {
			vr := &valueReader{stack: []vrState{{mode: mTopLevel}}}
			wanterr := (&valueReader{stack: []vrState{{mode: mTopLevel}}}).invalidTransitionErr(0, "Skip", []mode{mElement, mValue})
			goterr := vr.Skip()
			if !cmp.Equal(goterr, wanterr, cmp.Comparer(assert.CompareErrors)) {
				t.Errorf("Expected correct invalid transition error. got %v; want %v", goterr, wanterr)
			}
		})
	})
	t.Run("ReadBytes", func(t *testing.T) {
		vr := &valueReader{stack: []vrState{{mode: mTopLevel}, {mode: mDocument}}, frame: 1}
		wanterr := (&valueReader{stack: []vrState{{mode: mTopLevel}, {mode: mDocument}}, frame: 1}).
			invalidTransitionErr(0, "ReadValueBytes", []mode{mElement, mValue})
		_, _, goterr := vr.readValueBytes(nil)
		if !cmp.Equal(goterr, wanterr, cmp.Comparer(assert.CompareErrors)) {
			t.Errorf("Expected correct invalid transition error. got %v; want %v", goterr, wanterr)
		}
	})
}

func errequal(t *testing.T, err1, err2 error) bool {
	t.Helper()
	if err1 == nil && err2 == nil { // If they are both nil, they are equal
		return true
	}
	if err1 == nil || err2 == nil { // If only one is nil, they are not equal
		return false
	}

	if errors.Is(err1, err2) { // They are the same error, they are equal
		return true
	}

	if err1.Error() == err2.Error() { // They string formats match, they are equal
		return true
	}

	return false
}
