package tormenta_test

import (
	"testing"

	"github.com/jpincas/gouuidv6"
	"github.com/jpincas/tormenta/testtypes"

	"github.com/jpincas/tormenta"
)

func Test_Relations_LoadByID(t *testing.T) {
	// Open the DB
	db, _ := tormenta.OpenTestWithOptions("data/tests", testDBOptions)
	defer db.Close()

	doubleNestedStruct1 := testtypes.DoubleNestedRelatedStruct{}
	doubleNestedStruct2 := testtypes.DoubleNestedRelatedStruct{}
	db.Save(&doubleNestedStruct1, &doubleNestedStruct2)

	// Created some nested structs and save
	nestedStruct1 := testtypes.NestedRelatedStruct{
		NestedID: doubleNestedStruct2.ID,
	}
	nestedStruct2 := testtypes.NestedRelatedStruct{
		NestedID: doubleNestedStruct1.ID,
	}
	db.Save(&nestedStruct1, &nestedStruct2)

	// Create some related structs which nest the above and save
	relatedStruct1 := testtypes.RelatedStruct{
		NestedID: nestedStruct2.ID,
	}
	relatedStruct2 := testtypes.RelatedStruct{
		NestedID: nestedStruct1.ID,
	}
	db.Save(&relatedStruct1, &relatedStruct2)

	// Create some full structs including these relations
	struct1 := testtypes.FullStruct{
		HasOneID:        relatedStruct1.ID,
		HasAnotherOneID: relatedStruct1.ID,
		HasManyIDs:      []gouuidv6.UUID{relatedStruct1.ID, relatedStruct2.ID},
	}

	struct2 := testtypes.FullStruct{
		HasOneID:        relatedStruct2.ID,
		HasAnotherOneID: relatedStruct2.ID,
		HasManyIDs:      []gouuidv6.UUID{relatedStruct1.ID, relatedStruct2.ID},
	}

	struct3 := testtypes.FullStruct{
		HasOneID:        relatedStruct1.ID,
		HasAnotherOneID: relatedStruct1.ID,
		HasManyIDs:      []gouuidv6.UUID{relatedStruct1.ID, relatedStruct2.ID},
	}
	db.Save(&struct1, &struct2, &struct3)

	// A) Single invalid relation, no nesting
	// Reload
	fullStructs := []testtypes.FullStruct{}
	if n, err := db.Find(&fullStructs).Run(); err != nil || n != 3 {
		t.Errorf("Save/retrieve failed. Err: %v; n: %v", err, n)
	}

	// Convert to records
	records := []tormenta.Record{}
	for i := range fullStructs {
		records = append(records, &fullStructs[i])
	}

	testName := "non existent relation"
	if err := tormenta.LoadByID(db, []string{"DoesntHaveOne"}, records...); err == nil {
		t.Errorf("Testing %s. Should have error but didn't", testName)
	}

	// 1) Single relation, no nesting
	// Reload
	fullStructs = []testtypes.FullStruct{}
	if n, err := db.Find(&fullStructs).Run(); err != nil || n != 3 {
		t.Errorf("Save/retrieve failed. Err: %v; n: %v", err, n)
	}

	// Convert to records
	records = []tormenta.Record{}
	for i := range fullStructs {
		records = append(records, &fullStructs[i])
	}

	testName = "single, one level relation"
	if err := tormenta.LoadByID(db, []string{"HasOne"}, records...); err != nil {
		t.Errorf("Testing %s. Error loading relations: %s", testName, err)
	}

	for i, fullStruct := range fullStructs {
		if fullStruct.HasOneID != fullStruct.HasOne.ID {
			t.Errorf(
				"Testing %s. Comparing HasOneID to HasOne.ID for order %v and they are not the same: %v vs %v",
				testName,
				i,
				fullStruct.HasOneID,
				fullStruct.HasOne.ID,
			)
		}
	}

	// 2) Two relations, but repeated
	// Reload
	fullStructs = []testtypes.FullStruct{}
	if n, err := db.Find(&fullStructs).Run(); err != nil || n != 3 {
		t.Errorf("Save/retrieve failed. Err: %v; n: %v", err, n)
	}

	// Convert to records
	records = []tormenta.Record{}
	for i := range fullStructs {
		records = append(records, &fullStructs[i])
	}

	testName = "two relations, one level, repeated"
	if err := tormenta.LoadByID(db, []string{"HasOne", "HasOne"}, records...); err != nil {
		t.Errorf("Testing %s. Error loading relations: %s", testName, err)
	}

	for i, fullStruct := range fullStructs {
		if fullStruct.HasOneID != fullStruct.HasOne.ID {
			t.Errorf(
				"Testing %s. Comparing HasOneID to HasOne.ID for order %v and they are not the same: %v vs %v",
				testName,
				i,
				fullStruct.HasOneID,
				fullStruct.HasOne.ID,
			)
		}
	}

	// 3) Two relations
	// Reload
	fullStructs = []testtypes.FullStruct{}
	if n, err := db.Find(&fullStructs).Run(); err != nil || n != 3 {
		t.Errorf("Save/retrieve failed. Err: %v; n: %v", err, n)
	}

	// Convert to records
	records = []tormenta.Record{}
	for i := range fullStructs {
		records = append(records, &fullStructs[i])
	}

	testName = "two relations, one level, repeated"
	if err := tormenta.LoadByID(db, []string{"HasOne", "HasAnotherOne"}, records...); err != nil {
		t.Errorf("Testing %s. Error loading relations: %s", testName, err)
	}

	for i, fullStruct := range fullStructs {
		if fullStruct.HasOneID != fullStruct.HasOne.ID {
			t.Errorf(
				"Testing %s. Comparing HasOneID to HasOne.ID for order %v and they are not the same: %v vs %v",
				testName,
				i,
				fullStruct.HasOneID,
				fullStruct.HasOne.ID,
			)
		}

		if fullStruct.HasAnotherOneID != fullStruct.HasAnotherOne.ID {
			t.Errorf(
				"Testing %s. Comparing HasAnotherOneID to HasAnotherOne.ID for order %v and they are not the same: %v vs %v",
				testName,
				i,
				fullStruct.HasAnotherOneID,
				fullStruct.HasAnotherOne.ID,
			)
		}
	}

	// 4) Single relation, 2 level nesting
	// Reload
	fullStructs = []testtypes.FullStruct{}
	if n, err := db.Find(&fullStructs).Run(); err != nil || n != 3 {
		t.Errorf("Save/retrieve failed. Err: %v; n: %v", err, n)
	}

	// Convert to records
	records = []tormenta.Record{}
	for i := range fullStructs {
		records = append(records, &fullStructs[i])
	}

	testName = "single, nested relation"
	if err := tormenta.LoadByID(db, []string{"HasOne.Nested"}, records...); err != nil {
		t.Errorf("Testing %s. Error loading relations: %s", testName, err)
	}

	for i, fullStruct := range fullStructs {
		if fullStruct.HasOneID != fullStruct.HasOne.ID {
			t.Errorf(
				"Testing %s. Comparing HasOneID to HasOne.ID for order %v and they are not the same: %v vs %v",
				testName,
				i,
				fullStruct.HasOneID,
				fullStruct.HasOne.ID,
			)
		}

		if fullStruct.HasOne.Nested == nil {
			t.Fatalf("Testing %s - index %v. HasOne.Nested is nil - the relation didn't load", testName, i)
		}

		if fullStruct.HasOne.NestedID != fullStruct.HasOne.Nested.ID {
			t.Errorf(
				"Testing %s. Comparing HasOne.NestedID to HasOne.Nested.ID for order %v and they are not the same: %v vs %v",
				testName,
				i,
				fullStruct.HasOne.NestedID,
				fullStruct.HasOne.Nested.ID,
			)
		}
	}

	// 4) Single relation, 3 level nesting
	// Reload
	fullStructs = []testtypes.FullStruct{}
	if n, err := db.Find(&fullStructs).Run(); err != nil || n != 3 {
		t.Errorf("Save/retrieve failed. Err: %v; n: %v", err, n)
	}

	// Convert to records
	records = []tormenta.Record{}
	for i := range fullStructs {
		records = append(records, &fullStructs[i])
	}

	testName = "single, 3 level nested relation"
	if err := tormenta.LoadByID(db, []string{"HasOne.Nested.Nested"}, records...); err != nil {
		t.Errorf("Testing %s. Error loading relations: %s", testName, err)
	}

	for i, fullStruct := range fullStructs {
		if fullStruct.HasOneID != fullStruct.HasOne.ID {
			t.Errorf(
				"Testing %s. Comparing HasOneID to HasOne.ID for order %v and they are not the same: %v vs %v",
				testName,
				i,
				fullStruct.HasOneID,
				fullStruct.HasOne.ID,
			)
		}

		// L1

		if fullStruct.HasOne.Nested == nil {
			t.Fatalf("Testing %s - index %v. HasOne.Nested is nil - the relation didn't load", testName, i)
		}

		if fullStruct.HasOne.NestedID != fullStruct.HasOne.Nested.ID {
			t.Errorf(
				"Testing %s. Comparing HasOne.NestedID to HasOne.Nested.ID for order %v and they are not the same: %v vs %v",
				testName,
				i,
				fullStruct.HasOne.NestedID,
				fullStruct.HasOne.Nested.ID,
			)
		}

		// L2

		if fullStruct.HasOne.Nested.Nested == nil {
			t.Fatalf("Testing %s - index %v. HasOne.Nested.Nested is nil - the relation didn't load", testName, i)
		}

		if fullStruct.HasOne.Nested.NestedID != fullStruct.HasOne.Nested.Nested.ID {
			t.Errorf(
				"Testing %s. Comparing HasOne.Nested.NestedID to HasOne.Nested.Nested.ID for order %v and they are not the same: %v vs %v",
				testName,
				i,
				fullStruct.HasOne.NestedID,
				fullStruct.HasOne.Nested.ID,
			)
		}
	}

	// 5) Multiple IDs
	// Reload
	fullStructs = []testtypes.FullStruct{}
	if n, err := db.Find(&fullStructs).Run(); err != nil || n != 3 {
		t.Errorf("Save/retrieve failed. Err: %v; n: %v", err, n)
	}

	// Convert to records
	records = []tormenta.Record{}
	for i := range fullStructs {
		records = append(records, &fullStructs[i])
	}

	testName = "multiple ids"
	if err := tormenta.LoadByID(db, []string{"HasMany"}, records...); err != nil {
		t.Fatalf("Testing %s. Error loading relations: %s", testName, err)
	}

	for i, fullStruct := range fullStructs {

		if len(fullStruct.HasMany) == 0 {
			t.Fatalf("Testing %s. No HasMany relations loaded", testName)
		}

		for j, hasManyId := range fullStruct.HasManyIDs {

			if hasManyId != fullStruct.HasMany[j].ID {
				t.Errorf(
					"Testing %s. Comparing each HasManyID to the retrieved record ID for i:%v and they are not the same: %v vs %v",
					testName,
					i,
					hasManyId,
					fullStruct.HasMany[j].ID,
				)
			}
		}

	}
}

func Test_Relations_LoadByID_InvalidID(t *testing.T) {
	// Open the DB
	db, _ := tormenta.OpenTestWithOptions("data/tests", testDBOptions)
	defer db.Close()

	// Save a compleletely blank struct
	myStruct := &testtypes.FullStruct{}
	if _, err := db.Save(myStruct); err != nil {
		t.Errorf("Testing relations load by ID (invalid ID) - could not save: %s", err)
	}

	// Attempt load by ID using the "HasOne" and "HasMany" fields,
	// but since the ids on those fields are going to be blank, there is no relation to get,
	// That should not cause an error or crash
	if err := tormenta.LoadByID(db, []string{"HasOne", "HasMany"}, myStruct); err != nil {
		t.Errorf("Testing relations load by ID (invalid ID) - got error: %s", err)
	}

}

func Test_Relations_LoadByQuery_Basic(t *testing.T) {
	// Open the DB
	db, _ := tormenta.OpenTestWithOptions("data/tests", testDBOptions)
	defer db.Close()

	// Create a full struct
	struct1 := testtypes.FullStruct{}
	if _, err := db.Save(&struct1); err != nil {
		t.Error("Saving error")
	}

	// Some related structs that reference the above full structs
	relatedStruct1 := testtypes.RelatedStruct{
		FullStructID: struct1.ID,
	}
	relatedStruct2 := testtypes.RelatedStruct{
		FullStructID: struct1.ID,
	}
	if _, err := db.Save(&relatedStruct1, &relatedStruct2); err != nil {
		t.Error("Saving error")
	}

	tormenta.LoadByQuery(db, "RelatedStructsByQuery", nil, &struct1)
	if len(struct1.RelatedStructsByQuery) != 2 {
		t.Errorf("Did not get 2 related structs, got %v", len(struct1.RelatedStructsByQuery))
	}
}

func Test_Relations_LoadByQuery_MultipleEntities(t *testing.T) {
	// Open the DB
	db, _ := tormenta.OpenTestWithOptions("data/tests", testDBOptions)
	defer db.Close()

	var fullStructs []testtypes.FullStruct
	for i := 0; i < 2; i++ {
		// Start with a struct
		struct1 := testtypes.FullStruct{}
		if _, err := db.Save(&struct1); err != nil {
			t.Fatal("Saving error")
		}

		// Keep a record of it
		fullStructs = append(fullStructs, struct1)

		// Some related structs that reference the above full structs
		relatedStruct1 := testtypes.RelatedStruct{
			FullStructID: struct1.ID,
		}
		relatedStruct2 := testtypes.RelatedStruct{
			FullStructID: struct1.ID,
		}

		if _, err := db.Save(&relatedStruct1, &relatedStruct2); err != nil {
			t.Fatal("Saving error")
		}
	}

	var records []tormenta.Record
	for i := range fullStructs {
		records = append(records, &fullStructs[i])
	}

	tormenta.LoadByQuery(db, "RelatedStructsByQuery", nil, records...)

	for _, f := range fullStructs {
		if len(f.RelatedStructsByQuery) != 2 {
			t.Errorf("Expected 2 related structs, got %v", len(f.RelatedStructsByQuery))
		}
	}

}

func Test_Relations_LoadByQuery_Additional_Clauses(t *testing.T) {
	// Open the DB
	db, _ := tormenta.OpenTestWithOptions("data/tests", testDBOptions)
	defer db.Close()

	// Create a full struct
	struct1 := testtypes.FullStruct{}
	if _, err := db.Save(&struct1); err != nil {
		t.Error("Saving error")
	}

	// Some related structs that reference the above full structs
	relatedStruct1 := testtypes.RelatedStruct{
		FullStructID:    struct1.ID,
		StructBoolField: false,
	}
	relatedStruct2 := testtypes.RelatedStruct{
		FullStructID:    struct1.ID,
		StructBoolField: true,
	}

	if _, err := db.Save(&relatedStruct1, &relatedStruct2); err != nil {
		t.Error("Saving error")
	}

	conditions := func(q *tormenta.Query) *tormenta.Query {
		q.Match("StructBoolField", true)
		return q
	}

	tormenta.LoadByQuery(db, "RelatedStructsByQuery", conditions, &struct1)

	if len(struct1.RelatedStructsByQuery) != 1 {
		t.Error("Did not get 1 related structs")
	}
}