package backup

/*
 * This file contains structs and functions related to backing up "post-data" metadata
 * on the coordinator, which is any metadata that needs to be restored after data is
 * restored, such as indexes and rules.
 */

import (
	"strings"

	"github.com/apache/cloudberry-backup/toc"
	"github.com/apache/cloudberry-backup/utils"
	"github.com/apache/cloudberry-go-libs/gplog"
	"github.com/pkg/errors"
)

func PrintCreateIndexStatements(metadataFile *utils.FileWithByteCount, objToc *toc.TOC, indexes []IndexDefinition, indexMetadata MetadataMap) {
	for _, index := range indexes {
		start := metadataFile.ByteCount
		if !index.SupportsConstraint {
			section, entry := index.GetMetadataEntry()

			metadataFile.MustPrintf("\n\n%s;", index.Def.String)
			objToc.AddMetadataEntry(section, entry, start, metadataFile.ByteCount, []uint32{0, 0})

			// Start INDEX metadata
			entry.ReferenceObject = index.FQN()
			entry.ObjectType = "INDEX METADATA"
			if index.Tablespace != "" {
				start := metadataFile.ByteCount
				metadataFile.MustPrintf("\nALTER INDEX %s SET TABLESPACE %s;", index.FQN(), index.Tablespace)
				objToc.AddMetadataEntry(section, entry, start, metadataFile.ByteCount, []uint32{0, 0})
			}
			if index.ParentIndexFQN != "" && ((connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB()) {
				start := metadataFile.ByteCount
				metadataFile.MustPrintf("\nALTER INDEX %s ATTACH PARTITION %s;", index.ParentIndexFQN, index.FQN())
				objToc.AddMetadataEntry(section, entry, start, metadataFile.ByteCount, []uint32{0, 0})
			}
			tableFQN := utils.MakeFQN(index.OwningSchema, index.OwningTable)
			if index.IsClustered {
				start := metadataFile.ByteCount
				metadataFile.MustPrintf("\nALTER TABLE %s CLUSTER ON %s;", tableFQN, index.Name)
				objToc.AddMetadataEntry(section, entry, start, metadataFile.ByteCount, []uint32{0, 0})
			}
			if index.IsReplicaIdentity {
				start := metadataFile.ByteCount
				metadataFile.MustPrintf("\nALTER TABLE %s REPLICA IDENTITY USING INDEX %s;", tableFQN, index.Name)
				objToc.AddMetadataEntry(section, entry, start, metadataFile.ByteCount, []uint32{0, 0})
			}
			if index.StatisticsColumns != "" && index.StatisticsValues != "" {
				cols := strings.Split(index.StatisticsColumns, ",")
				vals := strings.Split(index.StatisticsValues, ",")
				if len(cols) != len(vals) {
					gplog.Fatal(errors.Errorf("Index StatisticsColumns(%d) and StatisticsValues(%d) count don't match\n", len(cols), len(vals)), "")
				}
				for i := 0; i < len(cols); i++ {
					start := metadataFile.ByteCount
					metadataFile.MustPrintf("\nALTER INDEX %s ALTER COLUMN %s SET STATISTICS %s;", index.FQN(), cols[i], vals[i])
					objToc.AddMetadataEntry(section, entry, start, metadataFile.ByteCount, []uint32{0, 0})
				}
			}
		}
		PrintObjectMetadata(metadataFile, objToc, indexMetadata[index.GetUniqueID()], index, "", []uint32{0, 0})
	}
}

func PrintCreateRuleStatements(metadataFile *utils.FileWithByteCount, objToc *toc.TOC, rules []RuleDefinition, ruleMetadata MetadataMap) {
	for _, rule := range rules {
		start := metadataFile.ByteCount
		metadataFile.MustPrintf("\n\n%s", rule.Def.String)

		section, entry := rule.GetMetadataEntry()
		objToc.AddMetadataEntry(section, entry, start, metadataFile.ByteCount, []uint32{0, 0})
		tableFQN := utils.MakeFQN(rule.OwningSchema, rule.OwningTable)
		PrintObjectMetadata(metadataFile, objToc, ruleMetadata[rule.GetUniqueID()], rule, tableFQN, []uint32{0, 0})
	}
}

func PrintCreateTriggerStatements(metadataFile *utils.FileWithByteCount, objToc *toc.TOC, triggers []TriggerDefinition, triggerMetadata MetadataMap) {
	for _, trigger := range triggers {
		start := metadataFile.ByteCount
		metadataFile.MustPrintf("\n\n%s;", trigger.Def.String)

		section, entry := trigger.GetMetadataEntry()
		objToc.AddMetadataEntry(section, entry, start, metadataFile.ByteCount, []uint32{0, 0})
		tableFQN := utils.MakeFQN(trigger.OwningSchema, trigger.OwningTable)
		PrintObjectMetadata(metadataFile, objToc, triggerMetadata[trigger.GetUniqueID()], trigger, tableFQN, []uint32{0, 0})
	}
}

func PrintCreateEventTriggerStatements(metadataFile *utils.FileWithByteCount, objToc *toc.TOC, eventTriggers []EventTrigger, eventTriggerMetadata MetadataMap) {
	for _, eventTrigger := range eventTriggers {
		start := metadataFile.ByteCount
		section, entry := eventTrigger.GetMetadataEntry()

		metadataFile.MustPrintf("\n\nCREATE EVENT TRIGGER %s\nON %s", eventTrigger.Name, eventTrigger.Event)
		if eventTrigger.EventTags != "" {
			metadataFile.MustPrintf("\nWHEN TAG IN (%s)", eventTrigger.EventTags)
		}
		if (connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB() {
			metadataFile.MustPrintf("\nEXECUTE FUNCTION %s();", eventTrigger.FunctionName)
		} else {
			metadataFile.MustPrintf("\nEXECUTE PROCEDURE %s();", eventTrigger.FunctionName)
		}
		objToc.AddMetadataEntry(section, entry, start, metadataFile.ByteCount, []uint32{0, 0})

		// Start EVENT TRIGGER metadata
		entry.ReferenceObject = eventTrigger.Name
		entry.ObjectType = toc.OBJ_EVENT_TRIGGER + " METADATA"
		if eventTrigger.Enabled != "O" {
			var enableOption string
			switch eventTrigger.Enabled {
			case "D":
				enableOption = "DISABLE"
			case "A":
				enableOption = "ENABLE ALWAYS"
			case "R":
				enableOption = "ENABLE REPLICA"
			default:
				enableOption = "ENABLE"
			}
			start := metadataFile.ByteCount
			metadataFile.MustPrintf("\nALTER EVENT TRIGGER %s %s;", eventTrigger.Name, enableOption)
			objToc.AddMetadataEntry(section, entry, start, metadataFile.ByteCount, []uint32{0, 0})
		}
		PrintObjectMetadata(metadataFile, objToc, eventTriggerMetadata[eventTrigger.GetUniqueID()], eventTrigger, "", []uint32{0, 0})
	}
}

func PrintCreatePolicyStatements(metadataFile *utils.FileWithByteCount, objToc *toc.TOC, policies []RLSPolicy, policyMetadata MetadataMap) {
	for _, policy := range policies {
		start := metadataFile.ByteCount
		section, entry := policy.GetMetadataEntry()

		tableFQN := utils.MakeFQN(policy.Schema, policy.Table)
		metadataFile.MustPrintf("\n\nALTER TABLE %s ENABLE ROW LEVEL SECURITY;", tableFQN)
		objToc.AddMetadataEntry(section, entry, start, metadataFile.ByteCount, []uint32{0, 0})

		permissiveOption := ""
		if policy.Permissive == "false" {
			permissiveOption = " AS RESTRICTIVE"
		}
		cmdOption := ""
		if policy.Cmd != "" {
			switch policy.Cmd {
			case "*":
				cmdOption = ""
			case "r":
				cmdOption = " FOR SELECT"
			case "a":
				cmdOption = " FOR INSERT"
			case "w":
				cmdOption = " FOR UPDATE"
			case "d":
				cmdOption = " FOR DELETE"
			default:
				gplog.Fatal(errors.Errorf("Unexpected policy command: expected '*|r|a|w|d' got '%s'\n", policy.Cmd), "")
			}
		}
		start = metadataFile.ByteCount
		metadataFile.MustPrintf("\nCREATE POLICY %s\nON %s%s%s", policy.Name, tableFQN, permissiveOption, cmdOption)

		if policy.Roles != "" {
			metadataFile.MustPrintf("\n TO %s", policy.Roles)
		}
		if policy.Qual != "" {
			metadataFile.MustPrintf("\n USING (%s)", policy.Qual)
		}
		if policy.WithCheck != "" {
			metadataFile.MustPrintf("\n WITH CHECK (%s)", policy.WithCheck)
		}
		metadataFile.MustPrintf(";\n")
		objToc.AddMetadataEntry(section, entry, start, metadataFile.ByteCount, []uint32{0, 0})
		PrintObjectMetadata(metadataFile, objToc, policyMetadata[policy.GetUniqueID()], policy, "", []uint32{0, 0})
	}
}

func PrintCreateExtendedStatistics(metadataFile *utils.FileWithByteCount, objToc *toc.TOC, statExtObjects []StatisticExt, statMetadata MetadataMap) {
	for _, stat := range statExtObjects {
		start := metadataFile.ByteCount
		metadataFile.MustPrintln()

		metadataFile.MustPrintf("\n%s;", stat.Definition)
		section, entry := stat.GetMetadataEntry()
		objToc.AddMetadataEntry(section, entry, start, metadataFile.ByteCount, []uint32{0, 0})
		PrintObjectMetadata(metadataFile, objToc, statMetadata[stat.GetUniqueID()], stat, "", []uint32{0, 0})
	}
}
