package dctx

import (
	"encoding/xml"
	"os"
	"testing"

	"github.com/stretchr/testify/assert"

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

func TestWriter_WriteSchema(t *testing.T) {
	// 1. Create a sample schema
	schema := models.InitSchema("public")
	schema.Name = "TestDB"

	// Table 1: users
	usersTable := models.InitTable("users", "public")
	usersTable.Comment = "Stores user information"
	idCol := models.InitColumn("id", "users", "public")
	idCol.Type = "serial"
	idCol.IsPrimaryKey = true
	usersTable.Columns["id"] = idCol
	nameCol := models.InitColumn("name", "users", "public")
	nameCol.Type = "varchar"
	nameCol.Length = 100
	usersTable.Columns["name"] = nameCol
	pkIndex := models.InitIndex("users_pkey", "users", "public")
	pkIndex.Unique = true
	pkIndex.Columns = []string{"id"}
	usersTable.Indexes["users_pkey"] = pkIndex
	schema.Tables = append(schema.Tables, usersTable)

	// Table 2: posts
	postsTable := models.InitTable("posts", "public")
	postsTable.Comment = "Stores blog posts"
	postIDCol := models.InitColumn("id", "posts", "public")
	postIDCol.Type = "serial"
	postIDCol.IsPrimaryKey = true
	postsTable.Columns["id"] = postIDCol
	titleCol := models.InitColumn("title", "posts", "public")
	titleCol.Type = "varchar"
	titleCol.Length = 255
	postsTable.Columns["title"] = titleCol
	userIDCol := models.InitColumn("user_id", "posts", "public")
	userIDCol.Type = "integer"
	postsTable.Columns["user_id"] = userIDCol
	postsPKIndex := models.InitIndex("posts_pkey", "posts", "public")
	postsPKIndex.Unique = true
	postsPKIndex.Columns = []string{"id"}
	postsTable.Indexes["posts_pkey"] = postsPKIndex

	fkIndex := models.InitIndex("posts_user_id_idx", "posts", "public")
	fkIndex.Columns = []string{"user_id"}
	postsTable.Indexes["posts_user_id_idx"] = fkIndex
	schema.Tables = append(schema.Tables, postsTable)

	// Constraint for the relationship
	fkConstraint := models.InitConstraint("fk_posts_users", models.ForeignKeyConstraint)
	fkConstraint.Table = "posts"
	fkConstraint.Schema = "public"
	fkConstraint.Columns = []string{"user_id"}
	fkConstraint.ReferencedTable = "users"
	fkConstraint.ReferencedSchema = "public"
	fkConstraint.ReferencedColumns = []string{"id"}
	postsTable.Constraints["fk_posts_users"] = fkConstraint

	// Relation
	relation := models.InitRelation("fk_posts_users", "public")
	relation.FromTable = "posts"
	relation.ToTable = "users"
	relation.ForeignKey = "fk_posts_users"
	schema.Relations = append(schema.Relations, relation)

	// 2. Setup writer
	outputPath := "/tmp/test.dctx"
	opts := &writers.WriterOptions{
		OutputPath: outputPath,
	}
	writer := NewWriter(opts)

	// 3. Write the schema
	err := writer.WriteSchema(schema)
	assert.NoError(t, err)

	// 4. Read the file and unmarshal it
	actualBytes, err := os.ReadFile(outputPath)
	assert.NoError(t, err)

	var dctx models.DCTXDictionary
	err = xml.Unmarshal(actualBytes, &dctx)
	assert.NoError(t, err)

	// 5. Assert properties of the unmarshaled struct
	assert.Equal(t, "TestDB", dctx.Name)
	assert.Equal(t, "1", dctx.Version)
	assert.Len(t, dctx.Tables, 2)
	assert.Len(t, dctx.Relations, 1)

	// Assert users table
	usersTableResult := dctx.Tables[0]
	assert.Equal(t, "users", usersTableResult.Name)
	assert.Len(t, usersTableResult.Fields, 2)
	assert.Len(t, usersTableResult.Keys, 1)
	userPK := usersTableResult.Keys[0]
	assert.True(t, userPK.Primary)
	assert.Equal(t, "users_pkey", userPK.Name)
	assert.Len(t, userPK.Components, 1)
	userPKComponent := userPK.Components[0]
	assert.NotEmpty(t, userPKComponent.FieldId)

	// Assert posts table
	postsTableResult := dctx.Tables[1]
	assert.Equal(t, "posts", postsTableResult.Name)
	assert.Len(t, postsTableResult.Fields, 3)
	assert.Len(t, postsTableResult.Keys, 2)
	postsFK := postsTableResult.Keys[1] // Assuming order
	assert.False(t, postsFK.Primary)
	assert.Equal(t, "posts_user_id_idx", postsFK.Name)
	assert.Len(t, postsFK.Components, 1)
	postsFKComponent := postsFK.Components[0]
	assert.NotEmpty(t, postsFKComponent.FieldId)

	// Assert relation
	relationResult := dctx.Relations[0]
	// PrimaryTable and ForeignTable should be GUIDs in DCTX format
	assert.NotEmpty(t, relationResult.PrimaryTable, "PrimaryTable should have a GUID")
	assert.NotEmpty(t, relationResult.ForeignTable, "ForeignTable should have a GUID")
	assert.NotEmpty(t, relationResult.PrimaryKey)
	assert.NotEmpty(t, relationResult.ForeignKey)

	// Check if the table GUIDs match
	assert.Equal(t, usersTableResult.Guid, relationResult.PrimaryTable, "PrimaryTable GUID should match users table")
	assert.Equal(t, postsTableResult.Guid, relationResult.ForeignTable, "ForeignTable GUID should match posts table")

	// Check if the key GUIDs match up
	assert.Equal(t, userPK.Guid, relationResult.PrimaryKey)
	assert.Equal(t, postsFK.Guid, relationResult.ForeignKey)

	// Verify field mappings exist
	assert.NotEmpty(t, relationResult.ForeignMappings, "Relation should have ForeignMappings")
	assert.NotEmpty(t, relationResult.PrimaryMappings, "Relation should have PrimaryMappings")

	// ForeignMapping should reference primary table (users) fields
	assert.Len(t, relationResult.ForeignMappings, 1)
	assert.NotEmpty(t, relationResult.ForeignMappings[0].Field)

	// PrimaryMapping should reference foreign table (posts) fields
	assert.Len(t, relationResult.PrimaryMappings, 1)
	assert.NotEmpty(t, relationResult.PrimaryMappings[0].Field)
}
