package backup_test

import (
	"database/sql"
	"fmt"

	"github.com/apache/cloudberry-backup/backup"
	"github.com/apache/cloudberry-backup/testutils"
	"github.com/apache/cloudberry-backup/toc"

	. "github.com/onsi/ginkgo/v2"
)

var _ = Describe("backup/predata_types tests", func() {
	emptyMetadata := backup.ObjectMetadata{}
	emptyMetadataMap := backup.MetadataMap{}
	typeMetadata := testutils.DefaultMetadata(toc.OBJ_TYPE, false, true, true, true)
	typeMetadataMap := testutils.DefaultMetadataMap(toc.OBJ_TYPE, false, true, true, true)

	BeforeEach(func() {
		tocfile, backupfile = testutils.InitializeTestTOC(buffer, "predata")
	})
	Describe("PrintCreateEnumTypeStatements", func() {
		enumOne := backup.EnumType{Oid: 1, Schema: "public", Name: "enum_type", EnumLabels: "'bar',\n\t'baz',\n\t'foo'"}
		enumTwo := backup.EnumType{Oid: 1, Schema: "public", Name: "enum_type", EnumLabels: "'bar',\n\t'baz',\n\t'foo'"}

		It("prints an enum type with multiple attributes", func() {
			backup.PrintCreateEnumTypeStatements(backupfile, tocfile, []backup.EnumType{enumOne}, emptyMetadataMap)
			testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "enum_type", toc.OBJ_TYPE)
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE TYPE public.enum_type AS ENUM (
	'bar',
	'baz',
	'foo'
);`)
		})
		It("prints an enum type with comment, security label, and owner", func() {
			backup.PrintCreateEnumTypeStatements(backupfile, tocfile, []backup.EnumType{enumTwo}, typeMetadataMap)
			expectedStatements := []string{`CREATE TYPE public.enum_type AS ENUM (
	'bar',
	'baz',
	'foo'
);`,
				"COMMENT ON TYPE public.enum_type IS 'This is a type comment.';",
				"ALTER TYPE public.enum_type OWNER TO testrole;",
				"SECURITY LABEL FOR dummy ON TYPE public.enum_type IS 'unclassified';"}
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, expectedStatements...)
		})
	})
	Describe("PrintCreateCompositeTypeStatement", func() {
		var compType backup.CompositeType
		var oneAtt, oneAttWithCollation, twoAtts, attWithComment []backup.Attribute
		BeforeEach(func() {
			compType = backup.CompositeType{Oid: 1, Schema: "public", Name: "composite_type"}
			oneAtt = []backup.Attribute{{Name: "foo", Type: "integer"}}
			oneAttWithCollation = []backup.Attribute{{Name: "foo", Type: "integer", Collation: "public.some_coll"}}
			twoAtts = []backup.Attribute{{Name: "foo", Type: "integer"}, {Name: "bar", Type: "text"}}
			attWithComment = []backup.Attribute{{Name: "foo", Type: "integer", Comment: "'attribute comment'"}}
		})

		It("prints a composite type with one attribute", func() {
			compType.Attributes = oneAtt
			backup.PrintCreateCompositeTypeStatement(backupfile, tocfile, compType, emptyMetadata)
			testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "composite_type", toc.OBJ_TYPE)
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE TYPE public.composite_type AS (
	foo integer
);`)
		})
		It("prints a composite type with one attribute with a collation", func() {
			compType.Attributes = oneAttWithCollation
			backup.PrintCreateCompositeTypeStatement(backupfile, tocfile, compType, emptyMetadata)
			testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "composite_type", toc.OBJ_TYPE)
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE TYPE public.composite_type AS (
	foo integer COLLATE public.some_coll
);`)
		})
		It("prints a composite type with multiple attributes", func() {
			compType.Attributes = twoAtts
			backup.PrintCreateCompositeTypeStatement(backupfile, tocfile, compType, emptyMetadata)
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE TYPE public.composite_type AS (
	foo integer,
	bar text
);`)
		})
		It("prints a composite type with comment, security label, and owner", func() {
			compType.Attributes = twoAtts
			backup.PrintCreateCompositeTypeStatement(backupfile, tocfile, compType, typeMetadata)
			expectedEntries := []string{`CREATE TYPE public.composite_type AS (
	foo integer,
	bar text
);`,
				"COMMENT ON TYPE public.composite_type IS 'This is a type comment.';",
				"ALTER TYPE public.composite_type OWNER TO testrole;",
				"SECURITY LABEL FOR dummy ON TYPE public.composite_type IS 'unclassified';"}
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, expectedEntries...)
		})
		It("prints a composite type with attribute comment", func() {
			compType.Attributes = attWithComment
			backup.PrintCreateCompositeTypeStatement(backupfile, tocfile, compType, emptyMetadata)
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE TYPE public.composite_type AS (
	foo integer
);`, "COMMENT ON COLUMN public.composite_type.foo IS 'attribute comment';")
		})
	})
	Describe("PrintCreateBaseTypeStatement", func() {
		baseSimple := backup.BaseType{Oid: 1, Schema: "public", Name: "base_type", Input: "input_fn", Output: "output_fn", Receive: "", Send: "", ModIn: "", ModOut: "", InternalLength: -1, IsPassedByValue: false, Alignment: "c", Storage: "p", DefaultVal: "", Element: "", Category: "U", Preferred: false, Delimiter: ""}
		basePartial := backup.BaseType{Oid: 1, Schema: "public", Name: "base_type", Input: "input_fn", Output: "output_fn", Receive: "receive_fn", Send: "send_fn", ModIn: "modin_fn", ModOut: "modout_fn", InternalLength: -1, IsPassedByValue: false, Alignment: "c", Storage: "p", DefaultVal: "42", Element: "int4", Category: "U", Delimiter: ","}
		baseFull := backup.BaseType{Oid: 1, Schema: "public", Name: "base_type", Input: "input_fn", Output: "output_fn", Receive: "receive_fn", Send: "send_fn", ModIn: "modin_fn", ModOut: "modout_fn", InternalLength: 16, IsPassedByValue: true, Alignment: "s", Storage: "e", DefaultVal: "42", Element: "int4", Category: "N", Preferred: true, Delimiter: ",", StorageOptions: "compresstype=zlib, compresslevel=1, blocksize=32768", Collatable: true}
		basePermOne := backup.BaseType{Oid: 1, Schema: "public", Name: "base_type", Input: "input_fn", Output: "output_fn", Receive: "", Send: "", ModIn: "", ModOut: "", InternalLength: -1, IsPassedByValue: false, Alignment: "d", Storage: "m", DefaultVal: "", Element: "", Category: "U", Delimiter: ""}
		basePermTwo := backup.BaseType{Oid: 1, Schema: "public", Name: "base_type", Input: "input_fn", Output: "output_fn", Receive: "", Send: "", ModIn: "", ModOut: "", InternalLength: -1, IsPassedByValue: false, Alignment: "i", Storage: "x", DefaultVal: "", Element: "", Category: "U", Delimiter: ""}

		It("prints a base type with no optional arguments", func() {
			backup.PrintCreateBaseTypeStatement(backupfile, tocfile, baseSimple, emptyMetadata)
			testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "base_type", toc.OBJ_TYPE)
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE TYPE public.base_type (
	INPUT = input_fn,
	OUTPUT = output_fn
);`)
		})
		It("prints a base type where all optional arguments have default values where possible", func() {
			backup.PrintCreateBaseTypeStatement(backupfile, tocfile, basePartial, emptyMetadata)
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, fmt.Sprintf(`CREATE TYPE public.base_type (
	INPUT = input_fn,
	OUTPUT = output_fn,
	RECEIVE = receive_fn,
	SEND = send_fn,
	TYPMOD_IN = modin_fn,
	TYPMOD_OUT = modout_fn,
	DEFAULT = '42',
	ELEMENT = int4,
	DELIMITER = ','
);`))
		})
		It("prints a base type with all optional arguments provided", func() {
			backup.PrintCreateBaseTypeStatement(backupfile, tocfile, baseFull, emptyMetadata)
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, fmt.Sprintf(`CREATE TYPE public.base_type (
	INPUT = input_fn,
	OUTPUT = output_fn,
	RECEIVE = receive_fn,
	SEND = send_fn,
	TYPMOD_IN = modin_fn,
	TYPMOD_OUT = modout_fn,
	INTERNALLENGTH = 16,
	PASSEDBYVALUE,
	ALIGNMENT = int2,
	STORAGE = external,
	DEFAULT = '42',
	ELEMENT = int4,
	DELIMITER = ',',
	CATEGORY = 'N',
	PREFERRED = true,
	COLLATABLE = true
);

ALTER TYPE public.base_type
	SET DEFAULT ENCODING (compresstype=zlib, compresslevel=1, blocksize=32768);`))
		})
		It("prints a base type with double alignment and main storage", func() {
			backup.PrintCreateBaseTypeStatement(backupfile, tocfile, basePermOne, emptyMetadata)
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE TYPE public.base_type (
	INPUT = input_fn,
	OUTPUT = output_fn,
	ALIGNMENT = double,
	STORAGE = main
);`)
		})
		It("prints a base type with int4 alignment and external storage", func() {
			backup.PrintCreateBaseTypeStatement(backupfile, tocfile, basePermTwo, emptyMetadata)
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE TYPE public.base_type (
	INPUT = input_fn,
	OUTPUT = output_fn,
	ALIGNMENT = int4,
	STORAGE = extended
);`)
		})
		It("prints a base type with comment, security label, and owner", func() {
			backup.PrintCreateBaseTypeStatement(backupfile, tocfile, baseSimple, typeMetadata)
			expectedEntries := []string{`CREATE TYPE public.base_type (
	INPUT = input_fn,
	OUTPUT = output_fn
);`,
				"COMMENT ON TYPE public.base_type IS 'This is a type comment.';",
				"ALTER TYPE public.base_type OWNER TO testrole;",
				"SECURITY LABEL FOR dummy ON TYPE public.base_type IS 'unclassified';"}
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, expectedEntries...)
		})
	})
	Describe("PrintCreateShellTypeStatements", func() {
		shellOne := backup.ShellType{Oid: 1, Schema: "public", Name: "shell_type1"}
		baseOne := backup.BaseType{Oid: 1, Schema: "public", Name: "base_type1", Input: "input_fn", Output: "output_fn", Receive: "", Send: "", ModIn: "", ModOut: "", InternalLength: -1, IsPassedByValue: false, Alignment: "c", Storage: "p", DefaultVal: "", Element: "", Category: "U", Delimiter: ""}
		baseTwo := backup.BaseType{Oid: 1, Schema: "public", Name: "base_type2", Input: "input_fn", Output: "output_fn", Receive: "", Send: "", ModIn: "", ModOut: "", InternalLength: -1, IsPassedByValue: false, Alignment: "c", Storage: "p", DefaultVal: "", Element: "", Category: "U", Delimiter: ""}
		rangeOne := backup.RangeType{Oid: 1, Schema: "public", Name: "range_type1"}
		It("prints shell type for a shell type", func() {
			backup.PrintCreateShellTypeStatements(backupfile, tocfile, []backup.ShellType{shellOne}, []backup.BaseType{}, []backup.RangeType{})
			testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "shell_type1", toc.OBJ_TYPE)
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, "CREATE TYPE public.shell_type1;")
		})
		It("prints shell type for a base type", func() {
			backup.PrintCreateShellTypeStatements(backupfile, tocfile, []backup.ShellType{}, []backup.BaseType{baseOne, baseTwo}, []backup.RangeType{})
			testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "base_type1", toc.OBJ_TYPE)
			testutils.ExpectEntry(tocfile.PredataEntries, 1, "public", "", "base_type2", toc.OBJ_TYPE)
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, "CREATE TYPE public.base_type1;", "CREATE TYPE public.base_type2;")
		})
		It("prints shell type for a range type", func() {
			backup.PrintCreateShellTypeStatements(backupfile, tocfile, []backup.ShellType{}, []backup.BaseType{}, []backup.RangeType{rangeOne})
			testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "range_type1", toc.OBJ_TYPE)
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, "CREATE TYPE public.range_type1;")
		})
	})
	Describe("PrintCreateDomainStatement", func() {
		emptyConstraint := make([]backup.Constraint, 0)
		checkConstraint := []backup.Constraint{{Name: "domain1_check", Def: sql.NullString{String: "CHECK (VALUE > 2)", Valid: true}, OwningObject: "public.domain1"}}
		domainOne := backup.Domain{Oid: 1, Schema: "public", Name: "domain1", DefaultVal: "4", BaseType: "numeric", NotNull: true, Collation: "public.mycollation"}
		domainTwo := backup.Domain{Oid: 1, Schema: "public", Name: "domain2", DefaultVal: "", BaseType: "varchar", NotNull: false, Collation: ""}
		It("prints a domain with a constraint", func() {
			backup.PrintCreateDomainStatement(backupfile, tocfile, domainOne, emptyMetadata, checkConstraint)
			testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "domain1", toc.OBJ_DOMAIN)
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE DOMAIN public.domain1 AS numeric DEFAULT 4 COLLATE public.mycollation NOT NULL
	CONSTRAINT domain1_check CHECK (VALUE > 2);`)
		})
		It("prints a domain without constraint", func() {
			backup.PrintCreateDomainStatement(backupfile, tocfile, domainOne, emptyMetadata, emptyConstraint)
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE DOMAIN public.domain1 AS numeric DEFAULT 4 COLLATE public.mycollation NOT NULL;`)
		})
		It("prints a domain without constraint with comment, security label, and owner", func() {
			domainMetadata := testutils.DefaultMetadata(toc.OBJ_DOMAIN, false, true, true, true)
			backup.PrintCreateDomainStatement(backupfile, tocfile, domainTwo, domainMetadata, emptyConstraint)
			expectedEntries := []string{"CREATE DOMAIN public.domain2 AS varchar;",
				"COMMENT ON DOMAIN public.domain2 IS 'This is a domain comment.';",
				"ALTER DOMAIN public.domain2 OWNER TO testrole;",
				"SECURITY LABEL FOR dummy ON DOMAIN public.domain2 IS 'unclassified';"}
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, expectedEntries...)
		})
	})
	Describe("PrintCreateRangeTypeStatement", func() {
		basicRangeType := backup.RangeType{
			Name:    "rangetype",
			Schema:  "public",
			SubType: "test_subtype_schema.test_subtype",
		}
		complexRangeType := backup.RangeType{
			Name: "rangetype", Schema: "public",
			SubType:        "test_subtype_schema.test_subtype",
			SubTypeOpClass: "opclass_schema.test_opclass",
			Collation:      "collation_schema.test_collation",
			Canonical:      "canonical_schema.test_canonical",
			SubTypeDiff:    "diff_schema.test_diff",
		}
		It("prints a basic range type", func() {
			backup.PrintCreateRangeTypeStatement(backupfile, tocfile, basicRangeType, emptyMetadata)
			testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "rangetype", toc.OBJ_TYPE)
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE TYPE public.rangetype AS RANGE (
	SUBTYPE = test_subtype_schema.test_subtype
);`)
		})
		It("prints a complex range type", func() {
			backup.PrintCreateRangeTypeStatement(backupfile, tocfile, complexRangeType, emptyMetadata)
			testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "rangetype", toc.OBJ_TYPE)
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE TYPE public.rangetype AS RANGE (
	SUBTYPE = test_subtype_schema.test_subtype,
	SUBTYPE_OPCLASS = opclass_schema.test_opclass,
	COLLATION = collation_schema.test_collation,
	CANONICAL = canonical_schema.test_canonical,
	SUBTYPE_DIFF = diff_schema.test_diff
);`)
		})
		It("prints a range type with an owner, security label, and a comment", func() {
			backup.PrintCreateRangeTypeStatement(backupfile, tocfile, basicRangeType, typeMetadata)
			expectedStatements := []string{`CREATE TYPE public.rangetype AS RANGE (
	SUBTYPE = test_subtype_schema.test_subtype
);`,
				"COMMENT ON TYPE public.rangetype IS 'This is a type comment.';",
				"ALTER TYPE public.rangetype OWNER TO testrole;",
				"SECURITY LABEL FOR dummy ON TYPE public.rangetype IS 'unclassified';"}
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, expectedStatements...)
		})
	})
	Describe("PrintCreateCollationStatement", func() {
		It("prints a create collation statement", func() {
			collation := backup.Collation{Oid: 1, Name: "collation1", Collate: "collate1", Ctype: "ctype1", Schema: "schema1"}
			backup.PrintCreateCollationStatements(backupfile, tocfile, []backup.Collation{collation}, emptyMetadataMap)
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE COLLATION schema1.collation1 (LC_COLLATE = 'collate1', LC_CTYPE = 'ctype1');`)
		})
		It("prints a create collation statement with owner and comment", func() {
			collation := backup.Collation{Oid: 1, Name: "collation1", Collate: "collate1", Ctype: "ctype1", Schema: "schema1"}
			collationMetadataMap := testutils.DefaultMetadataMap(toc.OBJ_COLLATION, false, true, true, false)
			backup.PrintCreateCollationStatements(backupfile, tocfile, []backup.Collation{collation}, collationMetadataMap)
			expectedStatements := []string{
				"CREATE COLLATION schema1.collation1 (LC_COLLATE = 'collate1', LC_CTYPE = 'ctype1');",
				"COMMENT ON COLLATION schema1.collation1 IS 'This is a collation comment.';",
				"ALTER COLLATION schema1.collation1 OWNER TO testrole;"}
			testutils.AssertBufferContents(tocfile.PredataEntries, buffer, expectedStatements...)
		})
	})
})
