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/postdata tests", func() {
	var emptyMetadataMap = backup.MetadataMap{}
	BeforeEach(func() {
		tocfile, backupfile = testutils.InitializeTestTOC(buffer, "postdata")
	})
	Context("PrintCreateIndexStatements", func() {
		var index backup.IndexDefinition
		BeforeEach(func() {
			index = backup.IndexDefinition{Oid: 1, Name: "testindex", OwningSchema: "public", OwningTable: "testtable", Def: sql.NullString{String: "CREATE INDEX testindex ON public.testtable USING btree(i)", Valid: true}}
		})
		It("can print a basic index", func() {
			indexes := []backup.IndexDefinition{index}
			backup.PrintCreateIndexStatements(backupfile, tocfile, indexes, emptyMetadataMap)
			testutils.ExpectEntry(tocfile.PostdataEntries, 0, "public", "public.testtable", "testindex", toc.OBJ_INDEX)
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer, `CREATE INDEX testindex ON public.testtable USING btree(i);`)
		})
		It("can print an index used for clustering", func() {
			index.IsClustered = true
			indexes := []backup.IndexDefinition{index}
			backup.PrintCreateIndexStatements(backupfile, tocfile, indexes, emptyMetadataMap)
			testutils.ExpectEntry(tocfile.PostdataEntries, 0, "public", "public.testtable", "testindex", toc.OBJ_INDEX)
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer, "CREATE INDEX testindex ON public.testtable USING btree(i);",
				"ALTER TABLE public.testtable CLUSTER ON testindex;")
		})
		It("can print an index with a tablespace", func() {
			index.Tablespace = "test_tablespace"
			indexes := []backup.IndexDefinition{index}
			backup.PrintCreateIndexStatements(backupfile, tocfile, indexes, emptyMetadataMap)
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer, "CREATE INDEX testindex ON public.testtable USING btree(i);",
				"ALTER INDEX public.testindex SET TABLESPACE test_tablespace;")
		})
		It("can print an index with a comment", func() {
			indexes := []backup.IndexDefinition{index}
			indexMetadataMap := testutils.DefaultMetadataMap(toc.OBJ_INDEX, false, false, true, false)
			backup.PrintCreateIndexStatements(backupfile, tocfile, indexes, indexMetadataMap)
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer, "CREATE INDEX testindex ON public.testtable USING btree(i);",
				"COMMENT ON INDEX public.testindex IS 'This is an index comment.';")
		})
		It("can print an index that is a replica identity", func() {
			index.IsReplicaIdentity = true
			indexes := []backup.IndexDefinition{index}
			backup.PrintCreateIndexStatements(backupfile, tocfile, indexes, emptyMetadataMap)
			testutils.ExpectEntry(tocfile.PostdataEntries, 0, "public", "public.testtable", "testindex", toc.OBJ_INDEX)
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer,
				"CREATE INDEX testindex ON public.testtable USING btree(i);",
				"ALTER TABLE public.testtable REPLICA IDENTITY USING INDEX testindex;",
			)
		})
		It("can print an index of a partition that is attached to a parent partition index", func() {
			testutils.SkipIfBefore7(connectionPool)

			parentIndex := backup.IndexDefinition{Oid: 2, Name: "parenttestindex", OwningSchema: "public", OwningTable: "parenttesttable", Def: sql.NullString{String: "CREATE INDEX parenttestindex ON public.parenttesttable USING btree(i)", Valid: true}}
			index.ParentIndexFQN = "public.parenttestindex"
			indexes := []backup.IndexDefinition{parentIndex, index}
			backup.PrintCreateIndexStatements(backupfile, tocfile, indexes, emptyMetadataMap)
			testutils.ExpectEntry(tocfile.PostdataEntries, 0, "public", "public.parenttesttable", "parenttestindex", toc.OBJ_INDEX)
			testutils.ExpectEntry(tocfile.PostdataEntries, 1, "public", "public.testtable", "testindex", toc.OBJ_INDEX)
			testutils.ExpectEntry(tocfile.PostdataEntries, 2, "public", "public.testindex", "testindex", "INDEX METADATA")
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer,
				"CREATE INDEX parenttestindex ON public.parenttesttable USING btree(i);",
				"CREATE INDEX testindex ON public.testtable USING btree(i);",
				"ALTER INDEX public.parenttestindex ATTACH PARTITION public.testindex;",
			)
		})
	})
	Context("PrintCreateRuleStatements", func() {
		rule := backup.RuleDefinition{Oid: 1, Name: "testrule", OwningSchema: "public", OwningTable: "testtable", Def: sql.NullString{String: "CREATE RULE update_notify AS ON UPDATE TO testtable DO NOTIFY testtable;", Valid: true}}
		It("can print a basic rule", func() {
			rules := []backup.RuleDefinition{rule}
			emptyMetadataMap := backup.MetadataMap{}
			backup.PrintCreateRuleStatements(backupfile, tocfile, rules, emptyMetadataMap)
			testutils.ExpectEntry(tocfile.PostdataEntries, 0, "public", "public.testtable", "testrule", toc.OBJ_RULE)
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer, "CREATE RULE update_notify AS ON UPDATE TO testtable DO NOTIFY testtable;")
		})
		It("can print a rule with a comment", func() {
			rules := []backup.RuleDefinition{rule}
			ruleMetadataMap := testutils.DefaultMetadataMap(toc.OBJ_RULE, false, false, true, false)
			backup.PrintCreateRuleStatements(backupfile, tocfile, rules, ruleMetadataMap)
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer, "CREATE RULE update_notify AS ON UPDATE TO testtable DO NOTIFY testtable;",
				"COMMENT ON RULE testrule ON public.testtable IS 'This is a rule comment.';")
		})
	})
	Context("PrintCreateTriggerStatements", func() {
		trigger := backup.TriggerDefinition{Oid: 1, Name: "testtrigger", OwningSchema: "public", OwningTable: "testtable", Def: sql.NullString{String: "CREATE TRIGGER sync_testtable AFTER INSERT OR DELETE OR UPDATE ON testtable FOR EACH STATEMENT EXECUTE PROCEDURE flatfile_update_trigger()", Valid: true}}
		It("can print a basic trigger", func() {
			triggers := []backup.TriggerDefinition{trigger}
			emptyMetadataMap := backup.MetadataMap{}
			backup.PrintCreateTriggerStatements(backupfile, tocfile, triggers, emptyMetadataMap)
			testutils.ExpectEntry(tocfile.PostdataEntries, 0, "public", "public.testtable", "testtrigger", toc.OBJ_TRIGGER)
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer, "CREATE TRIGGER sync_testtable AFTER INSERT OR DELETE OR UPDATE ON testtable FOR EACH STATEMENT EXECUTE PROCEDURE flatfile_update_trigger();")
		})
		It("can print a trigger with a comment", func() {
			triggers := []backup.TriggerDefinition{trigger}
			triggerMetadataMap := testutils.DefaultMetadataMap(toc.OBJ_TRIGGER, false, false, true, false)
			backup.PrintCreateTriggerStatements(backupfile, tocfile, triggers, triggerMetadataMap)
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer, "CREATE TRIGGER sync_testtable AFTER INSERT OR DELETE OR UPDATE ON testtable FOR EACH STATEMENT EXECUTE PROCEDURE flatfile_update_trigger();",
				"COMMENT ON TRIGGER testtrigger ON public.testtable IS 'This is a trigger comment.';")
		})
	})
	Context("PrintCreateEventTriggerStatements", func() {
		var evTrigExecReplacement string
		BeforeEach(func() {
			if (connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB() {
				evTrigExecReplacement = toc.OBJ_FUNCTION
			} else {
				evTrigExecReplacement = toc.OBJ_PROCEDURE
			}
		})
		It("can print a basic event trigger", func() {
			eventTrigger := backup.EventTrigger{Oid: 1, Name: "testeventtrigger", Event: "ddl_command_start", FunctionName: "abort_any_command", Enabled: "O"}
			eventTriggers := []backup.EventTrigger{eventTrigger}
			emptyMetadataMap := backup.MetadataMap{}
			backup.PrintCreateEventTriggerStatements(backupfile, tocfile, eventTriggers, emptyMetadataMap)
			testutils.ExpectEntry(tocfile.PostdataEntries, 0, "", "", "testeventtrigger", toc.OBJ_EVENT_TRIGGER)
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer, fmt.Sprintf(`CREATE EVENT TRIGGER testeventtrigger
ON ddl_command_start
EXECUTE %s abort_any_command();`, evTrigExecReplacement))
		})
		It("can print a basic event trigger with a comment", func() {
			eventTrigger := backup.EventTrigger{Oid: 1, Name: "testeventtrigger", Event: "ddl_command_start", FunctionName: "abort_any_command", Enabled: "O"}
			eventTriggers := []backup.EventTrigger{eventTrigger}
			eventTriggerMetadataMap := testutils.DefaultMetadataMap(toc.OBJ_EVENT_TRIGGER, false, false, true, false)
			backup.PrintCreateEventTriggerStatements(backupfile, tocfile, eventTriggers, eventTriggerMetadataMap)
			testutils.ExpectEntry(tocfile.PostdataEntries, 0, "", "", "testeventtrigger", toc.OBJ_EVENT_TRIGGER)
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer, fmt.Sprintf(`CREATE EVENT TRIGGER testeventtrigger
ON ddl_command_start
EXECUTE %s abort_any_command();`, evTrigExecReplacement), `COMMENT ON EVENT TRIGGER testeventtrigger IS 'This is an event trigger comment.';`)
		})
		It("can print a basic event trigger with an owner", func() {
			eventTrigger := backup.EventTrigger{Oid: 1, Name: "testeventtrigger", Event: "ddl_command_start", FunctionName: "abort_any_command", Enabled: "O"}
			eventTriggers := []backup.EventTrigger{eventTrigger}
			eventTriggerMetadataMap := testutils.DefaultMetadataMap(toc.OBJ_EVENT_TRIGGER, false, true, false, false)
			backup.PrintCreateEventTriggerStatements(backupfile, tocfile, eventTriggers, eventTriggerMetadataMap)
			testutils.ExpectEntry(tocfile.PostdataEntries, 0, "", "", "testeventtrigger", toc.OBJ_EVENT_TRIGGER)
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer, fmt.Sprintf(`CREATE EVENT TRIGGER testeventtrigger
ON ddl_command_start
EXECUTE %s abort_any_command();`, evTrigExecReplacement), `ALTER EVENT TRIGGER testeventtrigger OWNER TO testrole;`)
		})
		It("can print a basic event trigger with a security label", func() {
			eventTrigger := backup.EventTrigger{Oid: 1, Name: "testeventtrigger", Event: "ddl_command_start", FunctionName: "abort_any_command", Enabled: "O"}
			eventTriggers := []backup.EventTrigger{eventTrigger}
			eventTriggerMetadataMap := testutils.DefaultMetadataMap(toc.OBJ_EVENT_TRIGGER, false, false, false, true)
			backup.PrintCreateEventTriggerStatements(backupfile, tocfile, eventTriggers, eventTriggerMetadataMap)
			testutils.ExpectEntry(tocfile.PostdataEntries, 0, "", "", "testeventtrigger", toc.OBJ_EVENT_TRIGGER)
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer, fmt.Sprintf(`CREATE EVENT TRIGGER testeventtrigger
ON ddl_command_start
EXECUTE %s abort_any_command();`, evTrigExecReplacement), `SECURITY LABEL FOR dummy ON EVENT TRIGGER testeventtrigger IS 'unclassified';`)
		})
		It("can print an event trigger with filter variables", func() {
			eventTrigger := backup.EventTrigger{Oid: 1, Name: "testeventtrigger", Event: "ddl_command_start", FunctionName: "abort_any_command", Enabled: "O", EventTags: `'DROP FUNCTION','DROP TABLE'`}
			eventTriggers := []backup.EventTrigger{eventTrigger}
			emptyMetadataMap := backup.MetadataMap{}
			backup.PrintCreateEventTriggerStatements(backupfile, tocfile, eventTriggers, emptyMetadataMap)
			testutils.ExpectEntry(tocfile.PostdataEntries, 0, "", "", "testeventtrigger", toc.OBJ_EVENT_TRIGGER)
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer, fmt.Sprintf(`CREATE EVENT TRIGGER testeventtrigger
ON ddl_command_start
WHEN TAG IN ('DROP FUNCTION','DROP TABLE')
EXECUTE %s abort_any_command();`, evTrigExecReplacement))
		})
		It("can print an event trigger with filter variables with enable option DISABLE", func() {
			eventTrigger := backup.EventTrigger{Oid: 1, Name: "testeventtrigger", Event: "ddl_command_start", FunctionName: "abort_any_command", Enabled: "D"}
			eventTriggers := []backup.EventTrigger{eventTrigger}
			emptyMetadataMap := backup.MetadataMap{}
			backup.PrintCreateEventTriggerStatements(backupfile, tocfile, eventTriggers, emptyMetadataMap)
			testutils.ExpectEntry(tocfile.PostdataEntries, 0, "", "", "testeventtrigger", toc.OBJ_EVENT_TRIGGER)
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer, fmt.Sprintf(`CREATE EVENT TRIGGER testeventtrigger
ON ddl_command_start
EXECUTE %s abort_any_command();`, evTrigExecReplacement), `ALTER EVENT TRIGGER testeventtrigger DISABLE;`)
		})
		It("can print an event trigger with filter variables with enable option ENABLE", func() {
			eventTrigger := backup.EventTrigger{Oid: 1, Name: "testeventtrigger", Event: "ddl_command_start", FunctionName: "abort_any_command", Enabled: ""}
			eventTriggers := []backup.EventTrigger{eventTrigger}
			emptyMetadataMap := backup.MetadataMap{}
			backup.PrintCreateEventTriggerStatements(backupfile, tocfile, eventTriggers, emptyMetadataMap)
			testutils.ExpectEntry(tocfile.PostdataEntries, 0, "", "", "testeventtrigger", toc.OBJ_EVENT_TRIGGER)
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer, fmt.Sprintf(`CREATE EVENT TRIGGER testeventtrigger
ON ddl_command_start
EXECUTE %s abort_any_command();`, evTrigExecReplacement), `ALTER EVENT TRIGGER testeventtrigger ENABLE;`)
		})
		It("can print an event trigger with filter variables with enable option ENABLE REPLICA", func() {
			eventTrigger := backup.EventTrigger{Oid: 1, Name: "testeventtrigger", Event: "ddl_command_start", FunctionName: "abort_any_command", Enabled: "R"}
			eventTriggers := []backup.EventTrigger{eventTrigger}
			emptyMetadataMap := backup.MetadataMap{}
			backup.PrintCreateEventTriggerStatements(backupfile, tocfile, eventTriggers, emptyMetadataMap)
			testutils.ExpectEntry(tocfile.PostdataEntries, 0, "", "", "testeventtrigger", toc.OBJ_EVENT_TRIGGER)
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer, fmt.Sprintf(`CREATE EVENT TRIGGER testeventtrigger
ON ddl_command_start
EXECUTE %s abort_any_command();`, evTrigExecReplacement), `ALTER EVENT TRIGGER testeventtrigger ENABLE REPLICA;`)
		})
		It("can print an event trigger with filter variables with enable option ENABLE ALWAYS", func() {
			eventTrigger := backup.EventTrigger{Oid: 1, Name: "testeventtrigger", Event: "ddl_command_start", FunctionName: "abort_any_command", Enabled: "A"}
			eventTriggers := []backup.EventTrigger{eventTrigger}
			emptyMetadataMap := backup.MetadataMap{}
			backup.PrintCreateEventTriggerStatements(backupfile, tocfile, eventTriggers, emptyMetadataMap)
			testutils.ExpectEntry(tocfile.PostdataEntries, 0, "", "", "testeventtrigger", toc.OBJ_EVENT_TRIGGER)
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer, fmt.Sprintf(`CREATE EVENT TRIGGER testeventtrigger
ON ddl_command_start
EXECUTE %s abort_any_command();`, evTrigExecReplacement), `ALTER EVENT TRIGGER testeventtrigger ENABLE ALWAYS;`)
		})
	})
	Context("PrintCreateExtendedStatistics", func() {
		statExt := backup.StatisticExt{Oid: 1, Name: "test_stat", Namespace: "public", Owner: "username", TableSchema: "myschema", TableName: "mytable", Definition: "CREATE STATISTICS public.test_stat (dependencies) ON a, b FROM myschema.mytable"}
		It("can print an extended statistics", func() {
			stats := []backup.StatisticExt{statExt}
			emptyMetadataMap := backup.MetadataMap{}
			backup.PrintCreateExtendedStatistics(backupfile, tocfile, stats, emptyMetadataMap)
			testutils.ExpectEntry(tocfile.PostdataEntries, 0, "public", "myschema.mytable", "test_stat", toc.OBJ_STATISTICS)
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer, `CREATE STATISTICS public.test_stat (dependencies) ON a, b FROM myschema.mytable;`)
		})
	})
	Describe("PrintCreatePostdataViewStatements", func() {
		It("prints all postdata views", func() {
			view1 := backup.View{
				Schema:     "schema1",
				Name:       "view1",
				Definition: sql.NullString{String: "SELECT 1", Valid: true},
			}
			view2 := backup.View{
				Schema:     "schema1",
				Name:       "view2",
				Definition: sql.NullString{String: "SELECT 2", Valid: true},
				Options:    " WITH (check_option = cascaded, security_barrier)",
			}
			backup.PrintCreatePostdataViewStatements(backupfile, tocfile, []backup.View{view1, view2})
			testutils.ExpectEntry(tocfile.PostdataEntries, 0, "schema1", "", "view1", toc.OBJ_VIEW)
			testutils.ExpectEntry(tocfile.PostdataEntries, 1, "schema1", "", "view2", toc.OBJ_VIEW)
			expected1 := "CREATE OR REPLACE VIEW schema1.view1 AS SELECT 1"
			expected2 := "CREATE OR REPLACE VIEW schema1.view2 WITH (check_option = cascaded, security_barrier) AS SELECT 2"
			testutils.AssertBufferContents(tocfile.PostdataEntries, buffer, expected1, expected2)
		})
	})
})
