package schema_test

import (
	"sync"
	"testing"
	"time"

	"gorm.io/gorm"
	"gorm.io/gorm/schema"
	"gorm.io/gorm/utils/tests"
)

func checkStructRelation(t *testing.T, data interface{}, relations ...Relation) {
	if s, err := schema.Parse(data, &sync.Map{}, schema.NamingStrategy{}); err != nil {
		t.Errorf("Failed to parse schema, got error %v", err)
	} else {
		for _, rel := range relations {
			checkSchemaRelation(t, s, rel)
		}
	}
}

func TestBelongsToOverrideForeignKey(t *testing.T) {
	type Profile struct {
		gorm.Model
		Name string
	}

	type User struct {
		gorm.Model
		Profile      Profile `gorm:"ForeignKey:ProfileRefer"`
		ProfileRefer int
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Profile", Type: schema.BelongsTo, Schema: "User", FieldSchema: "Profile",
		References: []Reference{{"ID", "Profile", "ProfileRefer", "User", "", false}},
	})
}

func TestBelongsToOverrideReferences(t *testing.T) {
	type Profile struct {
		gorm.Model
		Refer string
		Name  string
	}

	type User struct {
		gorm.Model
		Profile   Profile `gorm:"ForeignKey:ProfileID;References:Refer"`
		ProfileID int
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Profile", Type: schema.BelongsTo, Schema: "User", FieldSchema: "Profile",
		References: []Reference{{"Refer", "Profile", "ProfileID", "User", "", false}},
	})
}

func TestBelongsToWithOnlyReferences(t *testing.T) {
	type Profile struct {
		gorm.Model
		Refer string
		Name  string
	}

	type User struct {
		gorm.Model
		Profile      Profile `gorm:"References:Refer"`
		ProfileRefer int
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Profile", Type: schema.BelongsTo, Schema: "User", FieldSchema: "Profile",
		References: []Reference{{"Refer", "Profile", "ProfileRefer", "User", "", false}},
	})
}

func TestBelongsToWithOnlyReferences2(t *testing.T) {
	type Profile struct {
		gorm.Model
		Refer string
		Name  string
	}

	type User struct {
		gorm.Model
		Profile   Profile `gorm:"References:Refer"`
		ProfileID int
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Profile", Type: schema.BelongsTo, Schema: "User", FieldSchema: "Profile",
		References: []Reference{{"Refer", "Profile", "ProfileID", "User", "", false}},
	})
}

func TestSelfReferentialBelongsTo(t *testing.T) {
	type User struct {
		ID        int32 `gorm:"primaryKey"`
		Name      string
		CreatorID *int32
		Creator   *User
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Creator", Type: schema.BelongsTo, Schema: "User", FieldSchema: "User",
		References: []Reference{{"ID", "User", "CreatorID", "User", "", false}},
	})
}

func TestSelfReferentialBelongsToOverrideReferences(t *testing.T) {
	type User struct {
		ID        int32 `gorm:"primaryKey"`
		Name      string
		CreatedBy *int32
		Creator   *User `gorm:"foreignKey:CreatedBy;references:ID"`
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Creator", Type: schema.BelongsTo, Schema: "User", FieldSchema: "User",
		References: []Reference{{"ID", "User", "CreatedBy", "User", "", false}},
	})
}

func TestBelongsToWithMixin(t *testing.T) {
	type Profile struct {
		gorm.Model
		Refer string
		Name  string
	}

	type ProfileMixin struct {
		Profile      Profile `gorm:"References:Refer"`
		ProfileRefer int
	}

	type User struct {
		gorm.Model
		ProfileMixin
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Profile", Type: schema.BelongsTo, Schema: "User", FieldSchema: "Profile",
		References: []Reference{{"Refer", "Profile", "ProfileRefer", "User", "", false}},
	})
}

func TestHasOneOverrideForeignKey(t *testing.T) {
	type Profile struct {
		gorm.Model
		Name      string
		UserRefer uint
	}

	type User struct {
		gorm.Model
		Profile Profile `gorm:"ForeignKey:UserRefer"`
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Profile", Type: schema.HasOne, Schema: "User", FieldSchema: "Profile",
		References: []Reference{{"ID", "User", "UserRefer", "Profile", "", true}},
	})
}

func TestHasOneOverrideReferences(t *testing.T) {
	type Profile struct {
		gorm.Model
		Name   string
		UserID uint
	}

	type User struct {
		gorm.Model
		Refer   string
		Profile Profile `gorm:"ForeignKey:UserID;References:Refer"`
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Profile", Type: schema.HasOne, Schema: "User", FieldSchema: "Profile",
		References: []Reference{{"Refer", "User", "UserID", "Profile", "", true}},
	})
}

func TestHasOneOverrideReferences2(t *testing.T) {
	type Profile struct {
		gorm.Model
		Name string
	}

	type User struct {
		gorm.Model
		ProfileID uint     `gorm:"column:profile_id"`
		Profile   *Profile `gorm:"foreignKey:ID;references:ProfileID"`
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Profile", Type: schema.HasOne, Schema: "User", FieldSchema: "Profile",
		References: []Reference{{"ProfileID", "User", "ID", "Profile", "", true}},
	})
}

func TestHasOneWithOnlyReferences(t *testing.T) {
	type Profile struct {
		gorm.Model
		Name      string
		UserRefer uint
	}

	type User struct {
		gorm.Model
		Refer   string
		Profile Profile `gorm:"References:Refer"`
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Profile", Type: schema.HasOne, Schema: "User", FieldSchema: "Profile",
		References: []Reference{{"Refer", "User", "UserRefer", "Profile", "", true}},
	})
}

func TestHasOneWithOnlyReferences2(t *testing.T) {
	type Profile struct {
		gorm.Model
		Name   string
		UserID uint
	}

	type User struct {
		gorm.Model
		Refer   string
		Profile Profile `gorm:"References:Refer"`
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Profile", Type: schema.HasOne, Schema: "User", FieldSchema: "Profile",
		References: []Reference{{"Refer", "User", "UserID", "Profile", "", true}},
	})
}

func TestHasManyOverrideForeignKey(t *testing.T) {
	type Profile struct {
		gorm.Model
		Name      string
		UserRefer uint
	}

	type User struct {
		gorm.Model
		Profile []Profile `gorm:"ForeignKey:UserRefer"`
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Profile", Type: schema.HasMany, Schema: "User", FieldSchema: "Profile",
		References: []Reference{{"ID", "User", "UserRefer", "Profile", "", true}},
	})
}

func TestHasManyOverrideReferences(t *testing.T) {
	type Profile struct {
		gorm.Model
		Name   string
		UserID uint
	}

	type User struct {
		gorm.Model
		Refer   string
		Profile []Profile `gorm:"ForeignKey:UserID;References:Refer"`
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Profile", Type: schema.HasMany, Schema: "User", FieldSchema: "Profile",
		References: []Reference{{"Refer", "User", "UserID", "Profile", "", true}},
	})
}

func TestMany2ManyOverrideForeignKeyAndReferences(t *testing.T) {
	type Profile struct {
		gorm.Model
		Name      string
		UserRefer uint
	}

	type User struct {
		gorm.Model
		Profiles  []Profile `gorm:"many2many:user_profiles;ForeignKey:Refer;JoinForeignKey:UserReferID;References:UserRefer;JoinReferences:ProfileRefer"`
		Profiles2 []Profile `gorm:"many2many:user_profiles2;ForeignKey:refer;JoinForeignKey:user_refer_id;References:user_refer;JoinReferences:profile_refer"`
		Refer     uint
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Profiles", Type: schema.Many2Many, Schema: "User", FieldSchema: "Profile",
		JoinTable: JoinTable{Name: "user_profiles", Table: "user_profiles"},
		References: []Reference{
			{"Refer", "User", "UserReferID", "user_profiles", "", true},
			{"UserRefer", "Profile", "ProfileRefer", "user_profiles", "", false},
		},
	}, Relation{
		Name: "Profiles2", Type: schema.Many2Many, Schema: "User", FieldSchema: "Profile",
		JoinTable: JoinTable{Name: "user_profiles2", Table: "user_profiles2"},
		References: []Reference{
			{"Refer", "User", "User_refer_id", "user_profiles2", "", true},
			{"UserRefer", "Profile", "Profile_refer", "user_profiles2", "", false},
		},
	})
}

func TestMany2ManyOverrideForeignKey(t *testing.T) {
	type Profile struct {
		gorm.Model
		Name      string
		UserRefer uint
	}

	type User struct {
		gorm.Model
		Profiles []Profile `gorm:"many2many:user_profiles;ForeignKey:Refer;References:UserRefer"`
		Refer    uint
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Profiles", Type: schema.Many2Many, Schema: "User", FieldSchema: "Profile",
		JoinTable: JoinTable{Name: "user_profiles", Table: "user_profiles"},
		References: []Reference{
			{"Refer", "User", "UserRefer", "user_profiles", "", true},
			{"UserRefer", "Profile", "ProfileUserRefer", "user_profiles", "", false},
		},
	})
}

func TestMany2ManySharedForeignKey(t *testing.T) {
	type Profile struct {
		gorm.Model
		Name         string
		Kind         string
		ProfileRefer uint
	}

	type User struct {
		gorm.Model
		Profiles []Profile `gorm:"many2many:user_profiles;foreignKey:Refer,Kind;joinForeignKey:UserRefer,Kind;References:ProfileRefer,Kind;joinReferences:ProfileR,Kind"`
		Kind     string
		Refer    uint
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Profiles", Type: schema.Many2Many, Schema: "User", FieldSchema: "Profile",
		JoinTable: JoinTable{Name: "user_profiles", Table: "user_profiles"},
		References: []Reference{
			{"Refer", "User", "UserRefer", "user_profiles", "", true},
			{"Kind", "User", "Kind", "user_profiles", "", true},
			{"ProfileRefer", "Profile", "ProfileR", "user_profiles", "", false},
			{"Kind", "Profile", "Kind", "user_profiles", "", false},
		},
	})
}

func TestMany2ManyOverrideJoinForeignKey(t *testing.T) {
	type Profile struct {
		gorm.Model
		Name      string
		UserRefer uint
	}

	type User struct {
		gorm.Model
		Profiles []Profile `gorm:"many2many:user_profile;JoinForeignKey:UserReferID;JoinReferences:ProfileRefer"`
		Refer    uint
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Profiles", Type: schema.Many2Many, Schema: "User", FieldSchema: "Profile",
		JoinTable: JoinTable{Name: "user_profile", Table: "user_profile"},
		References: []Reference{
			{"ID", "User", "UserReferID", "user_profile", "", true},
			{"ID", "Profile", "ProfileRefer", "user_profile", "", false},
		},
	})
}

func TestBuildReadonlyMany2ManyRelation(t *testing.T) {
	type Profile struct {
		gorm.Model
		Name      string
		UserRefer uint
	}

	type User struct {
		gorm.Model
		Profiles []Profile `gorm:"->;many2many:user_profile;JoinForeignKey:UserReferID;JoinReferences:ProfileRefer"`
		Refer    uint
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Profiles", Type: schema.Many2Many, Schema: "User", FieldSchema: "Profile",
		JoinTable: JoinTable{Name: "user_profile", Table: "user_profile"},
		References: []Reference{
			{"ID", "User", "UserReferID", "user_profile", "", true},
			{"ID", "Profile", "ProfileRefer", "user_profile", "", false},
		},
	})
}

func TestMany2ManyWithMultiPrimaryKeys(t *testing.T) {
	type Tag struct {
		ID     uint   `gorm:"primary_key"`
		Locale string `gorm:"primary_key"`
		Value  string
	}

	type Blog struct {
		ID         uint   `gorm:"primary_key"`
		Locale     string `gorm:"primary_key"`
		Subject    string
		Body       string
		Tags       []Tag `gorm:"many2many:blog_tags;"`
		SharedTags []Tag `gorm:"many2many:shared_blog_tags;ForeignKey:id;References:id"`
		LocaleTags []Tag `gorm:"many2many:locale_blog_tags;ForeignKey:id,locale;References:id"`
	}

	checkStructRelation(t, &Blog{},
		Relation{
			Name: "Tags", Type: schema.Many2Many, Schema: "Blog", FieldSchema: "Tag",
			JoinTable: JoinTable{Name: "blog_tags", Table: "blog_tags"},
			References: []Reference{
				{"ID", "Blog", "BlogID", "blog_tags", "", true},
				{"Locale", "Blog", "BlogLocale", "blog_tags", "", true},
				{"ID", "Tag", "TagID", "blog_tags", "", false},
				{"Locale", "Tag", "TagLocale", "blog_tags", "", false},
			},
		},
		Relation{
			Name: "SharedTags", Type: schema.Many2Many, Schema: "Blog", FieldSchema: "Tag",
			JoinTable: JoinTable{Name: "shared_blog_tags", Table: "shared_blog_tags"},
			References: []Reference{
				{"ID", "Blog", "BlogID", "shared_blog_tags", "", true},
				{"ID", "Tag", "TagID", "shared_blog_tags", "", false},
			},
		},
		Relation{
			Name: "LocaleTags", Type: schema.Many2Many, Schema: "Blog", FieldSchema: "Tag",
			JoinTable: JoinTable{Name: "locale_blog_tags", Table: "locale_blog_tags"},
			References: []Reference{
				{"ID", "Blog", "BlogID", "locale_blog_tags", "", true},
				{"Locale", "Blog", "BlogLocale", "locale_blog_tags", "", true},
				{"ID", "Tag", "TagID", "locale_blog_tags", "", false},
			},
		},
	)
}

func TestMultipleMany2Many(t *testing.T) {
	type Thing struct {
		ID int
	}

	type Person struct {
		ID       int
		Likes    []Thing `gorm:"many2many:likes"`
		Dislikes []Thing `gorm:"many2many:dislikes"`
	}

	checkStructRelation(t, &Person{},
		Relation{
			Name: "Likes", Type: schema.Many2Many, Schema: "Person", FieldSchema: "Thing",
			JoinTable: JoinTable{Name: "likes", Table: "likes"},
			References: []Reference{
				{"ID", "Person", "PersonID", "likes", "", true},
				{"ID", "Thing", "ThingID", "likes", "", false},
			},
		},
		Relation{
			Name: "Dislikes", Type: schema.Many2Many, Schema: "Person", FieldSchema: "Thing",
			JoinTable: JoinTable{Name: "dislikes", Table: "dislikes"},
			References: []Reference{
				{"ID", "Person", "PersonID", "dislikes", "", true},
				{"ID", "Thing", "ThingID", "dislikes", "", false},
			},
		},
	)
}

func TestSelfReferentialMany2Many(t *testing.T) {
	type User struct {
		ID         int32 `gorm:"primaryKey"`
		Name       string
		CreatedBy  int32
		Creators   []User      `gorm:"foreignKey:CreatedBy"`
		AnotherPro interface{} `gorm:"-"`
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Creators", Type: schema.HasMany, Schema: "User", FieldSchema: "User",
		References: []Reference{{"ID", "User", "CreatedBy", "User", "", true}},
	})

	user, err := schema.Parse(&User{}, &sync.Map{}, schema.NamingStrategy{})
	if err != nil {
		t.Fatalf("failed to parse schema")
	}

	relSchema := user.Relationships.Relations["Creators"].FieldSchema
	if user != relSchema {
		t.Fatalf("schema should be same, expects %p but got %p", user, relSchema)
	}
}

type CreatedByModel struct {
	CreatedByID uint
	CreatedBy   *CreatedUser
}

type CreatedUser struct {
	gorm.Model
	CreatedByModel
}

func TestEmbeddedRelation(t *testing.T) {
	checkStructRelation(t, &CreatedUser{}, Relation{
		Name: "CreatedBy", Type: schema.BelongsTo, Schema: "CreatedUser", FieldSchema: "CreatedUser",
		References: []Reference{
			{"ID", "CreatedUser", "CreatedByID", "CreatedUser", "", false},
		},
	})

	userSchema, err := schema.Parse(&CreatedUser{}, &sync.Map{}, schema.NamingStrategy{})
	if err != nil {
		t.Fatalf("failed to parse schema, got error %v", err)
	}

	if len(userSchema.Relationships.Relations) != 1 {
		t.Fatalf("expects 1 relations, but got %v", len(userSchema.Relationships.Relations))
	}

	if createdByRel, ok := userSchema.Relationships.Relations["CreatedBy"]; ok {
		if createdByRel.FieldSchema != userSchema {
			t.Fatalf("expects same field schema, but got new %p, old %p", createdByRel.FieldSchema, userSchema)
		}
	} else {
		t.Fatalf("expects created by relations, but not found")
	}
}

func TestEmbeddedHas(t *testing.T) {
	type Toy struct {
		ID        int
		Name      string
		OwnerID   int
		OwnerType string
	}
	type User struct {
		ID  int
		Cat struct {
			Name string
			Toy  Toy   `gorm:"polymorphic:Owner;"`
			Toys []Toy `gorm:"polymorphic:Owner;"`
		} `gorm:"embedded;embeddedPrefix:cat_"`
		Dog struct {
			ID     int
			Name   string
			UserID int
			Toy    Toy   `gorm:"polymorphic:Owner;"`
			Toys   []Toy `gorm:"polymorphic:Owner;"`
		}
		Toys []Toy `gorm:"polymorphic:Owner;"`
	}

	s, err := schema.Parse(&User{}, &sync.Map{}, schema.NamingStrategy{})
	if err != nil {
		t.Fatalf("Failed to parse schema, got error %v", err)
	}

	checkEmbeddedRelations(t, s.Relationships.EmbeddedRelations, map[string]EmbeddedRelations{
		"Cat": {
			Relations: map[string]Relation{
				"Toy": {
					Name:        "Toy",
					Type:        schema.HasOne,
					Schema:      "User",
					FieldSchema: "Toy",
					Polymorphic: Polymorphic{ID: "OwnerID", Type: "OwnerType", Value: "users"},
					References: []Reference{
						{ForeignKey: "OwnerType", ForeignSchema: "Toy", PrimaryValue: "users"},
						{ForeignKey: "OwnerType", ForeignSchema: "Toy", PrimaryValue: "users"},
					},
				},
				"Toys": {
					Name:        "Toys",
					Type:        schema.HasMany,
					Schema:      "User",
					FieldSchema: "Toy",
					Polymorphic: Polymorphic{ID: "OwnerID", Type: "OwnerType", Value: "users"},
					References: []Reference{
						{ForeignKey: "OwnerType", ForeignSchema: "Toy", PrimaryValue: "users"},
						{ForeignKey: "OwnerType", ForeignSchema: "Toy", PrimaryValue: "users"},
					},
				},
			},
		},
	})
}

func TestPolymorphic(t *testing.T) {
	t.Run("has one", func(t *testing.T) {
		type Toy struct {
			ID        int
			Name      string
			OwnerID   int
			OwnerType string
		}

		type Cat struct {
			ID   int
			Name string
			Toy  Toy `gorm:"polymorphic:Owner;"`
		}

		s, err := schema.Parse(&Cat{}, &sync.Map{}, schema.NamingStrategy{})
		if err != nil {
			t.Fatalf("Failed to parse schema, got error %v", err)
		}

		checkEmbeddedRelations(t, s.Relationships.EmbeddedRelations, map[string]EmbeddedRelations{
			"Cat": {
				Relations: map[string]Relation{
					"Toy": {
						Name:        "Toy",
						Type:        schema.HasOne,
						Schema:      "User",
						FieldSchema: "Toy",
						Polymorphic: Polymorphic{ID: "OwnerID", Type: "OwnerType", Value: "users"},
						References: []Reference{
							{ForeignKey: "OwnerType", ForeignSchema: "Toy", PrimaryValue: "users"},
						},
					},
				},
			},
		})
	})

	t.Run("has one with custom polymorphic type and id", func(t *testing.T) {
		type Toy struct {
			ID    int
			Name  string
			RefId int
			Type  string
		}

		type Cat struct {
			ID   int
			Name string
			Toy  Toy `gorm:"polymorphic:Owner;polymorphicType:Type;polymorphicId:RefId"`
		}

		s, err := schema.Parse(&Cat{}, &sync.Map{}, schema.NamingStrategy{})
		if err != nil {
			t.Fatalf("Failed to parse schema, got error %v", err)
		}

		checkEmbeddedRelations(t, s.Relationships.EmbeddedRelations, map[string]EmbeddedRelations{
			"Cat": {
				Relations: map[string]Relation{
					"Toy": {
						Name:        "Toy",
						Type:        schema.HasOne,
						Schema:      "User",
						FieldSchema: "Toy",
						Polymorphic: Polymorphic{ID: "ref_id", Type: "Type", Value: "users"},
						References: []Reference{
							{ForeignKey: "Type", ForeignSchema: "Toy", PrimaryValue: "users"},
						},
					},
				},
			},
		})
	})

	t.Run("has one with only polymorphic type", func(t *testing.T) {
		type Toy struct {
			ID      int
			Name    string
			OwnerID int
			Type    string
		}

		type Cat struct {
			ID   int
			Name string
			Toy  Toy `gorm:"polymorphic:Owner;polymorphicType:Type"`
		}

		s, err := schema.Parse(&Cat{}, &sync.Map{}, schema.NamingStrategy{})
		if err != nil {
			t.Fatalf("Failed to parse schema, got error %v", err)
		}

		checkEmbeddedRelations(t, s.Relationships.EmbeddedRelations, map[string]EmbeddedRelations{
			"Cat": {
				Relations: map[string]Relation{
					"Toy": {
						Name:        "Toy",
						Type:        schema.HasOne,
						Schema:      "User",
						FieldSchema: "Toy",
						Polymorphic: Polymorphic{ID: "owner_id", Type: "Type", Value: "users"},
						References: []Reference{
							{ForeignKey: "Type", ForeignSchema: "Toy", PrimaryValue: "users"},
						},
					},
				},
			},
		})
	})

	t.Run("has many", func(t *testing.T) {
		type Toy struct {
			ID        int
			Name      string
			OwnerID   int
			OwnerType string
		}

		type Cat struct {
			ID   int
			Name string
			Toys []Toy `gorm:"polymorphic:Owner;"`
		}

		s, err := schema.Parse(&Cat{}, &sync.Map{}, schema.NamingStrategy{})
		if err != nil {
			t.Fatalf("Failed to parse schema, got error %v", err)
		}

		checkEmbeddedRelations(t, s.Relationships.EmbeddedRelations, map[string]EmbeddedRelations{
			"Cat": {
				Relations: map[string]Relation{
					"Toys": {
						Name:        "Toys",
						Type:        schema.HasMany,
						Schema:      "User",
						FieldSchema: "Toy",
						Polymorphic: Polymorphic{ID: "OwnerID", Type: "OwnerType", Value: "users"},
						References: []Reference{
							{ForeignKey: "OwnerType", ForeignSchema: "Toy", PrimaryValue: "users"},
						},
					},
				},
			},
		})
	})

	t.Run("has many with custom polymorphic type and id", func(t *testing.T) {
		type Toy struct {
			ID    int
			Name  string
			RefId int
			Type  string
		}

		type Cat struct {
			ID   int
			Name string
			Toys []Toy `gorm:"polymorphicType:Type;polymorphicId:RefId"`
		}

		s, err := schema.Parse(&Cat{}, &sync.Map{}, schema.NamingStrategy{})
		if err != nil {
			t.Fatalf("Failed to parse schema, got error %v", err)
		}

		checkEmbeddedRelations(t, s.Relationships.EmbeddedRelations, map[string]EmbeddedRelations{
			"Cat": {
				Relations: map[string]Relation{
					"Toys": {
						Name:        "Toys",
						Type:        schema.HasMany,
						Schema:      "User",
						FieldSchema: "Toy",
						Polymorphic: Polymorphic{ID: "ref_id", Type: "Type", Value: "users"},
						References: []Reference{
							{ForeignKey: "Type", ForeignSchema: "Toy", PrimaryValue: "users"},
						},
					},
				},
			},
		})
	})
}

func TestEmbeddedBelongsTo(t *testing.T) {
	type Country struct {
		ID   int `gorm:"primaryKey"`
		Name string
	}
	type Address struct {
		CountryID int
		Country   Country
	}
	type NestedAddress struct {
		Address
	}
	type CountryMixin struct {
		CountryID int
		Country   Country
	}
	type Org struct {
		ID              int
		PostalAddress   Address `gorm:"embedded;embeddedPrefix:postal_address_"`
		VisitingAddress Address `gorm:"embedded;embeddedPrefix:visiting_address_"`
		AddressID       int
		Address         struct {
			ID int
			Address
		}
		NestedAddress *NestedAddress `gorm:"embedded;embeddedPrefix:nested_address_"`
		CountryMixin
	}

	s, err := schema.Parse(&Org{}, &sync.Map{}, schema.NamingStrategy{})
	if err != nil {
		t.Errorf("Failed to parse schema, got error %v", err)
	}

	checkEmbeddedRelations(t, s.Relationships.EmbeddedRelations, map[string]EmbeddedRelations{
		"PostalAddress": {
			Relations: map[string]Relation{
				"Country": {
					Name: "Country", Type: schema.BelongsTo, Schema: "Org", FieldSchema: "Country",
					References: []Reference{
						{PrimaryKey: "ID", PrimarySchema: "Country", ForeignKey: "CountryID", ForeignSchema: "Org"},
					},
				},
			},
		},
		"VisitingAddress": {
			Relations: map[string]Relation{
				"Country": {
					Name: "Country", Type: schema.BelongsTo, Schema: "Org", FieldSchema: "Country",
					References: []Reference{
						{PrimaryKey: "ID", PrimarySchema: "Country", ForeignKey: "CountryID", ForeignSchema: "Org"},
					},
				},
			},
		},
		"NestedAddress": {
			Relations: map[string]Relation{
				"Country": {
					Name: "Country", Type: schema.BelongsTo, Schema: "Org", FieldSchema: "Country",
					References: []Reference{
						{PrimaryKey: "ID", PrimarySchema: "Country", ForeignKey: "CountryID", ForeignSchema: "Org"},
					},
				},
			},
		},
	})
}

func TestVariableRelation(t *testing.T) {
	var result struct {
		User
	}

	checkStructRelation(t, &result, Relation{
		Name: "Account", Type: schema.HasOne, Schema: "", FieldSchema: "Account",
		References: []Reference{
			{"ID", "", "UserID", "Account", "", true},
		},
	})

	checkStructRelation(t, &result, Relation{
		Name: "Company", Type: schema.BelongsTo, Schema: "", FieldSchema: "Company",
		References: []Reference{
			{"ID", "Company", "CompanyID", "", "", false},
		},
	})
}

func TestSameForeignKey(t *testing.T) {
	type UserAux struct {
		gorm.Model
		Aux  string
		UUID string
	}

	type User struct {
		gorm.Model
		Name string
		UUID string
		Aux  *UserAux `gorm:"foreignkey:UUID;references:UUID"`
	}

	checkStructRelation(t, &User{},
		Relation{
			Name: "Aux", Type: schema.HasOne, Schema: "User", FieldSchema: "UserAux",
			References: []Reference{
				{"UUID", "User", "UUID", "UserAux", "", true},
			},
		},
	)
}

func TestBelongsToSameForeignKey(t *testing.T) {
	type User struct {
		gorm.Model
		Name string
		UUID string
	}

	type UserAux struct {
		gorm.Model
		Aux  string
		UUID string
		User User `gorm:"ForeignKey:UUID;references:UUID;belongsTo"`
	}

	checkStructRelation(t, &UserAux{},
		Relation{
			Name: "User", Type: schema.BelongsTo, Schema: "UserAux", FieldSchema: "User",
			References: []Reference{
				{"UUID", "User", "UUID", "UserAux", "", false},
			},
		},
	)
}

func TestHasOneWithSameForeignKey(t *testing.T) {
	type Profile struct {
		gorm.Model
		Name         string
		ProfileRefer int // not used in relationship
	}

	type User struct {
		gorm.Model
		Profile      Profile `gorm:"ForeignKey:ID;references:ProfileRefer"`
		ProfileRefer int
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Profile", Type: schema.HasOne, Schema: "User", FieldSchema: "Profile",
		References: []Reference{{"ProfileRefer", "User", "ID", "Profile", "", true}},
	})
}

func TestHasManySameForeignKey(t *testing.T) {
	type Profile struct {
		gorm.Model
		Name      string
		UserRefer uint
	}

	type User struct {
		gorm.Model
		UserRefer uint
		Profile   []Profile `gorm:"ForeignKey:UserRefer"`
	}

	checkStructRelation(t, &User{}, Relation{
		Name: "Profile", Type: schema.HasMany, Schema: "User", FieldSchema: "Profile",
		References: []Reference{{"ID", "User", "UserRefer", "Profile", "", true}},
	})
}

type Author struct {
	gorm.Model
}

type Book struct {
	gorm.Model
	Author   Author
	AuthorID uint
}

func (Book) TableName() string {
	return "my_schema.a_very_very_very_very_very_very_very_very_long_table_name"
}

func TestParseConstraintNameWithSchemaQualifiedLongTableName(t *testing.T) {
	s, err := schema.Parse(
		&Book{},
		&sync.Map{},
		schema.NamingStrategy{IdentifierMaxLength: 64},
	)
	if err != nil {
		t.Fatalf("Failed to parse schema")
	}

	expectedConstraintName := "fk_my_schema_a_very_very_very_very_very_very_very_very_l4db13eec"
	constraint := s.Relationships.Relations["Author"].ParseConstraint()

	if constraint.Name != expectedConstraintName {
		t.Fatalf(
			"expected constraint name %s, got %s",
			expectedConstraintName,
			constraint.Name,
		)
	}
}

type InfoRelation struct {
	ID    int
	Code  string
	Info1 []*Info1 `gorm:"foreignkey:Code;references:Code"`
	Info2 []*Info2 `gorm:"foreignkey:Code;references:Code"`
}

type Info1 struct {
	CreatedAt time.Time
	UpdatedAt time.Time
	Code      string
	Relation  []*InfoRelation `gorm:"foreignkey:Code;references:Code"`
}

type Info2 struct {
	CreatedAt time.Time
	UpdatedAt time.Time
	Code      string
	Relation  []*InfoRelation `gorm:"foreignkey:Code;references:Code"`
}

func TestDataRace(t *testing.T) {
	syncMap := &sync.Map{}
	for i := 0; i < 10; i++ {
		go func() {
			schema.Parse(&Info1{}, syncMap, schema.NamingStrategy{IdentifierMaxLength: 64})
		}()

		go func() {
			schema.Parse(&Info2{}, syncMap, schema.NamingStrategy{IdentifierMaxLength: 64})
		}()

		go func() {
			var result User
			schema.Parse(&result, syncMap, schema.NamingStrategy{IdentifierMaxLength: 64})
		}()
		go func() {
			var result tests.Account
			schema.Parse(&result, syncMap, schema.NamingStrategy{IdentifierMaxLength: 64})
		}()
	}
}
