package schema_test

import (
	"reflect"
	"sync"
	"testing"

	"gorm.io/gorm/schema"
)

type UserIndex struct {
	Name         string `gorm:"index"`
	Name2        string `gorm:"index:idx_name,unique"`
	Name3        string `gorm:"index:,sort:desc,collate:utf8,type:btree,length:10,where:name3 != 'jinzhu'"`
	Name4        string `gorm:"uniqueIndex"`
	Name5        int64  `gorm:"index:,class:FULLTEXT,comment:hello \\, world,where:age > 10"`
	Name6        int64  `gorm:"index:profile,comment:hello \\, world,where:age > 10"`
	Age          int64  `gorm:"index:profile,expression:ABS(age),option:WITH PARSER parser_name"`
	OID          int64  `gorm:"index:idx_id;index:idx_oid,unique"`
	MemberNumber string `gorm:"index:idx_id,priority:1"`
	Name7        string `gorm:"index:type"`

	// Composite Index: Flattened structure.
	Data0A string `gorm:"index:,composite:comp_id0"`
	Data0B string `gorm:"index:,composite:comp_id0"`

	// Composite Index: Nested structure.
	Data1A string `gorm:"index:,composite:comp_id1"`
	CompIdxLevel1C

	// Composite Index: Unique and priority.
	Data2A string `gorm:"index:,unique,composite:comp_id2,priority:2"`
	CompIdxLevel2C
}

type CompIdxLevel1C struct {
	CompIdxLevel1B
	Data1C string `gorm:"index:,composite:comp_id1"`
}

type CompIdxLevel1B struct {
	Data1B string `gorm:"index:,composite:comp_id1"`
}

type CompIdxLevel2C struct {
	CompIdxLevel2B
	Data2C string `gorm:"index:,unique,composite:comp_id2,priority:1"`
}

type CompIdxLevel2B struct {
	Data2B string `gorm:"index:,unique,composite:comp_id2,priority:3"`
}

func TestParseIndex(t *testing.T) {
	user, err := schema.Parse(&UserIndex{}, &sync.Map{}, schema.NamingStrategy{})
	if err != nil {
		t.Fatalf("failed to parse user index, got error %v", err)
	}

	results := map[string]schema.Index{
		"idx_user_indices_name": {
			Name:   "idx_user_indices_name",
			Fields: []schema.IndexOption{{Field: &schema.Field{Name: "Name"}}},
		},
		"idx_name": {
			Name:   "idx_name",
			Class:  "UNIQUE",
			Fields: []schema.IndexOption{{Field: &schema.Field{Name: "Name2"}}},
		},
		"idx_user_indices_name3": {
			Name:  "idx_user_indices_name3",
			Type:  "btree",
			Where: "name3 != 'jinzhu'",
			Fields: []schema.IndexOption{{
				Field:   &schema.Field{Name: "Name3"},
				Sort:    "desc",
				Collate: "utf8",
				Length:  10,
			}},
		},
		"idx_user_indices_name4": {
			Name:   "idx_user_indices_name4",
			Class:  "UNIQUE",
			Fields: []schema.IndexOption{{Field: &schema.Field{Name: "Name4"}}},
		},
		"idx_user_indices_name5": {
			Name:    "idx_user_indices_name5",
			Class:   "FULLTEXT",
			Comment: "hello , world",
			Where:   "age > 10",
			Fields:  []schema.IndexOption{{Field: &schema.Field{Name: "Name5"}}},
		},
		"profile": {
			Name:    "profile",
			Comment: "hello , world",
			Where:   "age > 10",
			Option:  "WITH PARSER parser_name",
			Fields: []schema.IndexOption{{Field: &schema.Field{Name: "Name6"}}, {
				Field:      &schema.Field{Name: "Age"},
				Expression: "ABS(age)",
			}},
		},
		"idx_id": {
			Name:   "idx_id",
			Fields: []schema.IndexOption{{Field: &schema.Field{Name: "MemberNumber"}}, {Field: &schema.Field{Name: "OID"}}},
		},
		"idx_oid": {
			Name:   "idx_oid",
			Class:  "UNIQUE",
			Fields: []schema.IndexOption{{Field: &schema.Field{Name: "OID"}}},
		},
		"type": {
			Name:   "type",
			Type:   "",
			Fields: []schema.IndexOption{{Field: &schema.Field{Name: "Name7"}}},
		},
		"idx_user_indices_comp_id0": {
			Name: "idx_user_indices_comp_id0",
			Type: "",
			Fields: []schema.IndexOption{{
				Field: &schema.Field{Name: "Data0A"},
			}, {
				Field: &schema.Field{Name: "Data0B"},
			}},
		},
		"idx_user_indices_comp_id1": {
			Name: "idx_user_indices_comp_id1",
			Fields: []schema.IndexOption{{
				Field: &schema.Field{Name: "Data1A"},
			}, {
				Field: &schema.Field{Name: "Data1B"},
			}, {
				Field: &schema.Field{Name: "Data1C"},
			}},
		},
		"idx_user_indices_comp_id2": {
			Name:  "idx_user_indices_comp_id2",
			Class: "UNIQUE",
			Fields: []schema.IndexOption{{
				Field: &schema.Field{Name: "Data2C"},
			}, {
				Field: &schema.Field{Name: "Data2A"},
			}, {
				Field: &schema.Field{Name: "Data2B"},
			}},
		},
	}

	indices := user.ParseIndexes()

	for k, result := range results {
		v, ok := indices[k]
		if !ok {
			t.Fatalf("Failed to found index %v from parsed indices %+v", k, indices)
		}

		for _, name := range []string{"Name", "Class", "Type", "Where", "Comment", "Option"} {
			if reflect.ValueOf(result).FieldByName(name).Interface() != reflect.ValueOf(v).FieldByName(name).Interface() {
				t.Errorf(
					"index %v %v should equal, expects %v, got %v",
					k, name, reflect.ValueOf(result).FieldByName(name).Interface(), reflect.ValueOf(v).FieldByName(name).Interface(),
				)
			}
		}

		for idx, ef := range result.Fields {
			rf := v.Fields[idx]
			if rf.Field.Name != ef.Field.Name {
				t.Fatalf("index field should equal, expects %v, got %v", rf.Field.Name, ef.Field.Name)
			}

			for _, name := range []string{"Expression", "Sort", "Collate", "Length"} {
				if reflect.ValueOf(ef).FieldByName(name).Interface() != reflect.ValueOf(rf).FieldByName(name).Interface() {
					t.Errorf(
						"index %v field #%v's %v should equal, expects %v, got %v", k, idx+1, name,
						reflect.ValueOf(ef).FieldByName(name).Interface(), reflect.ValueOf(rf).FieldByName(name).Interface(),
					)
				}
			}
		}
	}
}