package models_test

import (
	"bytes"
	"database/sql"
	"encoding/json"
	"testing"
	"time"

	"github.com/pocketbase/dbx"
	"github.com/pocketbase/pocketbase/models"
	"github.com/pocketbase/pocketbase/models/schema"
	"github.com/pocketbase/pocketbase/tools/list"
	"github.com/pocketbase/pocketbase/tools/types"
)

func TestNewRecord(t *testing.T) {
	collection := &models.Collection{
		Name: "test_collection",
		Schema: schema.NewSchema(
			&schema.SchemaField{
				Name: "test",
				Type: schema.FieldTypeText,
			},
		),
	}

	m := models.NewRecord(collection)

	if m.Collection().Name != collection.Name {
		t.Fatalf("Expected collection with name %q, got %q", collection.Id, m.Collection().Id)
	}

	if len(m.SchemaData()) != 0 {
		t.Fatalf("Expected empty schema data, got %v", m.SchemaData())
	}
}

func TestNewRecordFromNullStringMap(t *testing.T) {
	collection := &models.Collection{
		Name: "test",
		Schema: schema.NewSchema(
			&schema.SchemaField{
				Name: "field1",
				Type: schema.FieldTypeText,
			},
			&schema.SchemaField{
				Name: "field2",
				Type: schema.FieldTypeText,
			},
			&schema.SchemaField{
				Name: "field3",
				Type: schema.FieldTypeBool,
			},
			&schema.SchemaField{
				Name: "field4",
				Type: schema.FieldTypeNumber,
			},
			&schema.SchemaField{
				Name: "field5",
				Type: schema.FieldTypeSelect,
				Options: &schema.SelectOptions{
					Values:    []string{"test1", "test2"},
					MaxSelect: 1,
				},
			},
			&schema.SchemaField{
				Name: "field6",
				Type: schema.FieldTypeFile,
				Options: &schema.FileOptions{
					MaxSelect: 2,
					MaxSize:   1,
				},
			},
		),
	}

	data := dbx.NullStringMap{
		"id": sql.NullString{
			String: "test_id",
			Valid:  true,
		},
		"created": sql.NullString{
			String: "2022-01-01 10:00:00.123Z",
			Valid:  true,
		},
		"updated": sql.NullString{
			String: "2022-01-01 10:00:00.456Z",
			Valid:  true,
		},
		// auth collection specific fields
		"username": sql.NullString{
			String: "test_username",
			Valid:  true,
		},
		"email": sql.NullString{
			String: "test_email",
			Valid:  true,
		},
		"emailVisibility": sql.NullString{
			String: "true",
			Valid:  true,
		},
		"verified": sql.NullString{
			String: "",
			Valid:  false,
		},
		"tokenKey": sql.NullString{
			String: "test_tokenKey",
			Valid:  true,
		},
		"passwordHash": sql.NullString{
			String: "test_passwordHash",
			Valid:  true,
		},
		"lastResetSentAt": sql.NullString{
			String: "2022-01-02 10:00:00.123Z",
			Valid:  true,
		},
		"lastVerificationSentAt": sql.NullString{
			String: "2022-02-03 10:00:00.456Z",
			Valid:  true,
		},
		// custom schema fields
		"field1": sql.NullString{
			String: "test",
			Valid:  true,
		},
		"field2": sql.NullString{
			String: "test",
			Valid:  false, // test invalid db serialization
		},
		"field3": sql.NullString{
			String: "true",
			Valid:  true,
		},
		"field4": sql.NullString{
			String: "123.123",
			Valid:  true,
		},
		"field5": sql.NullString{
			String: `["test1","test2"]`, // will select only the last elem
			Valid:  true,
		},
		"field6": sql.NullString{
			String: "test", // will be converted to slice
			Valid:  true,
		},
		"unknown": sql.NullString{
			String: "test",
			Valid:  true,
		},
	}

	scenarios := []struct {
		collectionType string
		expectedJson   string
	}{
		{
			models.CollectionTypeBase,
			`{"collectionId":"","collectionName":"test","created":"2022-01-01 10:00:00.123Z","field1":"test","field2":"","field3":true,"field4":123.123,"field5":"test2","field6":["test"],"id":"test_id","updated":"2022-01-01 10:00:00.456Z"}`,
		},
		{
			models.CollectionTypeAuth,
			`{"collectionId":"","collectionName":"test","created":"2022-01-01 10:00:00.123Z","email":"test_email","emailVisibility":true,"field1":"test","field2":"","field3":true,"field4":123.123,"field5":"test2","field6":["test"],"id":"test_id","updated":"2022-01-01 10:00:00.456Z","username":"test_username","verified":false}`,
		},
	}

	for i, s := range scenarios {
		collection.Type = s.collectionType
		m := models.NewRecordFromNullStringMap(collection, data)
		m.IgnoreEmailVisibility(true)

		encoded, err := m.MarshalJSON()
		if err != nil {
			t.Errorf("(%d) Unexpected error: %v", i, err)
			continue
		}

		if string(encoded) != s.expectedJson {
			t.Errorf("(%d) Expected \n%v \ngot \n%v", i, s.expectedJson, string(encoded))
		}

		// additional data checks
		if collection.IsAuth() {
			if v := m.GetString(schema.FieldNamePasswordHash); v != "test_passwordHash" {
				t.Errorf("(%d) Expected %q, got %q", i, "test_passwordHash", v)
			}
			if v := m.GetString(schema.FieldNameTokenKey); v != "test_tokenKey" {
				t.Errorf("(%d) Expected %q, got %q", i, "test_tokenKey", v)
			}
			if v := m.GetString(schema.FieldNameLastResetSentAt); v != "2022-01-02 10:00:00.123Z" {
				t.Errorf("(%d) Expected %q, got %q", i, "2022-01-02 10:00:00.123Z", v)
			}
			if v := m.GetString(schema.FieldNameLastVerificationSentAt); v != "2022-02-03 10:00:00.456Z" {
				t.Errorf("(%d) Expected %q, got %q", i, "2022-01-02 10:00:00.123Z", v)
			}
		}
	}
}

func TestNewRecordsFromNullStringMaps(t *testing.T) {
	collection := &models.Collection{
		Name: "test",
		Schema: schema.NewSchema(
			&schema.SchemaField{
				Name: "field1",
				Type: schema.FieldTypeText,
			},
			&schema.SchemaField{
				Name: "field2",
				Type: schema.FieldTypeNumber,
			},
			&schema.SchemaField{
				Name: "field3",
				Type: schema.FieldTypeUrl,
			},
		),
	}

	data := []dbx.NullStringMap{
		{
			"id": sql.NullString{
				String: "test_id1",
				Valid:  true,
			},
			"created": sql.NullString{
				String: "2022-01-01 10:00:00.123Z",
				Valid:  true,
			},
			"updated": sql.NullString{
				String: "2022-01-01 10:00:00.456Z",
				Valid:  true,
			},
			// partial auth fields
			"email": sql.NullString{
				String: "test_email",
				Valid:  true,
			},
			"tokenKey": sql.NullString{
				String: "test_tokenKey",
				Valid:  true,
			},
			"emailVisibility": sql.NullString{
				String: "true",
				Valid:  true,
			},
			// custom schema fields
			"field1": sql.NullString{
				String: "test",
				Valid:  true,
			},
			"field2": sql.NullString{
				String: "123.123",
				Valid:  true,
			},
			"field3": sql.NullString{
				String: "test",
				Valid:  false, // should force resolving to empty string
			},
			"unknown": sql.NullString{
				String: "test",
				Valid:  true,
			},
		},
		{
			"field3": sql.NullString{
				String: "test",
				Valid:  true,
			},
			"email": sql.NullString{
				String: "test_email",
				Valid:  true,
			},
			"emailVisibility": sql.NullString{
				String: "false",
				Valid:  true,
			},
		},
	}

	scenarios := []struct {
		collectionType string
		expectedJson   string
	}{
		{
			models.CollectionTypeBase,
			`[{"collectionId":"","collectionName":"test","created":"2022-01-01 10:00:00.123Z","field1":"test","field2":123.123,"field3":"","id":"test_id1","updated":"2022-01-01 10:00:00.456Z"},{"collectionId":"","collectionName":"test","created":"","field1":"","field2":0,"field3":"test","id":"","updated":""}]`,
		},
		{
			models.CollectionTypeAuth,
			`[{"collectionId":"","collectionName":"test","created":"2022-01-01 10:00:00.123Z","email":"test_email","emailVisibility":true,"field1":"test","field2":123.123,"field3":"","id":"test_id1","updated":"2022-01-01 10:00:00.456Z","username":"","verified":false},{"collectionId":"","collectionName":"test","created":"","emailVisibility":false,"field1":"","field2":0,"field3":"test","id":"","updated":"","username":"","verified":false}]`,
		},
	}

	for i, s := range scenarios {
		collection.Type = s.collectionType
		result := models.NewRecordsFromNullStringMaps(collection, data)

		encoded, err := json.Marshal(result)
		if err != nil {
			t.Errorf("(%d) Unexpected error: %v", i, err)
			continue
		}

		if string(encoded) != s.expectedJson {
			t.Errorf("(%d) Expected \n%v \ngot \n%v", i, s.expectedJson, string(encoded))
		}
	}
}

func TestRecordTableName(t *testing.T) {
	collection := &models.Collection{}
	collection.Name = "test"
	collection.RefreshId()

	m := models.NewRecord(collection)

	if m.TableName() != collection.Name {
		t.Fatalf("Expected table %q, got %q", collection.Name, m.TableName())
	}
}

func TestRecordCollection(t *testing.T) {
	collection := &models.Collection{}
	collection.RefreshId()

	m := models.NewRecord(collection)

	if m.Collection().Id != collection.Id {
		t.Fatalf("Expected collection with id %v, got %v", collection.Id, m.Collection().Id)
	}
}

func TestRecordOriginalCopy(t *testing.T) {
	m := models.NewRecord(&models.Collection{})
	m.Load(map[string]any{"f": "123"})

	// change the field
	m.Set("f", "456")

	if v := m.GetString("f"); v != "456" {
		t.Fatalf("Expected f to be %q, got %q", "456", v)
	}

	if v := m.OriginalCopy().GetString("f"); v != "123" {
		t.Fatalf("Expected the initial/original f to be %q, got %q", "123", v)
	}

	// loading new data shouldn't affect the original state
	m.Load(map[string]any{"f": "789"})

	if v := m.GetString("f"); v != "789" {
		t.Fatalf("Expected f to be %q, got %q", "789", v)
	}

	if v := m.OriginalCopy().GetString("f"); v != "123" {
		t.Fatalf("Expected the initial/original f still to be %q, got %q", "123", v)
	}
}

func TestRecordCleanCopy(t *testing.T) {
	m := models.NewRecord(&models.Collection{
		Name: "cname",
		Type: models.CollectionTypeAuth,
	})
	m.Load(map[string]any{
		"id":       "id1",
		"created":  "2023-01-01 00:00:00.000Z",
		"updated":  "2023-01-02 00:00:00.000Z",
		"username": "test",
		"verified": true,
		"email":    "test@example.com",
		"unknown":  "456",
	})

	// make a change to ensure that the latest data is targeted
	m.Set("id", "id2")

	// allow the special flags and options to check whether they will be ignored
	m.SetExpand(map[string]any{"test": 123})
	m.IgnoreEmailVisibility(true)
	m.WithUnkownData(true)

	copy := m.CleanCopy()
	copyExport, _ := copy.MarshalJSON()

	expectedExport := []byte(`{"collectionId":"","collectionName":"cname","created":"2023-01-01 00:00:00.000Z","emailVisibility":false,"id":"id2","updated":"2023-01-02 00:00:00.000Z","username":"test","verified":true}`)
	if !bytes.Equal(copyExport, expectedExport) {
		t.Fatalf("Expected clean export \n%s, \ngot \n%s", expectedExport, copyExport)
	}
}

func TestRecordSetAndGetExpand(t *testing.T) {
	collection := &models.Collection{}
	m := models.NewRecord(collection)

	data := map[string]any{"test": 123}

	m.SetExpand(data)

	// change the original data to check if it was shallow copied
	data["test"] = 456

	expand := m.Expand()
	if v, ok := expand["test"]; !ok || v != 123 {
		t.Fatalf("Expected expand.test to be %v, got %v", 123, v)
	}
}

func TestRecordMergeExpand(t *testing.T) {
	collection := &models.Collection{}
	m := models.NewRecord(collection)
	m.Id = "m"

	// a
	a := models.NewRecord(collection)
	a.Id = "a"
	a1 := models.NewRecord(collection)
	a1.Id = "a1"
	a2 := models.NewRecord(collection)
	a2.Id = "a2"
	a3 := models.NewRecord(collection)
	a3.Id = "a3"
	a31 := models.NewRecord(collection)
	a31.Id = "a31"
	a32 := models.NewRecord(collection)
	a32.Id = "a32"
	a.SetExpand(map[string]any{
		"a1":  a1,
		"a23": []*models.Record{a2, a3},
	})
	a3.SetExpand(map[string]any{
		"a31": a31,
		"a32": []*models.Record{a32},
	})

	// b
	b := models.NewRecord(collection)
	b.Id = "b"
	b1 := models.NewRecord(collection)
	b1.Id = "b1"
	b.SetExpand(map[string]any{
		"b1": b1,
	})

	// c
	c := models.NewRecord(collection)
	c.Id = "c"

	// load initial expand
	m.SetExpand(map[string]any{
		"a": a,
		"b": b,
		"c": []*models.Record{c},
	})

	// a (new)
	aNew := models.NewRecord(collection)
	aNew.Id = a.Id
	a3New := models.NewRecord(collection)
	a3New.Id = a3.Id
	a32New := models.NewRecord(collection)
	a32New.Id = "a32New"
	a33New := models.NewRecord(collection)
	a33New.Id = "a33New"
	a3New.SetExpand(map[string]any{
		"a32":    []*models.Record{a32New},
		"a33New": a33New,
	})
	aNew.SetExpand(map[string]any{
		"a23": []*models.Record{a2, a3New},
	})

	// b (new)
	bNew := models.NewRecord(collection)
	bNew.Id = "bNew"
	dNew := models.NewRecord(collection)
	dNew.Id = "dNew"

	// merge expands
	m.MergeExpand(map[string]any{
		"a":    aNew,
		"b":    []*models.Record{bNew},
		"dNew": dNew,
	})

	result := m.Expand()

	raw, err := json.Marshal(result)
	if err != nil {
		t.Fatal(err)
	}
	rawStr := string(raw)

	expected := `{"a":{"collectionId":"","collectionName":"","created":"","expand":{"a1":{"collectionId":"","collectionName":"","created":"","id":"a1","updated":""},"a23":[{"collectionId":"","collectionName":"","created":"","id":"a2","updated":""},{"collectionId":"","collectionName":"","created":"","expand":{"a31":{"collectionId":"","collectionName":"","created":"","id":"a31","updated":""},"a32":[{"collectionId":"","collectionName":"","created":"","id":"a32","updated":""},{"collectionId":"","collectionName":"","created":"","id":"a32New","updated":""}],"a33New":{"collectionId":"","collectionName":"","created":"","id":"a33New","updated":""}},"id":"a3","updated":""}]},"id":"a","updated":""},"b":[{"collectionId":"","collectionName":"","created":"","expand":{"b1":{"collectionId":"","collectionName":"","created":"","id":"b1","updated":""}},"id":"b","updated":""},{"collectionId":"","collectionName":"","created":"","id":"bNew","updated":""}],"c":[{"collectionId":"","collectionName":"","created":"","id":"c","updated":""}],"dNew":{"collectionId":"","collectionName":"","created":"","id":"dNew","updated":""}}`

	if expected != rawStr {
		t.Fatalf("Expected \n%v, \ngot \n%v", expected, rawStr)
	}
}

func TestRecordMergeExpandNilCheck(t *testing.T) {
	collection := &models.Collection{}

	scenarios := []struct {
		name     string
		expand   map[string]any
		expected string
	}{
		{
			"nil expand",
			nil,
			`{"collectionId":"","collectionName":"","created":"","id":"","updated":""}`,
		},
		{
			"empty expand",
			map[string]any{},
			`{"collectionId":"","collectionName":"","created":"","id":"","updated":""}`,
		},
		{
			"non-empty expand",
			map[string]any{"test": models.NewRecord(collection)},
			`{"collectionId":"","collectionName":"","created":"","expand":{"test":{"collectionId":"","collectionName":"","created":"","id":"","updated":""}},"id":"","updated":""}`,
		},
	}

	for _, s := range scenarios {
		m := models.NewRecord(collection)
		m.MergeExpand(s.expand)

		raw, err := json.Marshal(m)
		if err != nil {
			t.Fatal(err)
		}
		rawStr := string(raw)

		if rawStr != s.expected {
			t.Fatalf("[%s] Expected \n%v, \ngot \n%v", s.name, s.expected, rawStr)
		}
	}
}

func TestRecordSchemaData(t *testing.T) {
	collection := &models.Collection{
		Type: models.CollectionTypeAuth,
		Schema: schema.NewSchema(
			&schema.SchemaField{
				Name: "field1",
				Type: schema.FieldTypeText,
			},
			&schema.SchemaField{
				Name: "field2",
				Type: schema.FieldTypeNumber,
			},
		),
	}

	m := models.NewRecord(collection)
	m.Set("email", "test@example.com")
	m.Set("field1", 123)
	m.Set("field2", 456)
	m.Set("unknown", 789)

	encoded, err := json.Marshal(m.SchemaData())
	if err != nil {
		t.Fatal(err)
	}

	expected := `{"field1":"123","field2":456}`

	if v := string(encoded); v != expected {
		t.Fatalf("Expected \n%v \ngot \n%v", v, expected)
	}
}

func TestRecordUnknownData(t *testing.T) {
	collection := &models.Collection{
		Schema: schema.NewSchema(
			&schema.SchemaField{
				Name: "field1",
				Type: schema.FieldTypeText,
			},
			&schema.SchemaField{
				Name: "field2",
				Type: schema.FieldTypeNumber,
			},
		),
	}

	data := map[string]any{
		"id":                     "test_id",
		"created":                "2022-01-01 00:00:00.000",
		"updated":                "2022-01-01 00:00:00.000",
		"collectionId":           "test_collectionId",
		"collectionName":         "test_collectionName",
		"expand":                 "test_expand",
		"field1":                 "test_field1",
		"field2":                 "test_field1",
		"unknown1":               "test_unknown1",
		"unknown2":               "test_unknown2",
		"passwordHash":           "test_passwordHash",
		"username":               "test_username",
		"emailVisibility":        true,
		"email":                  "test_email",
		"verified":               true,
		"tokenKey":               "test_tokenKey",
		"lastResetSentAt":        "2022-01-01 00:00:00.000",
		"lastVerificationSentAt": "2022-01-01 00:00:00.000",
	}

	scenarios := []struct {
		collectionType string
		expectedKeys   []string
	}{
		{
			models.CollectionTypeBase,
			[]string{
				"unknown1",
				"unknown2",
				"passwordHash",
				"username",
				"emailVisibility",
				"email",
				"verified",
				"tokenKey",
				"lastResetSentAt",
				"lastVerificationSentAt",
			},
		},
		{
			models.CollectionTypeAuth,
			[]string{"unknown1", "unknown2"},
		},
	}

	for i, s := range scenarios {
		collection.Type = s.collectionType
		m := models.NewRecord(collection)
		m.Load(data)

		result := m.UnknownData()

		if len(result) != len(s.expectedKeys) {
			t.Errorf("(%d) Expected data \n%v \ngot \n%v", i, s.expectedKeys, result)
			continue
		}

		for _, key := range s.expectedKeys {
			if _, ok := result[key]; !ok {
				t.Errorf("(%d) Missing expected key %q in \n%v", i, key, result)
			}
		}
	}
}

func TestRecordSetAndGet(t *testing.T) {
	collection := &models.Collection{
		Schema: schema.NewSchema(
			&schema.SchemaField{
				Name: "field1",
				Type: schema.FieldTypeText,
			},
			&schema.SchemaField{
				Name: "field2",
				Type: schema.FieldTypeNumber,
			},
		),
	}

	m := models.NewRecord(collection)
	m.Set("id", "test_id")
	m.Set("created", "2022-09-15 00:00:00.123Z")
	m.Set("updated", "invalid")
	m.Set("field1", 123)                         // should be casted to string
	m.Set("field2", "invlaid")                   // should be casted to zero-number
	m.Set("unknown", 456)                        // undefined fields are allowed but not exported by default
	m.Set("expand", map[string]any{"test": 123}) // should store the value in m.expand

	if m.Get("id") != "test_id" {
		t.Fatalf("Expected id %q, got %q", "test_id", m.Get("id"))
	}

	if m.GetString("created") != "2022-09-15 00:00:00.123Z" {
		t.Fatalf("Expected created %q, got %q", "2022-09-15 00:00:00.123Z", m.GetString("created"))
	}

	if m.GetString("updated") != "" {
		t.Fatalf("Expected updated to be empty, got %q", m.GetString("updated"))
	}

	if m.Get("field1") != "123" {
		t.Fatalf("Expected field1 %q, got %v", "123", m.Get("field1"))
	}

	if m.Get("field2") != 0.0 {
		t.Fatalf("Expected field2 %v, got %v", 0.0, m.Get("field2"))
	}

	if m.Get("unknown") != 456 {
		t.Fatalf("Expected unknown %v, got %v", 456, m.Get("unknown"))
	}

	if m.Expand()["test"] != 123 {
		t.Fatalf("Expected expand to be %v, got %v", map[string]any{"test": 123}, m.Expand())
	}
}

func TestRecordGetBool(t *testing.T) {
	scenarios := []struct {
		value    any
		expected bool
	}{
		{nil, false},
		{"", false},
		{0, false},
		{1, true},
		{[]string{"true"}, false},
		{time.Now(), false},
		{"test", false},
		{"false", false},
		{"true", true},
		{false, false},
		{true, true},
	}

	collection := &models.Collection{}

	for i, s := range scenarios {
		m := models.NewRecord(collection)
		m.Set("test", s.value)

		result := m.GetBool("test")
		if result != s.expected {
			t.Errorf("(%d) Expected %v, got %v", i, s.expected, result)
		}
	}
}

func TestRecordGetString(t *testing.T) {
	scenarios := []struct {
		value    any
		expected string
	}{
		{nil, ""},
		{"", ""},
		{0, "0"},
		{1.4, "1.4"},
		{[]string{"true"}, ""},
		{map[string]int{"test": 1}, ""},
		{[]byte("abc"), "abc"},
		{"test", "test"},
		{false, "false"},
		{true, "true"},
	}

	collection := &models.Collection{}

	for i, s := range scenarios {
		m := models.NewRecord(collection)
		m.Set("test", s.value)

		result := m.GetString("test")
		if result != s.expected {
			t.Errorf("(%d) Expected %v, got %v", i, s.expected, result)
		}
	}
}

func TestRecordGetInt(t *testing.T) {
	scenarios := []struct {
		value    any
		expected int
	}{
		{nil, 0},
		{"", 0},
		{[]string{"true"}, 0},
		{map[string]int{"test": 1}, 0},
		{time.Now(), 0},
		{"test", 0},
		{123, 123},
		{2.4, 2},
		{"123", 123},
		{"123.5", 0},
		{false, 0},
		{true, 1},
	}

	collection := &models.Collection{}

	for i, s := range scenarios {
		m := models.NewRecord(collection)
		m.Set("test", s.value)

		result := m.GetInt("test")
		if result != s.expected {
			t.Errorf("(%d) Expected %v, got %v", i, s.expected, result)
		}
	}
}

func TestRecordGetFloat(t *testing.T) {
	scenarios := []struct {
		value    any
		expected float64
	}{
		{nil, 0},
		{"", 0},
		{[]string{"true"}, 0},
		{map[string]int{"test": 1}, 0},
		{time.Now(), 0},
		{"test", 0},
		{123, 123},
		{2.4, 2.4},
		{"123", 123},
		{"123.5", 123.5},
		{false, 0},
		{true, 1},
	}

	collection := &models.Collection{}

	for i, s := range scenarios {
		m := models.NewRecord(collection)
		m.Set("test", s.value)

		result := m.GetFloat("test")
		if result != s.expected {
			t.Errorf("(%d) Expected %v, got %v", i, s.expected, result)
		}
	}
}

func TestRecordGetTime(t *testing.T) {
	nowTime := time.Now()
	testTime, _ := time.Parse(types.DefaultDateLayout, "2022-01-01 08:00:40.000Z")

	scenarios := []struct {
		value    any
		expected time.Time
	}{
		{nil, time.Time{}},
		{"", time.Time{}},
		{false, time.Time{}},
		{true, time.Time{}},
		{"test", time.Time{}},
		{[]string{"true"}, time.Time{}},
		{map[string]int{"test": 1}, time.Time{}},
		{1641024040, testTime},
		{"2022-01-01 08:00:40.000", testTime},
		{nowTime, nowTime},
	}

	collection := &models.Collection{}

	for i, s := range scenarios {
		m := models.NewRecord(collection)
		m.Set("test", s.value)

		result := m.GetTime("test")
		if !result.Equal(s.expected) {
			t.Errorf("(%d) Expected %v, got %v", i, s.expected, result)
		}
	}
}

func TestRecordGetDateTime(t *testing.T) {
	nowTime := time.Now()
	testTime, _ := time.Parse(types.DefaultDateLayout, "2022-01-01 08:00:40.000Z")

	scenarios := []struct {
		value    any
		expected time.Time
	}{
		{nil, time.Time{}},
		{"", time.Time{}},
		{false, time.Time{}},
		{true, time.Time{}},
		{"test", time.Time{}},
		{[]string{"true"}, time.Time{}},
		{map[string]int{"test": 1}, time.Time{}},
		{1641024040, testTime},
		{"2022-01-01 08:00:40.000", testTime},
		{nowTime, nowTime},
	}

	collection := &models.Collection{}

	for i, s := range scenarios {
		m := models.NewRecord(collection)
		m.Set("test", s.value)

		result := m.GetDateTime("test")
		if !result.Time().Equal(s.expected) {
			t.Errorf("(%d) Expected %v, got %v", i, s.expected, result)
		}
	}
}

func TestRecordGetStringSlice(t *testing.T) {
	nowTime := time.Now()

	scenarios := []struct {
		value    any
		expected []string
	}{
		{nil, []string{}},
		{"", []string{}},
		{false, []string{"false"}},
		{true, []string{"true"}},
		{nowTime, []string{}},
		{123, []string{"123"}},
		{"test", []string{"test"}},
		{map[string]int{"test": 1}, []string{}},
		{`["test1", "test2"]`, []string{"test1", "test2"}},
		{[]int{123, 123, 456}, []string{"123", "456"}},
		{[]string{"test", "test", "123"}, []string{"test", "123"}},
	}

	collection := &models.Collection{}

	for i, s := range scenarios {
		m := models.NewRecord(collection)
		m.Set("test", s.value)

		result := m.GetStringSlice("test")

		if len(result) != len(s.expected) {
			t.Errorf("(%d) Expected %d elements, got %d: %v", i, len(s.expected), len(result), result)
			continue
		}

		for _, v := range result {
			if !list.ExistInSlice(v, s.expected) {
				t.Errorf("(%d) Cannot find %v in %v", i, v, s.expected)
			}
		}
	}
}

func TestRecordUnmarshalJSONField(t *testing.T) {
	collection := &models.Collection{
		Schema: schema.NewSchema(&schema.SchemaField{
			Name: "field",
			Type: schema.FieldTypeJson,
		}),
	}
	m := models.NewRecord(collection)

	var testPointer *string
	var testStr string
	var testInt int
	var testBool bool
	var testSlice []int
	var testMap map[string]any

	scenarios := []struct {
		value        any
		destination  any
		expectError  bool
		expectedJson string
	}{
		{nil, testStr, true, `""`},
		{"", testStr, false, `""`},
		{1, testInt, false, `1`},
		{true, testBool, false, `true`},
		{[]int{1, 2, 3}, testSlice, false, `[1,2,3]`},
		{map[string]any{"test": 123}, testMap, false, `{"test":123}`},
		// json encoded values
		{`null`, testPointer, false, `null`},
		{`true`, testBool, false, `true`},
		{`456`, testInt, false, `456`},
		{`"test"`, testStr, false, `"test"`},
		{`[4,5,6]`, testSlice, false, `[4,5,6]`},
		{`{"test":456}`, testMap, false, `{"test":456}`},
	}

	for i, s := range scenarios {
		m.Set("field", s.value)

		err := m.UnmarshalJSONField("field", &s.destination)
		hasErr := err != nil

		if hasErr != s.expectError {
			t.Errorf("(%d) Expected hasErr %v, got %v", i, s.expectError, hasErr)
			continue
		}

		raw, _ := json.Marshal(s.destination)
		if v := string(raw); v != s.expectedJson {
			t.Errorf("(%d) Expected %q, got %q", i, s.expectedJson, v)
		}
	}
}

func TestRecordBaseFilesPath(t *testing.T) {
	collection := &models.Collection{}
	collection.RefreshId()
	collection.Name = "test"

	m := models.NewRecord(collection)
	m.RefreshId()

	expected := collection.BaseFilesPath() + "/" + m.Id
	result := m.BaseFilesPath()

	if result != expected {
		t.Fatalf("Expected %q, got %q", expected, result)
	}
}

func TestRecordFindFileFieldByFile(t *testing.T) {
	collection := &models.Collection{
		Schema: schema.NewSchema(
			&schema.SchemaField{
				Name: "field1",
				Type: schema.FieldTypeText,
			},
			&schema.SchemaField{
				Name: "field2",
				Type: schema.FieldTypeFile,
				Options: &schema.FileOptions{
					MaxSelect: 1,
					MaxSize:   1,
				},
			},
			&schema.SchemaField{
				Name: "field3",
				Type: schema.FieldTypeFile,
				Options: &schema.FileOptions{
					MaxSelect: 2,
					MaxSize:   1,
				},
			},
		),
	}

	m := models.NewRecord(collection)
	m.Set("field1", "test")
	m.Set("field2", "test.png")
	m.Set("field3", []string{"test1.png", "test2.png"})

	scenarios := []struct {
		filename    string
		expectField string
	}{
		{"", ""},
		{"test", ""},
		{"test2", ""},
		{"test.png", "field2"},
		{"test2.png", "field3"},
	}

	for i, s := range scenarios {
		result := m.FindFileFieldByFile(s.filename)

		var fieldName string
		if result != nil {
			fieldName = result.Name
		}

		if s.expectField != fieldName {
			t.Errorf("(%d) Expected field %v, got %v", i, s.expectField, result)
			continue
		}
	}
}

func TestRecordLoadAndData(t *testing.T) {
	collection := &models.Collection{
		Schema: schema.NewSchema(
			&schema.SchemaField{
				Name: "field1",
				Type: schema.FieldTypeText,
			},
			&schema.SchemaField{
				Name: "field2",
				Type: schema.FieldTypeNumber,
			},
		),
	}

	data := map[string]any{
		"id":      "test_id",
		"created": "2022-01-01 10:00:00.123Z",
		"updated": "2022-01-01 10:00:00.456Z",
		"field1":  "test_field",
		"field2":  "123", // should be casted to float
		"unknown": "test_unknown",
		// auth collection sepcific casting test
		"passwordHash":           "test_passwordHash",
		"emailVisibility":        "12345", // should be casted to bool only for auth collections
		"username":               123,     // should be casted to string only for auth collections
		"email":                  "test_email",
		"verified":               true,
		"tokenKey":               "test_tokenKey",
		"lastResetSentAt":        "2022-01-01 11:00:00.000", // should be casted to DateTime only for auth collections
		"lastVerificationSentAt": "2022-01-01 12:00:00.000", // should be casted to DateTime only for auth collections
	}

	scenarios := []struct {
		collectionType string
	}{
		{models.CollectionTypeBase},
		{models.CollectionTypeAuth},
	}

	for i, s := range scenarios {
		collection.Type = s.collectionType
		m := models.NewRecord(collection)

		m.Load(data)

		expectations := map[string]any{}
		for k, v := range data {
			expectations[k] = v
		}

		expectations["created"], _ = types.ParseDateTime("2022-01-01 10:00:00.123Z")
		expectations["updated"], _ = types.ParseDateTime("2022-01-01 10:00:00.456Z")
		expectations["field2"] = 123.0

		// extra casting test
		if collection.IsAuth() {
			lastResetSentAt, _ := types.ParseDateTime(expectations["lastResetSentAt"])
			lastVerificationSentAt, _ := types.ParseDateTime(expectations["lastVerificationSentAt"])
			expectations["emailVisibility"] = false
			expectations["username"] = "123"
			expectations["verified"] = true
			expectations["lastResetSentAt"] = lastResetSentAt
			expectations["lastVerificationSentAt"] = lastVerificationSentAt
		}

		for k, v := range expectations {
			if m.Get(k) != v {
				t.Errorf("(%d) Expected field %s to be %v, got %v", i, k, v, m.Get(k))
			}
		}
	}
}

func TestRecordColumnValueMap(t *testing.T) {
	collection := &models.Collection{
		Schema: schema.NewSchema(
			&schema.SchemaField{
				Name: "field1",
				Type: schema.FieldTypeText,
			},
			&schema.SchemaField{
				Name: "field2",
				Type: schema.FieldTypeFile,
				Options: &schema.FileOptions{
					MaxSelect: 1,
					MaxSize:   1,
				},
			},
			&schema.SchemaField{
				Name: "field3",
				Type: schema.FieldTypeSelect,
				Options: &schema.SelectOptions{
					MaxSelect: 2,
					Values:    []string{"test1", "test2", "test3"},
				},
			},
			&schema.SchemaField{
				Name: "field4",
				Type: schema.FieldTypeRelation,
				Options: &schema.RelationOptions{
					MaxSelect: types.Pointer(2),
				},
			},
		),
	}

	scenarios := []struct {
		collectionType string
		expectedJson   string
	}{
		{
			models.CollectionTypeBase,
			`{"created":"2022-01-01 10:00:30.123Z","field1":"test","field2":"test.png","field3":["test1","test2"],"field4":["test11","test12"],"id":"test_id","updated":""}`,
		},
		{
			models.CollectionTypeAuth,
			`{"created":"2022-01-01 10:00:30.123Z","email":"test_email","emailVisibility":true,"field1":"test","field2":"test.png","field3":["test1","test2"],"field4":["test11","test12"],"id":"test_id","lastResetSentAt":"2022-01-02 10:00:30.123Z","lastVerificationSentAt":"","passwordHash":"test_passwordHash","tokenKey":"test_tokenKey","updated":"","username":"test_username","verified":false}`,
		},
	}

	created, _ := types.ParseDateTime("2022-01-01 10:00:30.123Z")
	lastResetSentAt, _ := types.ParseDateTime("2022-01-02 10:00:30.123Z")
	data := map[string]any{
		"id":              "test_id",
		"created":         created,
		"field1":          "test",
		"field2":          "test.png",
		"field3":          []string{"test1", "test2"},
		"field4":          []string{"test11", "test12", "test11"}, // strip duplicate,
		"unknown":         "test_unknown",
		"passwordHash":    "test_passwordHash",
		"username":        "test_username",
		"emailVisibility": true,
		"email":           "test_email",
		"verified":        "invalid", // should be casted
		"tokenKey":        "test_tokenKey",
		"lastResetSentAt": lastResetSentAt,
	}

	m := models.NewRecord(collection)

	for i, s := range scenarios {
		collection.Type = s.collectionType

		m.Load(data)

		result := m.ColumnValueMap()

		encoded, err := json.Marshal(result)
		if err != nil {
			t.Errorf("(%d) Unexpected error %v", i, err)
			continue
		}

		if str := string(encoded); str != s.expectedJson {
			t.Errorf("(%d) Expected \n%v \ngot \n%v", i, s.expectedJson, str)
		}
	}
}

func TestRecordPublicExportAndMarshalJSON(t *testing.T) {
	collection := &models.Collection{
		Name: "c_name",
		Schema: schema.NewSchema(
			&schema.SchemaField{
				Name: "field1",
				Type: schema.FieldTypeText,
			},
			&schema.SchemaField{
				Name: "field2",
				Type: schema.FieldTypeFile,
				Options: &schema.FileOptions{
					MaxSelect: 1,
					MaxSize:   1,
				},
			},
			&schema.SchemaField{
				Name: "field3",
				Type: schema.FieldTypeSelect,
				Options: &schema.SelectOptions{
					MaxSelect: 2,
					Values:    []string{"test1", "test2", "test3"},
				},
			},
		),
	}
	collection.Id = "c_id"

	scenarios := []struct {
		collectionType string
		exportHidden   bool
		exportUnknown  bool
		expectedJson   string
	}{
		// base
		{
			models.CollectionTypeBase,
			false,
			false,
			`{"collectionId":"c_id","collectionName":"c_name","created":"2022-01-01 10:00:30.123Z","expand":{"test":123},"field1":"test","field2":"test.png","field3":["test1","test2"],"id":"test_id","updated":""}`,
		},
		{
			models.CollectionTypeBase,
			true,
			false,
			`{"collectionId":"c_id","collectionName":"c_name","created":"2022-01-01 10:00:30.123Z","expand":{"test":123},"field1":"test","field2":"test.png","field3":["test1","test2"],"id":"test_id","updated":""}`,
		},
		{
			models.CollectionTypeBase,
			false,
			true,
			`{"collectionId":"c_id","collectionName":"c_name","created":"2022-01-01 10:00:30.123Z","email":"test_email","emailVisibility":"test_invalid","expand":{"test":123},"field1":"test","field2":"test.png","field3":["test1","test2"],"id":"test_id","lastResetSentAt":"2022-01-02 10:00:30.123Z","lastVerificationSentAt":"test_lastVerificationSentAt","passwordHash":"test_passwordHash","tokenKey":"test_tokenKey","unknown":"test_unknown","updated":"","username":123,"verified":true}`,
		},
		{
			models.CollectionTypeBase,
			true,
			true,
			`{"collectionId":"c_id","collectionName":"c_name","created":"2022-01-01 10:00:30.123Z","email":"test_email","emailVisibility":"test_invalid","expand":{"test":123},"field1":"test","field2":"test.png","field3":["test1","test2"],"id":"test_id","lastResetSentAt":"2022-01-02 10:00:30.123Z","lastVerificationSentAt":"test_lastVerificationSentAt","passwordHash":"test_passwordHash","tokenKey":"test_tokenKey","unknown":"test_unknown","updated":"","username":123,"verified":true}`,
		},

		// auth
		{
			models.CollectionTypeAuth,
			false,
			false,
			`{"collectionId":"c_id","collectionName":"c_name","created":"2022-01-01 10:00:30.123Z","emailVisibility":false,"expand":{"test":123},"field1":"test","field2":"test.png","field3":["test1","test2"],"id":"test_id","updated":"","username":"123","verified":true}`,
		},
		{
			models.CollectionTypeAuth,
			true,
			false,
			`{"collectionId":"c_id","collectionName":"c_name","created":"2022-01-01 10:00:30.123Z","email":"test_email","emailVisibility":false,"expand":{"test":123},"field1":"test","field2":"test.png","field3":["test1","test2"],"id":"test_id","updated":"","username":"123","verified":true}`,
		},
		{
			models.CollectionTypeAuth,
			false,
			true,
			`{"collectionId":"c_id","collectionName":"c_name","created":"2022-01-01 10:00:30.123Z","emailVisibility":false,"expand":{"test":123},"field1":"test","field2":"test.png","field3":["test1","test2"],"id":"test_id","unknown":"test_unknown","updated":"","username":"123","verified":true}`,
		},
		{
			models.CollectionTypeAuth,
			true,
			true,
			`{"collectionId":"c_id","collectionName":"c_name","created":"2022-01-01 10:00:30.123Z","email":"test_email","emailVisibility":false,"expand":{"test":123},"field1":"test","field2":"test.png","field3":["test1","test2"],"id":"test_id","unknown":"test_unknown","updated":"","username":"123","verified":true}`,
		},
	}

	created, _ := types.ParseDateTime("2022-01-01 10:00:30.123Z")
	lastResetSentAt, _ := types.ParseDateTime("2022-01-02 10:00:30.123Z")

	data := map[string]any{
		"id":                     "test_id",
		"created":                created,
		"field1":                 "test",
		"field2":                 "test.png",
		"field3":                 []string{"test1", "test2"},
		"expand":                 map[string]any{"test": 123},
		"collectionId":           "m_id",   // should be always ignored
		"collectionName":         "m_name", // should be always ignored
		"unknown":                "test_unknown",
		"passwordHash":           "test_passwordHash",
		"username":               123,            // for auth collections should be casted to string
		"emailVisibility":        "test_invalid", // for auth collections should be casted to bool
		"email":                  "test_email",
		"verified":               true,
		"tokenKey":               "test_tokenKey",
		"lastResetSentAt":        lastResetSentAt,
		"lastVerificationSentAt": "test_lastVerificationSentAt",
	}

	m := models.NewRecord(collection)

	for i, s := range scenarios {
		collection.Type = s.collectionType

		m.Load(data)
		m.IgnoreEmailVisibility(s.exportHidden)
		m.WithUnkownData(s.exportUnknown)

		exportResult, err := json.Marshal(m.PublicExport())
		if err != nil {
			t.Errorf("(%d) Unexpected error %v", i, err)
			continue
		}
		exportResultStr := string(exportResult)

		// MarshalJSON and PublicExport should return the same
		marshalResult, err := m.MarshalJSON()
		if err != nil {
			t.Errorf("(%d) Unexpected error %v", i, err)
			continue
		}
		marshalResultStr := string(marshalResult)

		if exportResultStr != marshalResultStr {
			t.Errorf("(%d) Expected the PublicExport to be the same as MarshalJSON, but got \n%v \nvs \n%v", i, exportResultStr, marshalResultStr)
		}

		if exportResultStr != s.expectedJson {
			t.Errorf("(%d) Expected json \n%v \ngot \n%v", i, s.expectedJson, exportResultStr)
		}
	}
}

func TestRecordUnmarshalJSON(t *testing.T) {
	collection := &models.Collection{
		Schema: schema.NewSchema(
			&schema.SchemaField{
				Name: "field1",
				Type: schema.FieldTypeText,
			},
			&schema.SchemaField{
				Name: "field2",
				Type: schema.FieldTypeNumber,
			},
		),
	}

	data := map[string]any{
		"id":      "test_id",
		"created": "2022-01-01 10:00:00.123Z",
		"updated": "2022-01-01 10:00:00.456Z",
		"field1":  "test_field",
		"field2":  "123", // should be casted to float
		"unknown": "test_unknown",
		// auth collection sepcific casting test
		"passwordHash":           "test_passwordHash",
		"emailVisibility":        "12345", // should be casted to bool only for auth collections
		"username":               123.123, // should be casted to string only for auth collections
		"email":                  "test_email",
		"verified":               true,
		"tokenKey":               "test_tokenKey",
		"lastResetSentAt":        "2022-01-01 11:00:00.000", // should be casted to DateTime only for auth collections
		"lastVerificationSentAt": "2022-01-01 12:00:00.000", // should be casted to DateTime only for auth collections
	}
	dataRaw, err := json.Marshal(data)
	if err != nil {
		t.Fatalf("Unexpected data marshal error %v", err)
	}

	scenarios := []struct {
		collectionType string
	}{
		{models.CollectionTypeBase},
		{models.CollectionTypeAuth},
	}

	// with invalid data
	m0 := models.NewRecord(collection)
	if err := m0.UnmarshalJSON([]byte("test")); err == nil {
		t.Fatal("Expected error, got nil")
	}

	// with valid data (it should be pretty much the same as load)
	for i, s := range scenarios {
		collection.Type = s.collectionType
		m := models.NewRecord(collection)

		err := m.UnmarshalJSON(dataRaw)
		if err != nil {
			t.Errorf("(%d) Unexpected error %v", i, err)
			continue
		}

		expectations := map[string]any{}
		for k, v := range data {
			expectations[k] = v
		}

		expectations["created"], _ = types.ParseDateTime("2022-01-01 10:00:00.123Z")
		expectations["updated"], _ = types.ParseDateTime("2022-01-01 10:00:00.456Z")
		expectations["field2"] = 123.0

		// extra casting test
		if collection.IsAuth() {
			lastResetSentAt, _ := types.ParseDateTime(expectations["lastResetSentAt"])
			lastVerificationSentAt, _ := types.ParseDateTime(expectations["lastVerificationSentAt"])
			expectations["emailVisibility"] = false
			expectations["username"] = "123.123"
			expectations["verified"] = true
			expectations["lastResetSentAt"] = lastResetSentAt
			expectations["lastVerificationSentAt"] = lastVerificationSentAt
		}

		for k, v := range expectations {
			if m.Get(k) != v {
				t.Errorf("(%d) Expected field %s to be %v, got %v", i, k, v, m.Get(k))
			}
		}
	}
}

func TestRecordReplaceModifers(t *testing.T) {
	collection := &models.Collection{
		Schema: schema.NewSchema(
			&schema.SchemaField{
				Name: "text",
				Type: schema.FieldTypeText,
			},
			&schema.SchemaField{
				Name: "number",
				Type: schema.FieldTypeNumber,
			},
			&schema.SchemaField{
				Name:    "rel_one",
				Type:    schema.FieldTypeRelation,
				Options: &schema.RelationOptions{MaxSelect: types.Pointer(1)},
			},
			&schema.SchemaField{
				Name: "rel_many",
				Type: schema.FieldTypeRelation,
			},
			&schema.SchemaField{
				Name:    "select_one",
				Type:    schema.FieldTypeSelect,
				Options: &schema.SelectOptions{MaxSelect: 1},
			},
			&schema.SchemaField{
				Name:    "select_many",
				Type:    schema.FieldTypeSelect,
				Options: &schema.SelectOptions{MaxSelect: 10},
			},
			&schema.SchemaField{
				Name:    "file_one",
				Type:    schema.FieldTypeFile,
				Options: &schema.FileOptions{MaxSelect: 1},
			},
			&schema.SchemaField{
				Name:    "file_one_index",
				Type:    schema.FieldTypeFile,
				Options: &schema.FileOptions{MaxSelect: 1},
			},
			&schema.SchemaField{
				Name:    "file_one_name",
				Type:    schema.FieldTypeFile,
				Options: &schema.FileOptions{MaxSelect: 1},
			},
			&schema.SchemaField{
				Name:    "file_many",
				Type:    schema.FieldTypeFile,
				Options: &schema.FileOptions{MaxSelect: 10},
			},
		),
	}

	record := models.NewRecord(collection)

	record.Load(map[string]any{
		"text":           "test",
		"number":         10,
		"rel_one":        "a",
		"rel_many":       []string{"a", "b"},
		"select_one":     "a",
		"select_many":    []string{"a", "b", "c"},
		"file_one":       "a",
		"file_one_index": "b",
		"file_one_name":  "c",
		"file_many":      []string{"a", "b", "c", "d", "e", "f"},
	})

	result := record.ReplaceModifers(map[string]any{
		"text-":            "m-",
		"text+":            "m+",
		"number-":          3,
		"number+":          5,
		"rel_one-":         "a",
		"rel_one+":         "b",
		"rel_many-":        []string{"a"},
		"rel_many+":        []string{"c", "d", "e"},
		"select_one-":      "a",
		"select_one+":      "c",
		"select_many-":     []string{"b", "c"},
		"select_many+":     []string{"d", "e"},
		"file_one+":        "skip", // should be ignored
		"file_one-":        "a",
		"file_one_index.0": "",
		"file_one_name.c":  "",
		"file_many+":       []string{"e", "f"}, // should be ignored
		"file_many-":       []string{"c", "d"},
		"file_many.f":      nil,
		"file_many.0":      nil,
	})

	raw, err := json.Marshal(result)
	if err != nil {
		t.Fatal(err)
	}

	expected := `{"file_many":["b","e"],"file_one":"","file_one_index":"","file_one_name":"","number":12,"rel_many":["b","c","d","e"],"rel_one":"b","select_many":["a","d","e"],"select_one":"c","text":"test"}`

	if v := string(raw); v != expected {
		t.Fatalf("Expected \n%s, \ngot \n%s", expected, v)
	}
}

// -------------------------------------------------------------------
// Auth helpers:
// -------------------------------------------------------------------

func TestRecordUsername(t *testing.T) {
	scenarios := []struct {
		collectionType string
		expectError    bool
	}{
		{models.CollectionTypeBase, true},
		{models.CollectionTypeAuth, false},
	}

	testValue := "test 1232 !@#%" // formatting isn't checked

	for i, s := range scenarios {
		collection := &models.Collection{Type: s.collectionType}
		m := models.NewRecord(collection)

		if s.expectError {
			if err := m.SetUsername(testValue); err == nil {
				t.Errorf("(%d) Expected error, got nil", i)
			}
			if v := m.Username(); v != "" {
				t.Fatalf("(%d) Expected empty string, got %q", i, v)
			}
			// verify that nothing is stored in the record data slice
			if v := m.Get(schema.FieldNameUsername); v != nil {
				t.Fatalf("(%d) Didn't expect data field %q: %v", i, schema.FieldNameUsername, v)
			}
		} else {
			if err := m.SetUsername(testValue); err != nil {
				t.Fatalf("(%d) Expected nil, got error %v", i, err)
			}
			if v := m.Username(); v != testValue {
				t.Fatalf("(%d) Expected %q, got %q", i, testValue, v)
			}
			// verify that the field is stored in the record data slice
			if v := m.Get(schema.FieldNameUsername); v != testValue {
				t.Fatalf("(%d) Expected data field value %q, got %q", i, testValue, v)
			}
		}
	}
}

func TestRecordEmail(t *testing.T) {
	scenarios := []struct {
		collectionType string
		expectError    bool
	}{
		{models.CollectionTypeBase, true},
		{models.CollectionTypeAuth, false},
	}

	testValue := "test 1232 !@#%" // formatting isn't checked

	for i, s := range scenarios {
		collection := &models.Collection{Type: s.collectionType}
		m := models.NewRecord(collection)

		if s.expectError {
			if err := m.SetEmail(testValue); err == nil {
				t.Errorf("(%d) Expected error, got nil", i)
			}
			if v := m.Email(); v != "" {
				t.Fatalf("(%d) Expected empty string, got %q", i, v)
			}
			// verify that nothing is stored in the record data slice
			if v := m.Get(schema.FieldNameEmail); v != nil {
				t.Fatalf("(%d) Didn't expect data field %q: %v", i, schema.FieldNameEmail, v)
			}
		} else {
			if err := m.SetEmail(testValue); err != nil {
				t.Fatalf("(%d) Expected nil, got error %v", i, err)
			}
			if v := m.Email(); v != testValue {
				t.Fatalf("(%d) Expected %q, got %q", i, testValue, v)
			}
			// verify that the field is stored in the record data slice
			if v := m.Get(schema.FieldNameEmail); v != testValue {
				t.Fatalf("(%d) Expected data field value %q, got %q", i, testValue, v)
			}
		}
	}
}

func TestRecordEmailVisibility(t *testing.T) {
	scenarios := []struct {
		collectionType string
		value          bool
		expectError    bool
	}{
		{models.CollectionTypeBase, true, true},
		{models.CollectionTypeBase, true, true},
		{models.CollectionTypeAuth, false, false},
		{models.CollectionTypeAuth, true, false},
	}

	for i, s := range scenarios {
		collection := &models.Collection{Type: s.collectionType}
		m := models.NewRecord(collection)

		if s.expectError {
			if err := m.SetEmailVisibility(s.value); err == nil {
				t.Errorf("(%d) Expected error, got nil", i)
			}
			if v := m.EmailVisibility(); v != false {
				t.Fatalf("(%d) Expected empty string, got %v", i, v)
			}
			// verify that nothing is stored in the record data slice
			if v := m.Get(schema.FieldNameEmailVisibility); v != nil {
				t.Fatalf("(%d) Didn't expect data field %q: %v", i, schema.FieldNameEmailVisibility, v)
			}
		} else {
			if err := m.SetEmailVisibility(s.value); err != nil {
				t.Fatalf("(%d) Expected nil, got error %v", i, err)
			}
			if v := m.EmailVisibility(); v != s.value {
				t.Fatalf("(%d) Expected %v, got %v", i, s.value, v)
			}
			// verify that the field is stored in the record data slice
			if v := m.Get(schema.FieldNameEmailVisibility); v != s.value {
				t.Fatalf("(%d) Expected data field value %v, got %v", i, s.value, v)
			}
		}
	}
}

func TestRecordEmailVerified(t *testing.T) {
	scenarios := []struct {
		collectionType string
		value          bool
		expectError    bool
	}{
		{models.CollectionTypeBase, true, true},
		{models.CollectionTypeBase, true, true},
		{models.CollectionTypeAuth, false, false},
		{models.CollectionTypeAuth, true, false},
	}

	for i, s := range scenarios {
		collection := &models.Collection{Type: s.collectionType}
		m := models.NewRecord(collection)

		if s.expectError {
			if err := m.SetVerified(s.value); err == nil {
				t.Errorf("(%d) Expected error, got nil", i)
			}
			if v := m.Verified(); v != false {
				t.Fatalf("(%d) Expected empty string, got %v", i, v)
			}
			// verify that nothing is stored in the record data slice
			if v := m.Get(schema.FieldNameVerified); v != nil {
				t.Fatalf("(%d) Didn't expect data field %q: %v", i, schema.FieldNameVerified, v)
			}
		} else {
			if err := m.SetVerified(s.value); err != nil {
				t.Fatalf("(%d) Expected nil, got error %v", i, err)
			}
			if v := m.Verified(); v != s.value {
				t.Fatalf("(%d) Expected %v, got %v", i, s.value, v)
			}
			// verify that the field is stored in the record data slice
			if v := m.Get(schema.FieldNameVerified); v != s.value {
				t.Fatalf("(%d) Expected data field value %v, got %v", i, s.value, v)
			}
		}
	}
}

func TestRecordTokenKey(t *testing.T) {
	scenarios := []struct {
		collectionType string
		expectError    bool
	}{
		{models.CollectionTypeBase, true},
		{models.CollectionTypeAuth, false},
	}

	testValue := "test 1232 !@#%" // formatting isn't checked

	for i, s := range scenarios {
		collection := &models.Collection{Type: s.collectionType}
		m := models.NewRecord(collection)

		if s.expectError {
			if err := m.SetTokenKey(testValue); err == nil {
				t.Errorf("(%d) Expected error, got nil", i)
			}
			if v := m.TokenKey(); v != "" {
				t.Fatalf("(%d) Expected empty string, got %q", i, v)
			}
			// verify that nothing is stored in the record data slice
			if v := m.Get(schema.FieldNameTokenKey); v != nil {
				t.Fatalf("(%d) Didn't expect data field %q: %v", i, schema.FieldNameTokenKey, v)
			}
		} else {
			if err := m.SetTokenKey(testValue); err != nil {
				t.Fatalf("(%d) Expected nil, got error %v", i, err)
			}
			if v := m.TokenKey(); v != testValue {
				t.Fatalf("(%d) Expected %q, got %q", i, testValue, v)
			}
			// verify that the field is stored in the record data slice
			if v := m.Get(schema.FieldNameTokenKey); v != testValue {
				t.Fatalf("(%d) Expected data field value %q, got %q", i, testValue, v)
			}
		}
	}
}

func TestRecordRefreshTokenKey(t *testing.T) {
	scenarios := []struct {
		collectionType string
		expectError    bool
	}{
		{models.CollectionTypeBase, true},
		{models.CollectionTypeAuth, false},
	}

	for i, s := range scenarios {
		collection := &models.Collection{Type: s.collectionType}
		m := models.NewRecord(collection)

		if s.expectError {
			if err := m.RefreshTokenKey(); err == nil {
				t.Errorf("(%d) Expected error, got nil", i)
			}
			if v := m.TokenKey(); v != "" {
				t.Fatalf("(%d) Expected empty string, got %q", i, v)
			}
			// verify that nothing is stored in the record data slice
			if v := m.Get(schema.FieldNameTokenKey); v != nil {
				t.Fatalf("(%d) Didn't expect data field %q: %v", i, schema.FieldNameTokenKey, v)
			}
		} else {
			if err := m.RefreshTokenKey(); err != nil {
				t.Fatalf("(%d) Expected nil, got error %v", i, err)
			}
			if v := m.TokenKey(); len(v) != 50 {
				t.Fatalf("(%d) Expected 50 chars, got %d", i, len(v))
			}
			// verify that the field is stored in the record data slice
			if v := m.Get(schema.FieldNameTokenKey); v != m.TokenKey() {
				t.Fatalf("(%d) Expected data field value %q, got %q", i, m.TokenKey(), v)
			}
		}
	}
}

func TestRecordLastResetSentAt(t *testing.T) {
	scenarios := []struct {
		collectionType string
		expectError    bool
	}{
		{models.CollectionTypeBase, true},
		{models.CollectionTypeAuth, false},
	}

	testValue, err := types.ParseDateTime("2022-01-01 00:00:00.123Z")
	if err != nil {
		t.Fatal(err)
	}

	for i, s := range scenarios {
		collection := &models.Collection{Type: s.collectionType}
		m := models.NewRecord(collection)

		if s.expectError {
			if err := m.SetLastResetSentAt(testValue); err == nil {
				t.Errorf("(%d) Expected error, got nil", i)
			}
			if v := m.LastResetSentAt(); !v.IsZero() {
				t.Fatalf("(%d) Expected empty value, got %v", i, v)
			}
			// verify that nothing is stored in the record data slice
			if v := m.Get(schema.FieldNameLastResetSentAt); v != nil {
				t.Fatalf("(%d) Didn't expect data field %q: %v", i, schema.FieldNameLastResetSentAt, v)
			}
		} else {
			if err := m.SetLastResetSentAt(testValue); err != nil {
				t.Fatalf("(%d) Expected nil, got error %v", i, err)
			}
			if v := m.LastResetSentAt(); v != testValue {
				t.Fatalf("(%d) Expected %v, got %v", i, testValue, v)
			}
			// verify that the field is stored in the record data slice
			if v := m.Get(schema.FieldNameLastResetSentAt); v != testValue {
				t.Fatalf("(%d) Expected data field value %v, got %v", i, testValue, v)
			}
		}
	}
}

func TestRecordLastVerificationSentAt(t *testing.T) {
	scenarios := []struct {
		collectionType string
		expectError    bool
	}{
		{models.CollectionTypeBase, true},
		{models.CollectionTypeAuth, false},
	}

	testValue, err := types.ParseDateTime("2022-01-01 00:00:00.123Z")
	if err != nil {
		t.Fatal(err)
	}

	for i, s := range scenarios {
		collection := &models.Collection{Type: s.collectionType}
		m := models.NewRecord(collection)

		if s.expectError {
			if err := m.SetLastVerificationSentAt(testValue); err == nil {
				t.Errorf("(%d) Expected error, got nil", i)
			}
			if v := m.LastVerificationSentAt(); !v.IsZero() {
				t.Fatalf("(%d) Expected empty value, got %v", i, v)
			}
			// verify that nothing is stored in the record data slice
			if v := m.Get(schema.FieldNameLastVerificationSentAt); v != nil {
				t.Fatalf("(%d) Didn't expect data field %q: %v", i, schema.FieldNameLastVerificationSentAt, v)
			}
		} else {
			if err := m.SetLastVerificationSentAt(testValue); err != nil {
				t.Fatalf("(%d) Expected nil, got error %v", i, err)
			}
			if v := m.LastVerificationSentAt(); v != testValue {
				t.Fatalf("(%d) Expected %v, got %v", i, testValue, v)
			}
			// verify that the field is stored in the record data slice
			if v := m.Get(schema.FieldNameLastVerificationSentAt); v != testValue {
				t.Fatalf("(%d) Expected data field value %v, got %v", i, testValue, v)
			}
		}
	}
}

func TestRecordPasswordHash(t *testing.T) {
	m := models.NewRecord(&models.Collection{})

	if v := m.PasswordHash(); v != "" {
		t.Errorf("Expected PasswordHash() to be empty, got %v", v)
	}

	m.Set(schema.FieldNamePasswordHash, "test")

	if v := m.PasswordHash(); v != "test" {
		t.Errorf("Expected PasswordHash() to be 'test', got %v", v)
	}
}

func TestRecordValidatePassword(t *testing.T) {
	// 123456
	hash := "$2a$10$YKU8mPP8sTE3xZrpuM.xQuq27KJ7aIJB2oUeKPsDDqZshbl5g5cDK"

	scenarios := []struct {
		collectionType string
		password       string
		hash           string
		expected       bool
	}{
		{models.CollectionTypeBase, "123456", hash, false},
		{models.CollectionTypeAuth, "", "", false},
		{models.CollectionTypeAuth, "", hash, false},
		{models.CollectionTypeAuth, "123456", hash, true},
		{models.CollectionTypeAuth, "654321", hash, false},
	}

	for i, s := range scenarios {
		collection := &models.Collection{Type: s.collectionType}
		m := models.NewRecord(collection)
		m.Set(schema.FieldNamePasswordHash, hash)

		if v := m.ValidatePassword(s.password); v != s.expected {
			t.Errorf("(%d) Expected %v, got %v", i, s.expected, v)
		}
	}
}

func TestRecordSetPassword(t *testing.T) {
	scenarios := []struct {
		collectionType string
		password       string
		expectError    bool
	}{
		{models.CollectionTypeBase, "", true},
		{models.CollectionTypeBase, "123456", true},
		{models.CollectionTypeAuth, "", true},
		{models.CollectionTypeAuth, "123456", false},
	}

	for i, s := range scenarios {
		collection := &models.Collection{Type: s.collectionType}
		m := models.NewRecord(collection)

		if s.expectError {
			if err := m.SetPassword(s.password); err == nil {
				t.Errorf("(%d) Expected error, got nil", i)
			}
			if v := m.GetString(schema.FieldNamePasswordHash); v != "" {
				t.Errorf("(%d) Expected empty hash, got %q", i, v)
			}
		} else {
			if err := m.SetPassword(s.password); err != nil {
				t.Errorf("(%d) Expected nil, got err", i)
			}
			if v := m.GetString(schema.FieldNamePasswordHash); v == "" {
				t.Errorf("(%d) Expected non empty hash", i)
			}
			if !m.ValidatePassword(s.password) {
				t.Errorf("(%d) Expected true, got false", i)
			}
		}
	}
}
