package integration

import (
	"database/sql"

	"github.com/apache/cloudberry-backup/backup"
	"github.com/apache/cloudberry-backup/testutils"
	"github.com/apache/cloudberry-backup/toc"
	"github.com/apache/cloudberry-go-libs/structmatcher"
	"github.com/apache/cloudberry-go-libs/testhelper"

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

var _ = Describe("backup integration create statement tests", func() {
	BeforeEach(func() {
		tocfile, backupfile = testutils.InitializeTestTOC(buffer, "predata")
	})
	Describe("PrintCreateSchemaStatements", func() {
		var partitionAlteredSchemas map[string]bool
		BeforeEach(func() {
			partitionAlteredSchemas = make(map[string]bool)
		})
		It("creates a non public schema", func() {
			schemas := []backup.Schema{{Oid: 0, Name: "test_schema"}}
			schemaMetadata := testutils.DefaultMetadataMap(toc.OBJ_SCHEMA, true, true, true, includeSecurityLabels)

			backup.PrintCreateSchemaStatements(backupfile, tocfile, schemas, schemaMetadata)

			testhelper.AssertQueryRuns(connectionPool, buffer.String())
			defer testhelper.AssertQueryRuns(connectionPool, "DROP SCHEMA test_schema")

			resultSchemas := backup.GetAllUserSchemas(connectionPool, partitionAlteredSchemas)

			Expect(resultSchemas).To(HaveLen(2))
			Expect(resultSchemas[0].Name).To(Equal("public"))

			structmatcher.ExpectStructsToMatchExcluding(&schemas[0], &resultSchemas[1], "Oid")
		})

		It("modifies the public schema", func() {
			schemas := []backup.Schema{{Oid: 2200, Name: "public"}}
			schemaMetadata := testutils.DefaultMetadataMap(toc.OBJ_SCHEMA, true, true, true, includeSecurityLabels)

			backup.PrintCreateSchemaStatements(backupfile, tocfile, schemas, schemaMetadata)

			testhelper.AssertQueryRuns(connectionPool, buffer.String())
			defer testhelper.AssertQueryRuns(connectionPool, "ALTER SCHEMA public OWNER TO anothertestrole")
			defer testhelper.AssertQueryRuns(connectionPool, "COMMENT ON SCHEMA public IS 'standard public schema'")

			resultSchemas := backup.GetAllUserSchemas(connectionPool, partitionAlteredSchemas)

			Expect(resultSchemas).To(HaveLen(1))
			structmatcher.ExpectStructsToMatchExcluding(&schemas[0], &resultSchemas[0])
		})
	})
	Describe("PrintConstraintStatement", func() {
		var (
			uniqueConstraint          backup.Constraint
			pkConstraint              backup.Constraint
			fkConstraint              backup.Constraint
			checkConstraint           backup.Constraint
			partitionCheckConstraint  backup.Constraint
			partitionChildConstraint  backup.Constraint
			partitionIntmdConstraint  backup.Constraint
			partitionParentConstraint backup.Constraint
			objectMetadata            backup.MetadataMap
		)
		BeforeEach(func() {
			uniqueConstraint = backup.Constraint{Oid: 0, Schema: "public", Name: "uniq2", ConType: "u", Def: sql.NullString{String: "UNIQUE (a, b)", Valid: true}, OwningObject: "public.testtable", IsDomainConstraint: false, IsPartitionParent: false}
			pkConstraint = backup.Constraint{Oid: 0, Schema: "public", Name: "constraints_other_table_pkey", ConType: "p", Def: sql.NullString{String: "PRIMARY KEY (b)", Valid: true}, OwningObject: "public.constraints_other_table", IsDomainConstraint: false, IsPartitionParent: false}
			fkConstraint = backup.Constraint{Oid: 0, Schema: "public", Name: "fk1", ConType: "f", Def: sql.NullString{String: "FOREIGN KEY (b) REFERENCES public.constraints_other_table(b)", Valid: true}, OwningObject: "public.testtable", IsDomainConstraint: false, IsPartitionParent: false}
			checkConstraint = backup.Constraint{Oid: 0, Schema: "public", Name: "check1", ConType: "c", Def: sql.NullString{String: "CHECK (a <> 42)", Valid: true}, OwningObject: "public.testtable", IsDomainConstraint: false, IsPartitionParent: false}
			partitionCheckConstraint = backup.Constraint{Oid: 0, Schema: "public", Name: "check1", ConType: "c", Def: sql.NullString{String: "CHECK (id <> 0)", Valid: true}, OwningObject: "public.part", IsDomainConstraint: false, IsPartitionParent: true}
			partitionIntmdConstraint = backup.Constraint{Oid: 0, Schema: "public", Name: "id_unique", ConType: "u", Def: sql.NullString{String: "UNIQUE (id)", Valid: true}, OwningObject: "public.part_one", IsDomainConstraint: false, IsPartitionParent: true}
			partitionChildConstraint = backup.Constraint{Oid: 0, Schema: "public", Name: "id_unique", ConType: "u", Def: sql.NullString{String: "UNIQUE (id)", Valid: true}, OwningObject: "public.part_one", IsDomainConstraint: false, IsPartitionParent: false}
			partitionParentConstraint = backup.Constraint{Oid: 0, Schema: "public", Name: "check_year", ConType: "c", Def: sql.NullString{String: "CHECK (year < 3000)", Valid: true}, OwningObject: "public.part", IsDomainConstraint: false, IsPartitionParent: true}
			testhelper.AssertQueryRuns(connectionPool, "CREATE TABLE public.testtable(a int, b text) DISTRIBUTED BY (b)")
			objectMetadata = testutils.DefaultMetadataMap(toc.OBJ_CONSTRAINT, false, false, false, false)

			if (connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("6")) || connectionPool.Version.IsCBDB() {
				uniqueConstraint.ConIsLocal = true
				pkConstraint.ConIsLocal = true
				fkConstraint.ConIsLocal = true
				checkConstraint.ConIsLocal = true
				partitionCheckConstraint.ConIsLocal = true
				partitionIntmdConstraint.ConIsLocal = true
				partitionChildConstraint.ConIsLocal = true
				partitionParentConstraint.ConIsLocal = true
			}
		})
		AfterEach(func() {
			testhelper.AssertQueryRuns(connectionPool, "DROP TABLE public.testtable CASCADE")
		})
		It("creates a unique constraint", func() {
			constraints := []backup.Constraint{uniqueConstraint}
			backup.PrintConstraintStatements(backupfile, tocfile, constraints, objectMetadata)

			testhelper.AssertQueryRuns(connectionPool, buffer.String())

			resultConstraints := backup.GetConstraints(connectionPool)

			Expect(resultConstraints).To(HaveLen(1))
			structmatcher.ExpectStructsToMatchExcluding(&uniqueConstraint, &resultConstraints[0], "Oid")
		})
		It("creates a primary key constraint", func() {
			constraints := []backup.Constraint{pkConstraint}
			backup.PrintConstraintStatements(backupfile, tocfile, constraints, objectMetadata)

			testhelper.AssertQueryRuns(connectionPool, "CREATE TABLE public.constraints_other_table(b text)")
			defer testhelper.AssertQueryRuns(connectionPool, "DROP TABLE public.constraints_other_table CASCADE")
			testhelper.AssertQueryRuns(connectionPool, buffer.String())

			resultConstraints := backup.GetConstraints(connectionPool)

			Expect(resultConstraints).To(HaveLen(1))
			structmatcher.ExpectStructsToMatchExcluding(&pkConstraint, &resultConstraints[0], "Oid")
		})
		It("creates a foreign key constraint", func() {
			constraints := []backup.Constraint{fkConstraint}
			backup.PrintConstraintStatements(backupfile, tocfile, constraints, objectMetadata)

			testhelper.AssertQueryRuns(connectionPool, "CREATE TABLE public.constraints_other_table(b text PRIMARY KEY)")
			defer testhelper.AssertQueryRuns(connectionPool, "DROP TABLE public.constraints_other_table CASCADE")
			testhelper.AssertQueryRuns(connectionPool, buffer.String())

			resultConstraints := backup.GetConstraints(connectionPool)

			Expect(resultConstraints).To(HaveLen(2))
			structmatcher.ExpectStructsToMatchExcluding(&pkConstraint, &resultConstraints[0], "Oid")
			structmatcher.ExpectStructsToMatchExcluding(&fkConstraint, &resultConstraints[1], "Oid")
		})
		It("creates a check constraint", func() {
			constraints := []backup.Constraint{checkConstraint}
			backup.PrintConstraintStatements(backupfile, tocfile, constraints, objectMetadata)

			testhelper.AssertQueryRuns(connectionPool, buffer.String())

			resultConstraints := backup.GetConstraints(connectionPool)

			Expect(resultConstraints).To(HaveLen(1))
			structmatcher.ExpectStructsToMatchExcluding(&checkConstraint, &resultConstraints[0], "Oid")
		})
		It("creates multiple constraints on one table", func() {
			constraints := []backup.Constraint{checkConstraint, uniqueConstraint, fkConstraint}
			backup.PrintConstraintStatements(backupfile, tocfile, constraints, objectMetadata)

			testhelper.AssertQueryRuns(connectionPool, "CREATE TABLE public.constraints_other_table(b text PRIMARY KEY)")
			defer testhelper.AssertQueryRuns(connectionPool, "DROP TABLE public.constraints_other_table CASCADE")
			testhelper.AssertQueryRuns(connectionPool, buffer.String())

			resultConstraints := backup.GetConstraints(connectionPool)

			Expect(resultConstraints).To(HaveLen(4))
			structmatcher.ExpectStructsToMatchExcluding(&checkConstraint, &resultConstraints[0], "Oid")
			structmatcher.ExpectStructsToMatchExcluding(&pkConstraint, &resultConstraints[1], "Oid")
			structmatcher.ExpectStructsToMatchExcluding(&fkConstraint, &resultConstraints[2], "Oid")
			structmatcher.ExpectStructsToMatchExcluding(&uniqueConstraint, &resultConstraints[3], "Oid")
		})
		It("manages order of primary and foreign key constraints", func() {
			constraints := []backup.Constraint{fkConstraint, uniqueConstraint, pkConstraint}
			backup.PrintConstraintStatements(backupfile, tocfile, constraints, objectMetadata)

			testhelper.AssertQueryRuns(connectionPool, "CREATE TABLE public.constraints_other_table(b text)")
			defer testhelper.AssertQueryRuns(connectionPool, "DROP TABLE public.constraints_other_table CASCADE")
			testhelper.AssertQueryRuns(connectionPool, buffer.String())

			resultConstraints := backup.GetConstraints(connectionPool)

			Expect(resultConstraints).To(HaveLen(3))
			structmatcher.ExpectStructsToMatchExcluding(&pkConstraint, &resultConstraints[0], "Oid")
			structmatcher.ExpectStructsToMatchExcluding(&fkConstraint, &resultConstraints[1], "Oid")
			structmatcher.ExpectStructsToMatchExcluding(&uniqueConstraint, &resultConstraints[2], "Oid")
		})
		It("creates a check constraint on a parent partition table", func() {
			constraints := []backup.Constraint{partitionCheckConstraint}
			backup.PrintConstraintStatements(backupfile, tocfile, constraints, objectMetadata)

			testhelper.AssertQueryRuns(connectionPool, `CREATE TABLE public.part (id int, year int)
DISTRIBUTED BY (id)
PARTITION BY RANGE (year)
( START (2007) END (2008) EVERY (1),
  DEFAULT PARTITION extra ); `)
			defer testhelper.AssertQueryRuns(connectionPool, "DROP TABLE public.part CASCADE")
			testhelper.AssertQueryRuns(connectionPool, buffer.String())

			resultConstraints := backup.GetConstraints(connectionPool)

			Expect(resultConstraints).To(HaveLen(1))
			structmatcher.ExpectStructsToMatchExcluding(&partitionCheckConstraint, &resultConstraints[0], "Oid")
		})
		It("creates a unique constraint on an intermediate partition table", func() {
			// TODO -- this seems like it should work on 6.  See about flexing the syntax below to 6-supported
			testutils.SkipIfBefore7(connectionPool)
			testhelper.AssertQueryRuns(connectionPool, `CREATE TABLE public.part (id int, year int)
DISTRIBUTED BY (id)
PARTITION BY RANGE (year);`)
			defer testhelper.AssertQueryRuns(connectionPool, "DROP TABLE public.part CASCADE")

			testhelper.AssertQueryRuns(connectionPool, `CREATE TABLE public.part_one PARTITION OF public.part
FOR VALUES FROM (2007) TO (2010) PARTITION BY RANGE (id);`)
			testhelper.AssertQueryRuns(connectionPool, `ALTER TABLE public.part_one ADD CONSTRAINT id_unique UNIQUE (id);`)

			testhelper.AssertQueryRuns(connectionPool, `CREATE TABLE public.part_two PARTITION OF public.part_one
FOR VALUES FROM (0) TO (100);`)

			resultConstraints := backup.GetConstraints(connectionPool)
			Expect(resultConstraints).To(HaveLen(1))
			structmatcher.ExpectStructsToMatchExcluding(&partitionIntmdConstraint, &resultConstraints[0], "Oid")
		})
		It("creates a unique constraint on a child partition table", func() {
			// TODO -- this seems like it should work on 6.  See about flexing the syntax below to 6-supported
			testutils.SkipIfBefore7(connectionPool)
			testhelper.AssertQueryRuns(connectionPool, `CREATE TABLE public.part (id int, year int)
DISTRIBUTED BY (id)
PARTITION BY RANGE (year);`)
			defer testhelper.AssertQueryRuns(connectionPool, "DROP TABLE public.part CASCADE")

			testhelper.AssertQueryRuns(connectionPool, `CREATE TABLE public.part_one PARTITION OF public.part
FOR VALUES FROM (2007) TO (2010);`)
			testhelper.AssertQueryRuns(connectionPool, `ALTER TABLE public.part_one ADD CONSTRAINT id_unique UNIQUE (id);`)

			resultConstraints := backup.GetConstraints(connectionPool)
			Expect(resultConstraints).To(HaveLen(1))
			structmatcher.ExpectStructsToMatchExcluding(&partitionChildConstraint, &resultConstraints[0], "Oid")
		})
		It("creates two unique constraints, one each  on parent and child partition tables", func() {
			// TODO -- this seems like it should work on 6.  See about flexing the syntax below to 6-supported
			testutils.SkipIfBefore7(connectionPool)
			testhelper.AssertQueryRuns(connectionPool, `CREATE TABLE public.part (id int, year int)
DISTRIBUTED BY (id)
PARTITION BY RANGE (year);`)
			defer testhelper.AssertQueryRuns(connectionPool, "DROP TABLE public.part CASCADE")
			testhelper.AssertQueryRuns(connectionPool, `ALTER TABLE public.part ADD CONSTRAINT check_year CHECK (year < 3000);`)

			testhelper.AssertQueryRuns(connectionPool, `CREATE TABLE public.part_one PARTITION OF public.part
FOR VALUES FROM (2007) TO (2010);`)
			testhelper.AssertQueryRuns(connectionPool, `ALTER TABLE public.part_one ADD CONSTRAINT id_unique UNIQUE (id);`)

			resultConstraints := backup.GetConstraints(connectionPool)
			Expect(resultConstraints).To(HaveLen(2))
			if resultConstraints[0].Name == "check_year" {
				structmatcher.ExpectStructsToMatchExcluding(&partitionParentConstraint, &resultConstraints[0], "Oid")
				structmatcher.ExpectStructsToMatchExcluding(&partitionChildConstraint, &resultConstraints[1], "Oid")
			} else {
				structmatcher.ExpectStructsToMatchExcluding(&partitionParentConstraint, &resultConstraints[1], "Oid")
				structmatcher.ExpectStructsToMatchExcluding(&partitionChildConstraint, &resultConstraints[0], "Oid")
			}

		})
	})
	Describe("PrintAccessMethodStatement", func() {
		It("creates user defined access method for tables and indexes", func() {
			testutils.SkipIfBefore7(connectionPool)

			accessMethodTable := backup.AccessMethod{Oid: 1, Name: "test_tableam_table", Handler: "heap_tableam_handler", Type: "t"}
			accessMethodIndex := backup.AccessMethod{Oid: 2, Name: "test_tableam_index", Handler: "gisthandler", Type: "i"}
			accessMethods := []backup.AccessMethod{accessMethodTable, accessMethodIndex}
			backup.PrintAccessMethodStatements(backupfile, tocfile, accessMethods, backup.MetadataMap{})

			testhelper.AssertQueryRuns(connectionPool, buffer.String())
			defer testhelper.AssertQueryRuns(connectionPool, "DROP ACCESS METHOD test_tableam_table;")
			defer testhelper.AssertQueryRuns(connectionPool, "DROP ACCESS METHOD test_tableam_index;")

			resultAccessMethods := backup.GetAccessMethods(connectionPool)
			Expect(resultAccessMethods).To(HaveLen(2))
			structmatcher.ExpectStructsToMatchExcluding(&resultAccessMethods[0], &accessMethodTable, "Oid")
			structmatcher.ExpectStructsToMatchExcluding(&resultAccessMethods[1], &accessMethodIndex, "Oid")
		})
	})
	Describe("GUC-printing functions", func() {
		gucs := backup.SessionGUCs{ClientEncoding: "UTF8"}
		Describe("PrintSessionGUCs", func() {
			It("prints the default session GUCs", func() {
				backup.PrintSessionGUCs(backupfile, tocfile, gucs)

				//We just want to check that these queries run successfully, no setup required
				testhelper.AssertQueryRuns(connectionPool, buffer.String())
			})

		})
	})
})
