package graphql

import (
	"strings"
	"testing"

	"git.warky.dev/wdevs/relspecgo/pkg/models"
	"git.warky.dev/wdevs/relspecgo/pkg/writers"
)

func TestWriter_WriteTable_Simple(t *testing.T) {
	table := models.InitTable("User", "public")

	idCol := models.InitColumn("id", "User", "public")
	idCol.Type = "bigint"
	idCol.IsPrimaryKey = true
	idCol.NotNull = true
	table.Columns["id"] = idCol

	nameCol := models.InitColumn("name", "User", "public")
	nameCol.Type = "text"
	nameCol.NotNull = true
	table.Columns["name"] = nameCol

	emailCol := models.InitColumn("email", "User", "public")
	emailCol.Type = "text"
	emailCol.NotNull = false
	table.Columns["email"] = emailCol

	opts := &writers.WriterOptions{
		OutputPath: "",
	}

	writer := NewWriter(opts)
	schema := models.InitSchema("public")
	schema.Tables = []*models.Table{table}
	db := models.InitDatabase("test")
	db.Schemas = []*models.Schema{schema}

	output := writer.databaseToGraphQL(db)

	// Verify output contains type definition
	if !strings.Contains(output, "type User {") {
		t.Error("Expected 'type User {' in output")
	}

	// Verify fields
	if !strings.Contains(output, "id: ID!") {
		t.Error("Expected 'id: ID!' in output")
	}

	if !strings.Contains(output, "name: String!") {
		t.Error("Expected 'name: String!' in output")
	}

	if !strings.Contains(output, "email: String") {
		t.Error("Expected 'email: String' in output")
	}

	// Ensure email is not followed by ! (nullable)
	if strings.Contains(output, "email: String!") {
		t.Error("Did not expect 'email: String!' (should be nullable)")
	}
}

func TestWriter_WriteDatabase_WithEnum(t *testing.T) {
	schema := models.InitSchema("public")

	// Create enum
	roleEnum := &models.Enum{
		Name:   "Role",
		Schema: "public",
		Values: []string{"ADMIN", "USER", "GUEST"},
	}
	schema.Enums = []*models.Enum{roleEnum}

	// Create table with enum field
	table := models.InitTable("User", "public")

	idCol := models.InitColumn("id", "User", "public")
	idCol.Type = "bigint"
	idCol.IsPrimaryKey = true
	idCol.NotNull = true
	table.Columns["id"] = idCol

	roleCol := models.InitColumn("role", "User", "public")
	roleCol.Type = "Role"
	roleCol.NotNull = true
	table.Columns["role"] = roleCol

	schema.Tables = []*models.Table{table}

	db := models.InitDatabase("test")
	db.Schemas = []*models.Schema{schema}

	opts := &writers.WriterOptions{}
	writer := NewWriter(opts)

	output := writer.databaseToGraphQL(db)

	// Verify enum definition
	if !strings.Contains(output, "enum Role {") {
		t.Error("Expected 'enum Role {' in output")
	}

	if !strings.Contains(output, "ADMIN") {
		t.Error("Expected 'ADMIN' enum value in output")
	}

	// Verify enum usage in type
	if !strings.Contains(output, "role: Role!") {
		t.Error("Expected 'role: Role!' in output")
	}
}

func TestWriter_WriteDatabase_WithRelations(t *testing.T) {
	schema := models.InitSchema("public")

	// Create User table
	userTable := models.InitTable("User", "public")
	userIdCol := models.InitColumn("id", "User", "public")
	userIdCol.Type = "bigint"
	userIdCol.IsPrimaryKey = true
	userIdCol.NotNull = true
	userTable.Columns["id"] = userIdCol

	userNameCol := models.InitColumn("name", "User", "public")
	userNameCol.Type = "text"
	userNameCol.NotNull = true
	userTable.Columns["name"] = userNameCol

	// Create Post table with FK to User
	postTable := models.InitTable("Post", "public")

	postIdCol := models.InitColumn("id", "Post", "public")
	postIdCol.Type = "bigint"
	postIdCol.IsPrimaryKey = true
	postIdCol.NotNull = true
	postTable.Columns["id"] = postIdCol

	titleCol := models.InitColumn("title", "Post", "public")
	titleCol.Type = "text"
	titleCol.NotNull = true
	postTable.Columns["title"] = titleCol

	authorIdCol := models.InitColumn("authorId", "Post", "public")
	authorIdCol.Type = "bigint"
	authorIdCol.NotNull = true
	postTable.Columns["authorId"] = authorIdCol

	// Add FK constraint
	fkConstraint := models.InitConstraint("fk_post_author", models.ForeignKeyConstraint)
	fkConstraint.Schema = "public"
	fkConstraint.Table = "Post"
	fkConstraint.Columns = []string{"authorId"}
	fkConstraint.ReferencedSchema = "public"
	fkConstraint.ReferencedTable = "User"
	fkConstraint.ReferencedColumns = []string{"id"}
	postTable.Constraints["fk_post_author"] = fkConstraint

	schema.Tables = []*models.Table{userTable, postTable}

	db := models.InitDatabase("test")
	db.Schemas = []*models.Schema{schema}

	opts := &writers.WriterOptions{}
	writer := NewWriter(opts)

	output := writer.databaseToGraphQL(db)

	// Verify Post has author field (forward relationship)
	if !strings.Contains(output, "author: User!") {
		t.Error("Expected 'author: User!' in Post type")
	}

	// Verify authorId FK column is NOT in the output
	if strings.Contains(output, "authorId:") {
		t.Error("Did not expect 'authorId:' field in output (FK columns should be hidden)")
	}

	// Verify User has posts field (reverse relationship)
	if !strings.Contains(output, "posts: [Post!]!") {
		t.Error("Expected 'posts: [Post!]!' in User type")
	}
}

func TestWriter_WriteDatabase_CustomScalars(t *testing.T) {
	schema := models.InitSchema("public")

	table := models.InitTable("Event", "public")

	idCol := models.InitColumn("id", "Event", "public")
	idCol.Type = "bigint"
	idCol.IsPrimaryKey = true
	idCol.NotNull = true
	table.Columns["id"] = idCol

	createdAtCol := models.InitColumn("createdAt", "Event", "public")
	createdAtCol.Type = "timestamp"
	createdAtCol.NotNull = true
	table.Columns["createdAt"] = createdAtCol

	metadataCol := models.InitColumn("metadata", "Event", "public")
	metadataCol.Type = "jsonb"
	metadataCol.NotNull = false
	table.Columns["metadata"] = metadataCol

	dateCol := models.InitColumn("eventDate", "Event", "public")
	dateCol.Type = "date"
	dateCol.NotNull = false
	table.Columns["eventDate"] = dateCol

	schema.Tables = []*models.Table{table}

	db := models.InitDatabase("test")
	db.Schemas = []*models.Schema{schema}

	opts := &writers.WriterOptions{}
	writer := NewWriter(opts)

	output := writer.databaseToGraphQL(db)

	// Verify scalar declarations
	if !strings.Contains(output, "scalar DateTime") {
		t.Error("Expected 'scalar DateTime' declaration")
	}

	if !strings.Contains(output, "scalar JSON") {
		t.Error("Expected 'scalar JSON' declaration")
	}

	if !strings.Contains(output, "scalar Date") {
		t.Error("Expected 'scalar Date' declaration")
	}

	// Verify field types
	if !strings.Contains(output, "createdAt: DateTime!") {
		t.Error("Expected 'createdAt: DateTime!' in output")
	}

	if !strings.Contains(output, "metadata: JSON") {
		t.Error("Expected 'metadata: JSON' in output")
	}

	if !strings.Contains(output, "eventDate: Date") {
		t.Error("Expected 'eventDate: Date' in output")
	}
}

func TestWriter_WriteDatabase_ManyToMany(t *testing.T) {
	schema := models.InitSchema("public")

	// Create Post table
	postTable := models.InitTable("Post", "public")
	postIdCol := models.InitColumn("id", "Post", "public")
	postIdCol.Type = "bigint"
	postIdCol.IsPrimaryKey = true
	postIdCol.NotNull = true
	postTable.Columns["id"] = postIdCol

	titleCol := models.InitColumn("title", "Post", "public")
	titleCol.Type = "text"
	titleCol.NotNull = true
	postTable.Columns["title"] = titleCol

	// Create Tag table
	tagTable := models.InitTable("Tag", "public")
	tagIdCol := models.InitColumn("id", "Tag", "public")
	tagIdCol.Type = "bigint"
	tagIdCol.IsPrimaryKey = true
	tagIdCol.NotNull = true
	tagTable.Columns["id"] = tagIdCol

	nameCol := models.InitColumn("name", "Tag", "public")
	nameCol.Type = "text"
	nameCol.NotNull = true
	tagTable.Columns["name"] = nameCol

	// Create PostTag join table
	joinTable := models.InitTable("PostTag", "public")

	postIdJoinCol := models.InitColumn("postId", "PostTag", "public")
	postIdJoinCol.Type = "bigint"
	postIdJoinCol.NotNull = true
	postIdJoinCol.IsPrimaryKey = true
	joinTable.Columns["postId"] = postIdJoinCol

	tagIdJoinCol := models.InitColumn("tagId", "PostTag", "public")
	tagIdJoinCol.Type = "bigint"
	tagIdJoinCol.NotNull = true
	tagIdJoinCol.IsPrimaryKey = true
	joinTable.Columns["tagId"] = tagIdJoinCol

	// Add composite PK constraint
	pkConstraint := models.InitConstraint("pk_posttag", models.PrimaryKeyConstraint)
	pkConstraint.Schema = "public"
	pkConstraint.Table = "PostTag"
	pkConstraint.Columns = []string{"postId", "tagId"}
	joinTable.Constraints["pk_posttag"] = pkConstraint

	// Add FK to Post
	fk1 := models.InitConstraint("fk_posttag_post", models.ForeignKeyConstraint)
	fk1.Schema = "public"
	fk1.Table = "PostTag"
	fk1.Columns = []string{"postId"}
	fk1.ReferencedSchema = "public"
	fk1.ReferencedTable = "Post"
	fk1.ReferencedColumns = []string{"id"}
	joinTable.Constraints["fk_posttag_post"] = fk1

	// Add FK to Tag
	fk2 := models.InitConstraint("fk_posttag_tag", models.ForeignKeyConstraint)
	fk2.Schema = "public"
	fk2.Table = "PostTag"
	fk2.Columns = []string{"tagId"}
	fk2.ReferencedSchema = "public"
	fk2.ReferencedTable = "Tag"
	fk2.ReferencedColumns = []string{"id"}
	joinTable.Constraints["fk_posttag_tag"] = fk2

	schema.Tables = []*models.Table{postTable, tagTable, joinTable}

	db := models.InitDatabase("test")
	db.Schemas = []*models.Schema{schema}

	opts := &writers.WriterOptions{}
	writer := NewWriter(opts)

	output := writer.databaseToGraphQL(db)

	// Verify join table is NOT in output
	if strings.Contains(output, "type PostTag") {
		t.Error("Did not expect 'type PostTag' (join tables should be hidden)")
	}

	// Verify Post has tags field
	if !strings.Contains(output, "tags: [Tag!]!") {
		t.Error("Expected 'tags: [Tag!]!' in Post type")
	}

	// Verify Tag has posts field
	if !strings.Contains(output, "posts: [Post!]!") {
		t.Error("Expected 'posts: [Post!]!' in Tag type")
	}
}

func TestWriter_WriteDatabase_UUIDType(t *testing.T) {
	schema := models.InitSchema("public")

	table := models.InitTable("User", "public")

	idCol := models.InitColumn("id", "User", "public")
	idCol.Type = "uuid"
	idCol.IsPrimaryKey = true
	idCol.NotNull = true
	table.Columns["id"] = idCol

	schema.Tables = []*models.Table{table}

	db := models.InitDatabase("test")
	db.Schemas = []*models.Schema{schema}

	opts := &writers.WriterOptions{}
	writer := NewWriter(opts)

	output := writer.databaseToGraphQL(db)

	// UUID primary keys should still map to ID
	if !strings.Contains(output, "id: ID!") {
		t.Error("Expected 'id: ID!' for UUID primary key")
	}
}

func TestWriter_Metadata_NoScalarDeclarations(t *testing.T) {
	schema := models.InitSchema("public")

	table := models.InitTable("Event", "public")

	idCol := models.InitColumn("id", "Event", "public")
	idCol.Type = "bigint"
	idCol.IsPrimaryKey = true
	table.Columns["id"] = idCol

	createdAtCol := models.InitColumn("createdAt", "Event", "public")
	createdAtCol.Type = "timestamp"
	createdAtCol.NotNull = true
	table.Columns["createdAt"] = createdAtCol

	schema.Tables = []*models.Table{table}

	db := models.InitDatabase("test")
	db.Schemas = []*models.Schema{schema}

	opts := &writers.WriterOptions{
		Metadata: map[string]any{
			"includeScalarDeclarations": false,
		},
	}
	writer := NewWriter(opts)

	output := writer.databaseToGraphQL(db)

	// Verify no scalar declarations
	if strings.Contains(output, "scalar DateTime") {
		t.Error("Did not expect 'scalar DateTime' with includeScalarDeclarations=false")
	}

	// But field should still use DateTime
	if !strings.Contains(output, "createdAt: DateTime!") {
		t.Error("Expected 'createdAt: DateTime!' in output")
	}
}
