//nolint:lll
package entity

import (
	"database/sql/driver"
	"encoding/json"
	"fmt"
	"sort"
	"time"
)

//go:generate ../../../tools/bin/goqueryset -in entity.go

const (
	ChangefeedInstanceTypeBusinessTypeNormal = "normal"
	ChangfeedInstanceTypeBusinessTypeDR      = "dr"
)

// gen:qs
type (
	Backup struct {
		Updatable
		Versioned
		DeletedAt  *time.Time `gorm:"type:TIMESTAMP;deleted_at"`
		FinishedAt *time.Time `gorm:"type:TIMESTAMP;finished_at"`

		BackupScheduleID  ID `gorm:"index;not null"`
		BackupSize        int
		BackupSchedule    *BackupSchedule   `gorm:"foreignkey:BackupScheduleID"`
		Storage           string            `gorm:"type:VARCHAR(1024);not null;default:'';"` // directory of backup，the default value is bucket uri + subdirectory name
		TableFilter       TableFilter       `gorm:"type:TEXT"`                               // table filter expression, it is the array of "db.tables" format
		ExpectedBackupTS  string            `gorm:"type:VARCHAR(255);expected_backup_ts"`    // specify start TSO of backup
		Targets           string            `gorm:"type:VARCHAR(4096);not null"`             // filter expression，unknown field，don't move please.  todo: confirm the use
		ClusterVersion    string            `gorm:"type:VARCHAR(255);not null"`
		Status            BackupStatus      `gorm:"type:ENUM('pending','running','success','failed', 'skipped', 'terminated');not null;index:idx_c_s,priority:2;"` // enum
		ErrMsg            string            `gorm:"type:VARCHAR(4096);"`
		CleanedUp         *bool             `gorm:"default:false"`
		LeakChecked       *bool             `gorm:"default:false"`
		OldBackupName     string            `gorm:"type:VARCHAR(255);not null"`
		StartTimeStamp    string            `gorm:"type:VARCHAR(255);start_ts"` // start TSO of backup
		ClusterSnapshotID ID                `gorm:"index;not null"`
		BucketID          ID                `gorm:"not null;default:0"`
		CleanPolicy       BackupCleanPolicy `gorm:"type:ENUM('delete','retain');not null;default:'delete'"`
		// only cluster in white-list can use this feature.
		IsVolumeBackup *bool `gorm:"default:false"`
		IsInternal     bool  `gorm:"default:false"`
		// Protected prevents the backup from being deleted by user API or auto cleanup.
		Protected bool `gorm:"default:false"`

		TenantID  ID `gorm:"type:bigint(20) unsigned;not null;index"`
		ProjectID ID `gorm:"type:bigint(20) unsigned;not null;index;"`
		ClusterID ID `gorm:"type:bigint(20) unsigned;not null;index:idx_c_s,priority:1;"`

		PiTRSettingSnapshot   *PiTRSetting   `gorm:"type:JSON;serializer:json"`
		BucketSettingSnapshot *BucketSetting `gorm:"type:JSON;serializer:json"`
		ExtraInfo             *BackupExtra   `gorm:"type:JSON;serializer:json"`
	}

	BackupSchedule struct {
		Updatable
		Versioned
		DeletedAt *time.Time `gorm:"type:TIMESTAMP;deleted_at"`

		Name      string     `gorm:"type:VARCHAR(255);not null"`
		Type      BackupType `gorm:"type:ENUM('auto','manual');not null"` // enum
		ClusterID ID         `gorm:"index;not null"`
		// deprecated, use RetentionPeriod instead
		MaxBackups      uint64            `gorm:"not null"`
		BackupCycle     BackupCycle       `gorm:"type:ENUM('daily','weekly');not null"`
		Schedule        string            `gorm:"type:VARCHAR(255);not null"`
		Directory       string            `gorm:"type:VARCHAR(1024);not null;default:'';"` // save path uri,parent directory of the backup
		Targets         string            `gorm:"type:VARCHAR(4096);not null"`             // filter expression，unknown field，don't move please.  todo: confirm the use
		UserID          ID                `gorm:"not null"`
		Method          BackupMethod      `gorm:"type:ENUM('full','incremental');not null"` // enum
		Description     string            `gorm:"type:VARCHAR(4096);not null"`
		NextBackupTime  *time.Time        `gorm:"type:TIMESTAMP"`
		RetentionPeriod uint64            `gorm:"default:0;not null"` // backup retention period, we will clean log backup and snapshot backup if it is overtime.
		LastCleanAt     *time.Time        `gorm:"type:TIMESTAMP"`
		ClusterType     BackupClusterType `gorm:"type:ENUM('dedicated','serverless');not null"` // enum when all backup deleted, then mark it true
		CleanedUp       *bool             `gorm:"default:false"`
	}

	BackupExportTarget struct {
		// TargetURI is the uri of the backup export target, for example:
		// AWS:  s3://your-s3-bucket-name/dir1/dir2/dir3/
		// GCP:  gs://your-gcs-bucket-name/dir1/dir2/dir3/
		// Azure: https://your-azblob-bucket-name.blob.core.windows.net/your-container-name/dir1/dir2/dir3/
		TargetURI string `gorm:"type:VARCHAR(4096);not null"`

		// Region is the region of the target S3 bucket, such as: us-east-1
		Region string `gorm:"type:VARCHAR(128)"`

		// DryRun controls export a single manifest to target bucket, in order to test the connectivity.
		DryRun bool `gorm:"default:false"`
	}

	BackupExport struct {
		Updatable
		Versioned
		DeletedAt  *time.Time `gorm:"type:TIMESTAMP;deleted_at"`
		FinishedAt *time.Time `gorm:"type:TIMESTAMP;finished_at"`

		// UserID is tenant.User on web console
		UserID    ID                 `gorm:"not null"`
		TenantID  ID                 `gorm:"not null;index"`
		ProjectID ID                 `gorm:"not null;index;"`
		ClusterID ID                 `gorm:"not null;index;"`
		BackupID  ID                 `gorm:"not null;index"`
		Backup    *Backup            `gorm:"foreignkey:BackupID"`
		Provider  string             `gorm:"type:VARCHAR(16);not null"` // cloud provider, like: aws, gcp, azure
		Region    string             `gorm:"type:VARCHAR(64);not null"` // cloud region, like: us-east-1, us-west-2, etc.
		Name      string             `gorm:"type:VARCHAR(255);not null"`
		Status    BackupExportStatus `gorm:"type:ENUM('pending','running','failed','success','canceled');not null"`
		StatusMsg string             `gorm:"type:VARCHAR(4096);"`
		Target    BackupExportTarget `gorm:"type:JSON;serializer:json"`
		JobID     string             `gorm:"type:VARCHAR(512);"`
		DAGInsID  string             `gorm:"type:VARCHAR(256);not null;"`
	}

	// ServerlessBackup stores backups of TiDB cluster on serverless tier
	ServerlessBackup struct {
		Base
		DeletedAt  *time.Time `gorm:"type:TIMESTAMP;deleted_at"`
		Region     string     `gorm:"type:VARCHAR(64);not null;index:idx_region_time,priority:1"`
		Name       string     `gorm:"type:VARCHAR(255);not null"`
		BackupID   uint64     `gorm:"not null"`
		BackupTime time.Time  `gorm:"type:TIMESTAMP;not null;index:idx_region_time,priority:2"`
	}

	// ServerlessUserClusterBackup stores backup of user cluster, it just references ServerlessBackup
	ServerlessUserClusterBackup struct {
		Updatable
		ClusterID          ID                                `gorm:"not null;index"`
		ProjectID          ID                                `gorm:"not null"`
		TenantID           ID                                `gorm:"not null"`
		ServerlessBackupID ID                                `gorm:"not null"`
		ServerlessBackup   *ServerlessBackup                 `gorm:"foreignkey:ServerlessBackupID"`
		ScheduleTime       *time.Time                        `gorm:"type:TIMESTAMP;schedule_time"`
		Region             string                            `gorm:"type:VARCHAR(64);not null"`
		Status             ServerlessUserClusterBackupStatus `gorm:"type:ENUM('pending','running','success','failed','skipped','terminated');not null"`
	}

	BackupSetting struct {
		Updatable
		Versioned
		ClusterID         ID                  `gorm:"type:bigint(20) unsigned;not null;uniqueIndex;"`
		ProjectID         ID                  `gorm:"type:bigint(20) unsigned;not null;index;"`
		TenantID          ID                  `gorm:"type:bigint(20) unsigned;not null;index;"`
		Status            BackupSettingStatus `gorm:"type:ENUM('creating','editing_region','editing_pitr','normal','deleted');"`
		InfraProviderType string              `gorm:"type:VARCHAR(16);not null"`

		VolumeBackup   *VolumeBackupSetting `gorm:"type:JSON;serializer:json"`
		AutoBackup     *AutoBackupSetting   `gorm:"type:JSON;serializer:json"`
		PiTR           *PiTRSetting         `gorm:"type:JSON;serializer:json"`
		Bucket         *BucketSetting       `gorm:"type:JSON;serializer:json"`
		CloudAccountID *string              `gorm:"type:VARCHAR(256);"` // cloud account id
		Action         BackupSettingAction  `gorm:"type:JSON;serializer:json"`
	}

	Bucket struct {
		// indexes for bucket
		// 1. scope_tenant_id_project_id_regions_cluster_id **unique** idx (for get active cluster/project level bucket )
		// 2. provider_scope_status normal idx (for bucket cleanup scan)

		// we MUST not use the gorm's auto migrate
		// because the prod meta db version is `v5.4` and not support multi schema change.
		// so we use raw sql to create index, see sql in `record_202302221327_bucket.go`

		// biz ? i don't know why this filed is string?
		Updatable
		Versioned
		TenantID  string `gorm:"type:VARCHAR(32);not null"`
		ProjectID string `gorm:"type:VARCHAR(32);index:idx_project_cluster;not null"`
		ClusterID string `gorm:"type:VARCHAR(32);index:idx_project_cluster;not null;default:'';"`

		Description string       `gorm:"type:TEXT"`
		Extension   JSONMap      `gorm:"type:VARCHAR(512);not null;default:'';"`
		Status      BucketStatus `gorm:"type:VARCHAR(16);not null;default:'creating';"`
		Name        string       `gorm:"type:VARCHAR(128);uniqueIndex:idx_name_dtime_accountid;default:null;"` // bucket name,also the CRName.
		Provider    string       `gorm:"type:VARCHAR(16);not null"`                                            // cloud provider,like: aws,gcp
		Regions     List         `gorm:"type:VARCHAR(64);not null"`                                            // cluster region has to be the first
		Scope       BucketScope  `gorm:"type:VARCHAR(32);not null"`                                            // Scope is used to define the bucket usable range，like cluster,project

		InfraProviderType string           `gorm:"type:VARCHAR(16);not null"`             // cloud infra provider type,like: aws,gcp/gke/eks
		AwsKmsArn         string           `gorm:"type:VARCHAR(255);not null;default:''"` // cmek arn for aws
		AzureCmekParams   *AzureCmekParams `gorm:"type:JSON;serializer:json"`             // cmek params for azure

		// time
		CloudAccountID *string `gorm:"type:VARCHAR(256);uniqueIndex:idx_name_dtime_accountid"` // cloud account id
		DeletedTime    *int64  `gorm:"uniqueIndex:idx_name_dtime_accountid;not null;default:0;"`
	}

	AWSBucketConfiguration struct {
		Updatable
		Versioned

		BucketID                ID              `gorm:"uniqueIndex;not null"`
		BucketPolicy            AWSBucketPolicy `gorm:"type:JSON;serializer:json"`
		VersioningEnable        *bool
		GCRuleEnable            *bool
		BucketReplicationPolicy AWSBucketReplicationPolicy `gorm:"type:JSON;serializer:json"`
		RTCEnabled              *bool                      `gorm:"column:rtc_enabled"`
	}

	AWSBucketReplication struct {
		Updatable
		Versioned

		SourceBucketID ID     `gorm:"uniqueIndex:sID_tID_prefix,priority:20;not null"`
		TargetBucketID ID     `gorm:"uniqueIndex:sID_tID_prefix,priority:30;not null"`
		ClusterID      ID     `gorm:"not null"`
		Prefix         string `gorm:"uniqueIndex:sID_tID_prefix,priority:40;type:VARCHAR(255);not null"`

		Status AWSBucketReplicationStatus `gorm:"type:ENUM('creating','created','deleting','deleted');not null"`
	}

	GCPBucketConfiguration struct {
		Updatable
		Versioned

		BucketID     ID              `gorm:"uniqueIndex;not null"`
		BucketPolicy GCPBucketPolicy `gorm:"type:JSON;serializer:json"`
	}

	// LogBackup is record of log backups.
	LogBackup struct {
		Updatable
		Versioned
		DeletedAt  *time.Time `gorm:"type:TIMESTAMP;deleted_at"`
		FinishedAt *time.Time `gorm:"type:TIMESTAMP;finished_at"`
		ScheduleAt *time.Time `gorm:"type:TIMESTAMP;finished_at"`
		PendingAt  *time.Time `gorm:"type:TIMESTAMP;finished_at"`
		StoppingAt *time.Time `gorm:"type:TIMESTAMP;finished_at"`

		BackupScheduleID ID                `gorm:"index;not null"`
		BackupSchedule   *BackupSchedule   `gorm:"foreignkey:BackupScheduleID"`
		Name             string            `gorm:"type:VARCHAR(255);not null"`
		Storage          string            `gorm:"type:VARCHAR(1024);not null;default:'';"` // directory of backup，the default value is bucket uri + sub directory name
		Regions          string            `gorm:"type:VARCHAR(255);not null;"`
		Status           LogBackupStatus   `gorm:"type:ENUM('scheduled','pending','running','stopping','stopped','pausing','paused','resuming','failed');not null"` // enum
		StartTS          *string           `gorm:"type:VARCHAR(255);"`
		CheckpointTS     string            `gorm:"type:VARCHAR(255);"`
		TruncateUntil    string            `gorm:"type:VARCHAR(255);"`
		ErrMsg           string            `gorm:"type:VARCHAR(4096);"`
		CleanedUp        *bool             `gorm:"default:false"`
		TenantID         ID                `gorm:"not null"`
		ProjectID        ID                `gorm:"index;not null"`
		ClusterID        ID                `gorm:"index;not null"`
		BucketID         ID                `gorm:"not null;default:0"`
		CleanPolicy      BackupCleanPolicy `gorm:"type:ENUM('delete','retain');not null;default:'delete'"`

		PiTRSettingSnapshot   *PiTRSetting   `gorm:"type:JSON;serializer:json"`
		BucketSettingSnapshot *BucketSetting `gorm:"type:JSON;serializer:json"`

		SizeCalculatorFinished bool            `gorm:"default:false;"`
		ExtraInfo              *LogBackupExtra `gorm:"type:JSON;serializer:json"`
	}

	LogBackupSize struct {
		Base
		LogBackupID   ID         `gorm:"not null;uniqueIndex:idx_l_e_t,priority:1"` // use priority to make index to be (log_backup_id, end_ts)
		LogBackup     *LogBackup `gorm:"foreignkey:LogBackupID"`
		TotalSize     int64      `gorm:"not null;default:0;comment:the unit is byte"`
		IncreasedSize int64      `gorm:"not null;default:0;comment:the unit is byte"`
		StartTS       time.Time  `gorm:"type:TIMESTAMP;not null"`
		EndTS         time.Time  `gorm:"type:TIMESTAMP;not null;uniqueIndex:idx_l_e_t,priority:2"`
	}

	// LogBackupTruncate is record of log backup truncates.
	LogBackupTruncate struct {
		Updatable
		Versioned
		DeletedAt  *time.Time `gorm:"type:TIMESTAMP;deleted_at"`
		FinishedAt *time.Time `gorm:"type:TIMESTAMP;finished_at"`
		RunningAt  *time.Time `gorm:"type:TIMESTAMP;finished_at"`

		LogBackupID ID                      `gorm:"index;not null"`
		LogBackup   *LogBackup              `gorm:"foreignkey:LogBackupID"`
		Storage     string                  `gorm:"type:VARCHAR(1024);not null;default:'';"`                    // directory of backup，the default value is bucket uri + sub directory name
		Status      LogBackupTruncateStatus `gorm:"type:ENUM('pending','running','success','failed');not null"` // enum
		Until       string                  `gorm:"type:VARCHAR(255);"`
		ErrMsg      string                  `gorm:"type:VARCHAR(4096);"`
	}

	// CompactBackup is record of compact backup tasks.
	CompactBackup struct {
		Updatable
		Versioned
		DeletedAt  *time.Time `gorm:"type:TIMESTAMP;deleted_at"`
		FinishedAt *time.Time `gorm:"type:TIMESTAMP;finished_at"`

		LogBackupID ID                  `gorm:"index;not null"`
		LogBackup   *LogBackup          `gorm:"foreignkey:LogBackupID"`
		ClusterID   ID                  `gorm:"index;not null"`
		Status      CompactBackupStatus `gorm:"type:ENUM('pending','running','success','failed','deleting','deleted');not null"`
		// BucketID specifies which bucket to use for compaction.
		// Defaults to 0, meaning the primary bucket from the log backup storage is used.
		BucketID ID `gorm:"not null;default:0"`

		// StartTS is the TSO string for the compaction range start (inclusive).
		// When set by the caller, CreateCompactBackupTaskAction uses it directly
		// instead of deriving from LogBackup.StartTS.
		StartTS string `gorm:"type:VARCHAR(255);default:'';not null"`
		// EndTS is the TSO string for the compaction range end (inclusive).
		// When set by the caller, CreateCompactBackupTaskAction uses it directly
		// instead of deriving from LogBackup.CheckpointTS.
		EndTS string `gorm:"type:VARCHAR(255);default:'';not null"`

		DownstreamCompactID *string `gorm:"type:VARCHAR(255);"`
		ErrMsg              string  `gorm:"type:VARCHAR(4096);"`
	}

	// PitBackupSwitch is record of the real status of pit backup switch.
	PitBackupSwitch struct {
		Updatable
		Versioned
		TenantID  ID `gorm:"not null"`
		ProjectID ID `gorm:"index;not null"`
		ClusterID ID `gorm:"type:bigint(20) unsigned;not null;uniqueIndex;"`

		// Turn is the monotonically increasing PiTR enable generation for this
		// cluster. It is part of the pause-guard custom-status name because a
		// log-backup ID can change during a PiTR restart.
		Turn uint64 `gorm:"type:bigint(20) unsigned;not null;default:0"`

		Status PitBackupSwitchStatus `gorm:"type:ENUM('disabled','waiting_for_enabling','enabling','enabled','disabling','waiting_for_restarting','restarting');not null"` // enum
		Action PitBackupSwitchAction `gorm:"type:JSON;serializer:json"`
	}
)

func (b *ServerlessUserClusterBackup) BackupTime() time.Time {
	if b.ScheduleTime != nil {
		return *b.ScheduleTime
	}
	return b.CreatedAt
}

// gen:qs
type (
	Restore struct {
		Updatable
		Versioned

		Name       string     `gorm:"type:VARCHAR(255);not null"`
		FinishedAt *time.Time `gorm:"finished_at;type:TIMESTAMP;"`
		ClusterID  ID         `gorm:"index;not null"`
		// UserID is tenant.User on web console
		UserID      ID          `gorm:"not null"`
		Targets     string      `gorm:"type:VARCHAR(4096);not null"` // filter expression
		Mode        RestoreMode `gorm:"type:ENUM('snapshot','pitr','volume-snapshot');not null;default:'snapshot'"`
		RestoredTS  int64       `gorm:"restored_ts;type:bigint"`
		Region      *string     `gorm:"region;type:VARCHAR(255)"`
		BackupID    ID          `gorm:"not null"`
		Backup      *Backup     `gorm:"foreignkey:BackupID"`
		LogBackupID ID          // log backup id, just for making a record
		LogBackup   *LogBackup  `gorm:"foreignkey:LogBackupID"`
		// 'pending','running','success','failed' will be set by restoring with workflow
		Status     RestoreStatus `gorm:"type:ENUM('pending','running','success','failed','finished','deleting','deleted');not null"` // enum
		ErrMsg     string        `gorm:"type:VARCHAR(4096);"`
		Progresses string        `gorm:"type:TEXT"`
		// RequestArgs    string        `gorm:"type:TEXT"` // for cloud-native arch cluster, it's *core.CreateClusterReq
		RequestArgsV2  string `gorm:"type:TEXT"` // for 3-layer arch cluster, it's *mgmtv1.RestoreDedicatedClusterRequest
		IsClusterReady bool   `gorm:"index;default:false;not null"`
		// Using "restore.KernelMetrics != nil" as determine logic of restore with workflow is counterintuitive. Because:
		// Firstly, the cost is relatively small,
		// Secondly, the inability to introduce feature switches can lead to circular dependencies
		KernelMetrics *RestoreKernelMetrics `gorm:"type:JSON;serializer:json"`

		TenantID  ID `gorm:"not null"`
		ProjectID ID `gorm:"not null"`

		// BucketID is the bucket where the backup file is stored
		BucketID ID `gorm:"not null"`

		ExtraInfo *RestoreExtra `gorm:"type:JSON;serializer:json"`
	}

	ServerlessRestore struct {
		Updatable
		ClusterID           ID                      `gorm:"not null;index"`
		ProjectID           ID                      `gorm:"not null"`
		TenantID            ID                      `gorm:"not null"`
		Name                string                  `gorm:"type:VARCHAR(255);not null"`
		Mode                RestoreMode             `gorm:"type:ENUM('snapshot','pitr');not null;default:'snapshot'"`
		RestoredTS          int64                   `gorm:"restored_ts;type:BIGINT"`                                    // UNIX timestamp in seconds for "snapshot" & "pitr" restore. For "snapshot", it's the backup time of snapshot
		UserClusterBackupID ID                      `gorm:"not null;index"`                                             // Backup ID for "snapshot" restore
		Status              ServerlessRestoreStatus `gorm:"type:ENUM('pending','running','success','failed');not null"` // enum
		FinishedAt          *time.Time              `gorm:"finished_at;type:TIMESTAMP"`
		ErrMsg              string                  `gorm:"type:VARCHAR(4096)"`
		RestoreBytes        uint64                  `gorm:"type:BIGINT(20) UNSIGNED;not null"`
		Progress            RestoreProgress         `gorm:"type:JSON;not null;"`
		UserID              ID                      `gorm:"not null"`
	}

	// ClassicRestoreAccess tracks temporary IAM roles created for classic restores.
	// It intentionally owns the AWS account identifier so role cleanup does not
	// depend on the lifecycle of the source bucket metadata.
	ClassicRestoreAccess struct {
		Updatable
		RestoreID    string    `gorm:"type:VARCHAR(128);not null;uniqueIndex:uk_classic_restore_access,priority:1"`
		BucketID     ID        `gorm:"type:BIGINT(20) unsigned;not null;uniqueIndex:uk_classic_restore_access,priority:2"`
		BackupID     ID        `gorm:"type:BIGINT(20) unsigned;not null"`
		AWSAccountID string    `gorm:"type:VARCHAR(32);not null"`
		RoleName     string    `gorm:"type:VARCHAR(64);not null"`
		RoleARN      string    `gorm:"type:VARCHAR(255);not null"`
		RequestHash  string    `gorm:"type:CHAR(64);not null"`
		ExpiresAt    time.Time `gorm:"type:TIMESTAMP;not null;index"`
	}
)

//gen:qs
type (
	BillingRecord struct {
		Base
		TenantID    ID                      `gorm:"type:BIGINT unsigned;not null"`
		ProjectID   ID                      `gorm:"type:BIGINT unsigned;not null"`
		ClusterID   ID                      `gorm:"type:BIGINT unsigned;not null;index:idx_c_c_m,priority:1"`
		MeasuredTS  time.Time               `gorm:"type:TIMESTAMP;measured_ts;not null;index:idx_c_c_m,priority:3"`
		SKU         string                  `gorm:"type:VARCHAR(1024);not null"`
		Total       int64                   `gorm:"type:BIGINT;not null"`
		ExtraLabels BillingRecordExtraLabel `gorm:"type:VARCHAR(1024)"`
		Category    string                  `gorm:"type:VARCHAR(128);not null;index:idx_c_c_m,priority:2"`
		Synced      bool                    `gorm:"not null;default:false"`
		CostID      string                  `gorm:"cost_id;type:VARCHAR(64);not null"`
	}
)

type BillingRecordExtraLabel map[string]string

func (labels BillingRecordExtraLabel) Value() (driver.Value, error) {
	b, err := json.Marshal(labels)
	return string(b), err
}

func (labels *BillingRecordExtraLabel) Scan(input interface{}) error {
	return json.Unmarshal(input.([]byte), labels)
}

// RestoreProgress is the progress of restore task
type RestoreProgress struct {
	Step             string `json:"step"`
	Progress         int32  `json:"progress"`
	LastTransitionTS int64  `json:"last_transition_ts"`
}

// Value marshal json
func (o RestoreProgress) Value() (driver.Value, error) {
	b, err := json.Marshal(o)
	return string(b), err
}

// Scan unmarshal json
func (o *RestoreProgress) Scan(src interface{}) error {
	return json.Unmarshal(src.([]byte), o)
}

func (o *RestoreProgress) Equal(other *RestoreProgress) bool {
	return o.Step == other.Step &&
		o.Progress == other.Progress
}

// gen:qs
type (
	BizPrivateLinkEndpoint struct {
		Updatable
		TenantID           ID                       `gorm:"type:bigint(20) unsigned;not null;index:idx_tid_pid_phase,priority:1;"`
		ProjectID          ID                       `gorm:"type:bigint(20) unsigned;not null;index:idx_tid_pid_phase,priority:2;"`
		CloudProvider      CloudProviderType        `gorm:"type:ENUM('aws', 'gcp', 'azure');not null;"`
		Region             string                   `gorm:"type:varchar(64);not null;"`
		Zones              AZs                      `gorm:"type:JSON;serializer:json"`
		Name               string                   `gorm:"type:varchar(255);not null;"`
		ServiceName        string                   `gorm:"type:varchar(255);not null;"`
		ServiceRegion      string                   `gorm:"type:varchar(64);not null;"`
		Status             BizPLEndpointStatus      `gorm:"type:enum('creating', 'pending-acceptance', 'available', 'rejected', 'failed', 'deleting', 'deleted');not null;index:idx_tid_pid_phase,priority:3;"`
		ErrMsg             string                   `gorm:"type:text;"`
		RegionalEndpointID string                   `gorm:"type:varchar(255);not null;"`
		CloudEndpointID    string                   `gorm:"type:varchar(255);not null;"`
		DeletedTime        int64                    `gorm:"type:bigint(20);not null;default:0;"`
		IsCustomDomain     bool                     `gorm:"default:false"`
		RootDomainName     string                   `gorm:"type:varchar(1024);not null;"`
		DNSRecords         []BizPLEndpointDNSRecord `gorm:"type:JSON;serializer:json"`
		EndpointAddress    string                   `gorm:"type:varchar(255);not null;"`
		ZonesAddresses     map[string]string        `gorm:"type:JSON;serializer:json"`
		CloudAccountID     string                   `gorm:"type:varchar(255);not null;"`
		Properties         map[string]string        `gorm:"type:JSON;serializer:json"`
		UnusedAt           *time.Time               `gorm:"type:TIMESTAMP;unused_at"`
	}

	BizPrivateLinkEndpointUsage struct {
		Base
		SoftDelete
		BizPrivateLinkEndpointID ID                             `gorm:"type:bigint(20) unsigned;not null;uniqueIndex:uniq_endpoint_type_id,priority:1;"`
		BusinessType             BizPLEndpointUsageBusinessType `gorm:"type:enum('changefeed');not null;uniqueIndex:uniq_endpoint_type_id,priority:2;"`
		BusinessID               ID                             `gorm:"type:bigint(20) unsigned;not null;uniqueIndex:uniq_endpoint_type_id,priority:3;"`
	}
)

type BizPLEndpointStatus string

const (
	BizPLEndpointStatusCreating          BizPLEndpointStatus = "creating"
	BizPLEndpointStatusPendingAcceptance BizPLEndpointStatus = "pending-acceptance"
	BizPLEndpointStatusAvailable         BizPLEndpointStatus = "available"
	BizPLEndpointStatusRejected          BizPLEndpointStatus = "rejected"
	BizPLEndpointStatusFailed            BizPLEndpointStatus = "failed"
	BizPLEndpointStatusDeleting          BizPLEndpointStatus = "deleting"
	BizPLEndpointStatusDeleted           BizPLEndpointStatus = "deleted"
)

type BizPLEndpointDNSRecord struct {
	AvailabilityZone string `json:"availability_zone"`
	RecordName       string `json:"record_name"`
}

type BizPLEndpointUsageBusinessType string

const (
	BizPLEndpointUsageBusinessTypeChangefeed = "changefeed"
)

func (c AdvertisedPatternGroup) Value() (driver.Value, error) {
	b, err := json.Marshal(c)
	return string(b), err
}

func (c *AdvertisedPatternGroup) Scan(input interface{}) error {
	return json.Unmarshal(input.([]byte), c)
}

func (c AdvertisedPatternGroup) Keys() []string {
	var keys []string
	for k := range c {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	return keys
}

type AdvertisedPatternGroup map[string]string

type AZs []string

// gen:qs
type (
	ChangefeedInstanceType struct {
		ID              ID
		Name            string `gorm:"uniqueIndex:idx_type_business_type;type:VARCHAR(64);not null"`
		RCUNum          uint32
		AwsInstanceType string `gorm:"type:VARCHAR(64)"` // deprecated
		GcpInstanceType string `gorm:"type:VARCHAR(64)"` // deprecated
		CpuNum          uint32 // deprecated
		MemoryGi        uint32 // deprecated
		Replicas        uint32 // deprecated
		RPSLimit        uint32
		BusinessType    string `gorm:"uniqueIndex:idx_type_business_type;type:ENUM('normal','dr');not null"`
	}

	CDCInstanceType struct {
		ID       ID
		Name     string `gorm:"type:VARCHAR(64);uniqueIndex:unique_name;not null"`
		RCUNum   uint32 `gorm:"uniqueIndex:unique_rcu;not null"`
		CpuNum   uint32
		MemoryGi uint32
		Replicas uint32
	}

	CDCMachineType struct {
		ID               ID
		CpuNum           uint32 `gorm:"uniqueIndex:unique_cpu_mem,priority:1;not null"`
		MemoryGi         uint32 `gorm:"uniqueIndex:unique_cpu_mem,priority:2;not null"`
		StorageGi        uint32
		AWSMachineType   string `gorm:"type:VARCHAR(64)"`
		GCPMachineType   string `gorm:"type:VARCHAR(64)"`
		AzureMachineType string `gorm:"type:VARCHAR(64)"`
		AWSStorageType   string `gorm:"type:VARCHAR(64)"`
		GCPStorageType   string `gorm:"type:VARCHAR(64)"`
		AzureStorageType string `gorm:"type:VARCHAR(64)"`
	}
)

// gen:qs
type (
	BizChangefeed struct {
		Versioned
		SoftDelete
		ID                ID                              `gorm:"primary_key;not null;autoIncrement:false"`
		CreatedAt         time.Time                       `gorm:"created_at;type:TIMESTAMP;not null;<-:create"` // create and read only
		UpdatedAt         *time.Time                      `gorm:"updated_at;type:TIMESTAMP"`
		Name              string                          `gorm:"type:VARCHAR(128)"`
		ClusterID         ID                              `gorm:"type:bigint(20) unsigned;not null;index"`
		ProjectID         ID                              `gorm:"type:bigint(20) unsigned;not null;index"`
		TenantID          ID                              `gorm:"type:bigint(20) unsigned;not null;index"`
		TiCDCClusterID    ID                              `gorm:"type:bigint(20) unsigned"`
		DownstreamType    TiCDCChangefeedDownStreamType   `gorm:"type:ENUM('mysql', 'kafka', 'serverless_tidb', 's3', 'gcs', 'pulsar', 'azblob');not null"`
		DownstreamParams  string                          `gorm:"type:MEDIUMTEXT"`
		SpecificationCode string                          `gorm:"type:VARCHAR(255);not null"`
		NetworkType       ChangefeedDownstreamNetworkType `gorm:"type:ENUM('unknown', 'internet','vpc_peering', 'private_link', 'private_service_connect', 'manually_private_link', 'manually_private_service_connect');not null"`
		NetworkInfo       string                          `gorm:"type:MEDIUMTEXT"`
		DeployType        TiCDCClusterDeployType          `gorm:"type:ENUM('dedicated', 'shared');not null"`
		OperationStatus   BizChangefeedOperationStatus    `gorm:"type:ENUM('creating', 'normal', 'scaling', 'paused', 'deleting', 'deleted', 'failed');not null"`
		CreatorID         ID                              `gorm:"type:bigint(20) unsigned;not null"`
		ErrMsg            string                          `gorm:"type:TEXT"`
		IsRegional        bool                            `gorm:"default:false"` // if the corresponding ticdc cluster is deployed by regional control plane. It's always 'true' now
		// BizPLEndpointID it's id of BizKafkaPrivateLinkEndpoint if the downstream is kafka.
		// it's id of DFPrivateLinkEndpoint if the downstream is serverless_tidb
		BizPLEndpointID ID `gorm:"type:bigint(20) unsigned;default:0;"`
	}
)

func (c *BizChangefeed) GetID() ID {
	return c.ID
}

func (c *BizChangefeed) GenKernelChangefeedID() string {
	return fmt.Sprintf("changefeed-%d", c.ID)
}

// gen:qs
type (
	TiCDCCluster struct {
		Updatable
		Versioned

		InfraProviderType string `gorm:"type:VARCHAR(16);not null"`
		CloudAccountID    string `gorm:"type:VARCHAR(256);not null;"` // cloud account id

		ClusterID ID `gorm:"type:bigint(20) unsigned;not null;index;"` // up stream cluster id
		ProjectID ID `gorm:"type:bigint(20) unsigned;not null;index;"`
		TenantID  ID `gorm:"type:bigint(20) unsigned;not null;index;"`

		Name              string                    `gorm:"type:VARCHAR(255);uniqueIndex:unique_name_deleted_time,priority:1;not null;"` // kernel cluster id
		DeletedAt         *time.Time                `gorm:"type:TIMESTAMP;deleted_at"`
		DeletedTime       int64                     `gorm:"type:bigint(20);not null;default:0;uniqueIndex:unique_name_deleted_time,priority:2"`
		BusinessType      TiCDCClusterBizType       `gorm:"type:ENUM('changefeed', 'recovery_group');not null;"`
		DeployType        TiCDCClusterDeployType    `gorm:"type:ENUM('dedicated', 'shared');not null;default:'dedicated'"` // TODO use enum type
		Version           string                    `gorm:"type:VARCHAR(255)"`
		Region            string                    `gorm:"type:VARCHAR(255)"`
		LogicalRegion     string                    `gorm:"type:VARCHAR(255)"`
		SpecificationCode string                    `gorm:"type:VARCHAR(255)"`
		Status            TiCDCClusterStatus        `gorm:"type:ENUM('creating', 'running', 'deleting', 'deleted', 'scaling', 'upgrading');not null;"`
		Action            TiCDCClusterSettingAction `gorm:"type:JSON;serializer:json"`
		IsRegional        bool                      `gorm:"default:false"` // if the ticdc cluster is deployed by regional control plane. It's always 'true' now
		IsNewKernel       bool                      `gorm:"default:false"` // if the ticdc cluster is using new kernel
	}

	TiCDCBizChangefeedResource struct {
		Updatable
		Versioned

		TiCDCClusterID        ID     `gorm:"type:bigint(20) unsigned;not null;uniqueIndex:unique_cdc_changefeed_id,priority:1"`
		BizChangefeedID       ID     `gorm:"type:bigint(20) unsigned;not null;uniqueIndex:unique_cdc_changefeed_id,priority:2"`
		ExpectedSpecification string `gorm:"type:VARCHAR(255)"`
		ActualSpecification   string `gorm:"type:VARCHAR(255)"`
	}

	TiCDCChangefeed struct {
		Updatable
		Versioned

		KernelChangefeedID string     `gorm:"type:VARCHAR(255);not null;default:'';uniqueIndex:uniq_kernel_id"`
		DeletedAt          *time.Time `gorm:"type:TIMESTAMP;deleted_at"`

		ProjectID         ID                  `gorm:"type:bigint(20) unsigned;not null;index;"`
		TenantID          ID                  `gorm:"type:bigint(20) unsigned;not null;index;"`
		TiCDCClusterID    ID                  `gorm:"type:bigint(20) unsigned;not null;index;"`
		TiCDCCluster      *TiCDCCluster       `gorm:"foreignkey:TiCDCClusterID"`
		UpStreamClusterID ID                  `gorm:"type:bigint(20) unsigned;not null;index;"`
		BusinessType      TiCDCClusterBizType `gorm:"type:ENUM('changefeed', 'recovery_group');not null;default:'recovery_group';index:idx_biz_type_id,priority:1"`
		// BusinessID is replication link item id if business type is recovery_group;
		// is biz_changefeed id if business type is changefeed.
		BusinessID ID `gorm:"type:bigint(20) unsigned;not null;default:0;index:idx_biz_type_id,priority:2"`

		DownStreamType TiCDCChangefeedDownStreamType `gorm:"type:ENUM('mysql', 'kafka', 'serverless_tidb', 's3', 'gcs', 'pulsar');not null"`
		KernelMetrics  TiCDCChangefeedKernelMetrics  `gorm:"type:JSON;serializer:json"`

		// KernelStatus tracks whether the kernel-side changefeed exists.
		// Default 'active' keeps existing rows backwards-compatible without
		// backfill; the create DAG explicitly writes 'pending' before calling
		// regional and flips to 'active' once the kernel ack is observed.
		KernelStatus TiCDCChangefeedKernelStatus `gorm:"type:ENUM('pending','active');not null;default:'active'"`
	}

	TiCDCCustomStorage struct {
		Updatable
		TiCDCClusterID ID
		SizeGi         uint32
		ThroughputMbs  uint32
		Iops           uint32
	}
)

type TiCDCChangefeedKernelMetrics struct {
	ID            string                             `json:"id,omitempty"`
	FeedState     ChangefeedKernelStatus             `json:"state,omitempty"`
	CheckpointTSO uint64                             `json:"checkpoint_tso,omitempty"`
	RunningError  *TiCDCChangefeedKernelRunningError `json:"error,omitempty"`
	UpdatedAt     *time.Time                         `json:"updated_at,omitempty"`
}

type TiCDCChangefeedKernelRunningError struct {
	Addr    string `json:"addr,omitempty"`
	Code    string `json:"code,omitempty"`
	Message string `json:"message,omitempty"`
}

func (e *TiCDCChangefeedKernelRunningError) Error() string {
	return fmt.Sprintf("code: %s, message: %s, ticdc addr: %s", e.Code, e.Message, e.Addr)
}

type PLEDnsEntries struct {
	RegionalAddress string
	ZonalAddresses  map[string]string
}

func (e PLEDnsEntries) Value() (driver.Value, error) {
	b, err := json.Marshal(e)
	return string(b), err
}

func (e *PLEDnsEntries) Scan(input interface{}) error {
	return json.Unmarshal(input.([]byte), e)
}

type DNSRecords map[string]DNSRecord

type DNSRecord struct {
	Type      string
	Name      string
	Ttl       uint64
	Addresses []string
}

func (r DNSRecords) Value() (driver.Value, error) {
	b, err := json.Marshal(r)
	return string(b), err
}

func (r *DNSRecords) Scan(input interface{}) error {
	return json.Unmarshal(input.([]byte), r)
}

// gen:qs
type (
	TiDBClusterGCSwitch struct {
		Updatable
		Versioned

		ProjectID ID `gorm:"type:bigint(20) unsigned;not null"`
		TenantID  ID `gorm:"type:bigint(20) unsigned;not null"`
		ClusterID ID `gorm:"type:bigint(20) unsigned;not null;index:idx_tcgc_cluster_id,unique;"`
		UserID    ID `gorm:"type:bigint(20) unsigned;"`

		DisableGCBusinessInfo TiDBClusterGCSwitchBusinessInfo `gorm:"type:json;serializer:json"`

		Status TiDBClusterGCSwitchStatus `gorm:"type:enum('enabled', 'disabled');not null"`
	}
)

type TiDBClusterGCSwitchBusinessInfoItem struct {
	BusinessID   ID
	BusinessType TiDBClusterGCSwitchBusinessType
}

type TiDBClusterGCSwitchBusinessInfo struct {
	Items []TiDBClusterGCSwitchBusinessInfoItem
}

// gen:qs
type (
	KernelChangefeedTrafficRecord struct {
		Updatable
		TenantID               ID                         `gorm:"type:BIGINT unsigned;not null"`
		ProjectID              ID                         `gorm:"type:BIGINT unsigned;not null"`
		ClusterID              ID                         `gorm:"type:BIGINT unsigned;not null;index:idx_cluster,priority:1"`
		ChangefeedID           ID                         `gorm:"index;not null"`
		Region                 string                     `gorm:"type:VARCHAR(255)"`
		BusinessType           KernelTrafficBizType       `gorm:"enum('recovery_group', 'private_link');not null"`
		SrcCloudProvider       CloudProviderType          `gorm:"type:ENUM('aws', 'gcp','azure');not null"`
		SecondaryCloudProvider CloudProviderType          `gorm:"type:ENUM('aws', 'gcp', 'azure');not null"`
		SecondaryRegion        string                     `gorm:"type:VARCHAR(255)"`
		StartTS                *time.Time                 `gorm:"type:timestamp;"`
		EndTS                  *time.Time                 `gorm:"type:timestamp;"`
		Traffic                int64                      `gorm:"not null;default:0;"`
		Extension              ChangefeedTrafficExtension `gorm:"type:VARCHAR(1024)"`
		Synced                 bool                       `gorm:"not null;default:false"`
	}
)

type ChangefeedTrafficExtension map[string]string

func (labels ChangefeedTrafficExtension) Value() (driver.Value, error) {
	b, err := json.Marshal(labels)
	return string(b), err
}

func (labels *ChangefeedTrafficExtension) Scan(input interface{}) error {
	return json.Unmarshal(input.([]byte), labels)
}

func (c ComponentStates) Value() (driver.Value, error) {
	b, err := json.Marshal(c)
	return string(b), err
}

func (c *ComponentStates) Scan(input interface{}) error {
	return json.Unmarshal(input.([]byte), c)
}

type ComponentStates struct {
	Port       int32            `json:"port"`
	Components []ComponentState `json:"components"`
}

type ComponentState struct {
	ComponentType   ComponentName `json:"component_type"`
	RegionName      string        `json:"region_name"`
	NodeProfileName string        `json:"node_profile_name"`
	Replicas        uint32        `json:"replicas"`
	StorageSizeGi   uint32        `json:"storage_size_gi"`
}

type VolumeBackupSetting struct {
	Enable bool `json:"enable"`
}

func (b *VolumeBackupSetting) Changed(other *VolumeBackupSetting) bool {
	if other == nil {
		return false
	}
	if b == nil {
		return true
	}
	return b.Enable != other.Enable
}

type AutoBackupSetting struct {
	Disable bool `json:"disable"`
}

func (b *AutoBackupSetting) Changed(other *AutoBackupSetting) bool {
	if other == nil {
		return false
	}
	if b == nil {
		return true
	}
	return b.Disable != other.Disable
}

//nolint:nolintlint,gomnd // DefaultChangefeedInstanceTypes is used when initializing DB
var DefaultChangefeedInstanceTypes = []*ChangefeedInstanceType{
	{
		ID:              1,
		Name:            "2rcu",
		RCUNum:          2,
		Replicas:        1,
		RPSLimit:        5000,
		AwsInstanceType: "m5.large",
		GcpInstanceType: "n2-standard-2",
		CpuNum:          2,
		MemoryGi:        8,
		BusinessType:    ChangefeedInstanceTypeBusinessTypeNormal,
	},
	{
		ID:              2,
		Name:            "4rcu",
		RCUNum:          4,
		Replicas:        1,
		RPSLimit:        10000,
		AwsInstanceType: "m5.xlarge",
		GcpInstanceType: "n2-standard-4",
		CpuNum:          4,
		MemoryGi:        16,
		BusinessType:    ChangefeedInstanceTypeBusinessTypeNormal,
	},
	{
		ID:              3,
		Name:            "8rcu",
		RCUNum:          8,
		Replicas:        1,
		RPSLimit:        20000,
		AwsInstanceType: "m5.2xlarge",
		GcpInstanceType: "n2-standard-8",
		CpuNum:          8,
		MemoryGi:        32,
		BusinessType:    ChangefeedInstanceTypeBusinessTypeNormal,
	},
	{
		ID:              4,
		Name:            "16rcu",
		RCUNum:          16,
		Replicas:        2,
		RPSLimit:        40000,
		AwsInstanceType: "m5.2xlarge",
		GcpInstanceType: "n2-standard-8",
		CpuNum:          8,
		MemoryGi:        32,
		BusinessType:    ChangefeedInstanceTypeBusinessTypeNormal,
	},
	{
		ID:              5,
		Name:            "24rcu",
		RCUNum:          24,
		Replicas:        3,
		RPSLimit:        60000,
		AwsInstanceType: "m5.2xlarge",
		GcpInstanceType: "n2-standard-8",
		CpuNum:          8,
		MemoryGi:        32,
		BusinessType:    ChangefeedInstanceTypeBusinessTypeNormal,
	},
	{
		ID:              6,
		Name:            "32rcu",
		RCUNum:          32,
		Replicas:        4,
		RPSLimit:        80000,
		AwsInstanceType: "m5.2xlarge",
		GcpInstanceType: "n2-standard-8",
		CpuNum:          8,
		MemoryGi:        32,
		BusinessType:    ChangefeedInstanceTypeBusinessTypeNormal,
	},
	{
		ID:              7,
		Name:            "40rcu",
		RCUNum:          40,
		Replicas:        5,
		RPSLimit:        100000,
		AwsInstanceType: "m5.2xlarge",
		GcpInstanceType: "n2-standard-8",
		CpuNum:          8,
		MemoryGi:        32,
		BusinessType:    ChangefeedInstanceTypeBusinessTypeNormal,
	},
}

type ZonalDNS struct {
	DNS map[string]string `json:"dns"`
}

func (z ZonalDNS) Value() (driver.Value, error) {
	b, err := json.Marshal(z)
	return string(b), err
}

func (z *ZonalDNS) Scan(input interface{}) error {
	return json.Unmarshal(input.([]byte), z)
}

// gen:qs
type (
	Import struct {
		Updatable
		Versioned

		Name       string `gorm:"type:VARCHAR(128)"`
		OldID      *uint64
		ClusterID  ID           `gorm:"not null" valid:"required"`
		ProjectID  ID           `gorm:"not null"`
		TenantID   ID           `gorm:"not null"`
		Status     ImportStatus `gorm:"type:ENUM('unknown','preparing', 'importing','completed','failed', 'canceling', 'canceled');not null;default:'preparing'"` // enum
		Message    string       `gorm:"type:TEXT"`                                                                                                                // failed message
		StartedAt  *time.Time   `gorm:"type:TIMESTAMP"`
		FinishedAt *time.Time   `gorm:"type:TIMESTAMP;index"`
		Creator    string       `gorm:"type:VARCHAR(255)"`

		CleanUp bool `gorm:"index;default:false;not null"`

		BucketRegion   string `gorm:"type:VARCHAR(255)"`
		AwsRoleArn     string `gorm:"type:VARCHAR(255);not null;default:''"`
		DataFormat     DataFormat
		DataSourceType DataSource

		// we just pass this to lightning's config.
		// for the privilege need see https://docs.pingcap.com/tidb/stable/tidb-lightning-faq#what-is-the-privilege-requirements-for-the-target-database
		TargetUsername string `gorm:"type:VARCHAR(255)"`
		TargetPassword string `gorm:"type:VARCHAR(1024)"`

		Filter          string        `gorm:"type:VARCHAR(255)"`
		CustomCSVFormat string        `gorm:"type:VARCHAR(255)"`
		OptionalArgs    *OptionalArgs `gorm:"type:JSON"`

		// Source data info
		TotalSize         *uint64
		ProcessedDataSize *uint64
		TotalFiles        *uint32
		SourceURL         string        `gorm:"type:VARCHAR(1024)"`
		CurrentTables     CurrentTables `gorm:"type:JSON"`

		// In progress info
		TotalTablesCount           uint32
		CompletedTables            uint32
		PendingTables              uint32
		CompletedPercent           uint32 // 0 ~100
		PostImportCompletedPercent uint32 // 0 ~100

		// Table results
		Tables       []ImportTable `gorm:"foreignkey:ImportID"`
		TableResults ImportTables  `gorm:"type:JSON"`

		TaskID int64

		LocalImportCfg *LocalImportCfg `gorm:"type:JSON"`

		TargetTableInfos TargetTableInfos `gorm:"type:JSON"`

		ServerTier       string     `gorm:"type:VARCHAR(64)"`
		ClusterVersion   string     `gorm:"type:VARCHAR(64)"`
		ComponentVersion string     `gorm:"type:VARCHAR(64);not null;index:idx_component_version"`
		TaskPhase        string     `gorm:"type:VARCHAR(64)"`
		Provider         string     `gorm:"type:VARCHAR(64);not null"`
		Region           string     `gorm:"type:VARCHAR(64);not null"`
		TaskImportingAt  *time.Time `gorm:"type:TIMESTAMP"`
		TaskPostproceAt  *time.Time `gorm:"type:TIMESTAMP"`
	}

	// Deprecated, no need this table
	// ImportTable is the entity model recording status for importing a single table.
	ImportTable struct {
		Updatable

		ImportID             ID                `gorm:"not null;index:idx_import_table,priority:1,unique" valid:"required"`
		TableName            string            `gorm:"not null;type:VARCHAR(255);index:idx_import_table,priority:2,unique"`
		Result               ImportTableResult `gorm:"type:ENUM('success','warning','error');not null;default:'success'"` // enum
		Message              string            `gorm:"type:TEXT"`
		ImportStartedAt      *time.Time        `gorm:"type:TIMESTAMP"`
		ImportFinishedAt     *time.Time        `gorm:"type:TIMESTAMP"`
		PostImportStartedAt  *time.Time        `gorm:"type:TIMESTAMP"`
		PostImportFinishedAt *time.Time        `gorm:"type:TIMESTAMP"`
	}
)

type OptionalArgs struct {
	ObjectNamePattern         string `json:"object_name_pattern"`
	TargetTablePattern        string `json:"target_table_pattern"`
	DuplicationHandlingForSQL string `json:"dup_handling_for_sql,omitempty"`
}

// Value marshal json
func (args OptionalArgs) Value() (driver.Value, error) {
	b, err := json.Marshal(args)
	return string(b), err
}

// Scan unmarshal json
func (args *OptionalArgs) Scan(input interface{}) error {
	return json.Unmarshal(input.([]byte), args)
}

type CurrentTable struct {
	Name             string `json:"name"`
	Size             uint64 `json:"size"`
	CompletedPercent uint32 `json:"completed_percent"` // 0 ~100
}

type CurrentTables struct {
	Tables []*CurrentTable `json:"tables"`
}

// Value marshal json
func (c CurrentTables) Value() (driver.Value, error) {
	b, err := json.Marshal(c)
	return string(b), err
}

// Scan unmarshal json
func (c *CurrentTables) Scan(input interface{}) error {
	return json.Unmarshal(input.([]byte), c)
}

type ImportTables struct {
	Tables []ImportTable
}

func (r ImportTables) Value() (driver.Value, error) {
	b, err := json.Marshal(r)
	return string(b), err
}

func (r *ImportTables) Scan(input interface{}) error {
	return json.Unmarshal(input.([]byte), r)
}

type LocalImportCfg struct {
	FileName         string `json:"file_name"`
	TargetDb         string `json:"target_db"`
	TargetTable      string `json:"target_table"`
	OriginalFileName string `json:"original_file_name"`
	TotalSize        uint32 `json:"total_size"`
}

func (c LocalImportCfg) Value() (driver.Value, error) {
	b, err := json.Marshal(c)
	return string(b), err
}

func (c *LocalImportCfg) Scan(input interface{}) error {
	return json.Unmarshal(input.([]byte), c)
}

type TargetTableInfos struct {
	Infos []TargetTableInfo
}

type TargetTableInfo struct {
	Schema     string `json:"schema"`
	Table      string `json:"table"`
	CustomFile string `json:"custom_file"`
}

func (t TargetTableInfos) Value() (driver.Value, error) {
	b, err := json.Marshal(t)
	return string(b), err
}

func (t *TargetTableInfos) Scan(input interface{}) error {
	return json.Unmarshal(input.([]byte), t)
}

// AzureCmekParams defines parameters are used for CMEK, refer to https://pingcap.feishu.cn/wiki/LtrvwoJU0ip7m9kk1k6cljnhnJe
type AzureCmekParams struct {
	// ApplicationID is the Application (client) ID of App registration
	// For example: "5d9498fa-0375-47cc-a762-cfb7fe702fe6"
	ApplicationID string `json:"applicationID,omitempty"`
	// ManagedIdentityResourceID is the resource ID of the managed identity
	// which is already attached to the App registration above with Federated credential.
	// For example: "/subscriptions/99549169-6cee-4263-8491-924a3011ee31/resourcegroups/chenfei-test/providers/Microsoft.ManagedIdentity/userAssignedIdentities/uami-cmek-test"
	ManagedIdentityResourceID string `json:"managedIdentityResourceID,omitempty"`
	// keyURI is the URI of the Key vault key used for encryption, it looks like this
	// "https://{keyVaultName}.vault.azure.net/keys/{keyName}/{keyVersion}".
	KeyVaultName string `json:"keyVaultName,omitempty"`
	KeyName      string `json:"keyName,omitempty"`
}

type AWSBucketPolicy struct {
	AllowARNs []string `json:"allow_ARNs"`
}

type AWSBucketReplicationPolicy struct {
	RuleIDs []string `json:"rule_IDs"`
}

type GCPBucketPolicy struct {
	ServiceAccounts []string `json:"service_accounts"`
}

type PiTRSetting struct {
	// the user expected status ,the real status is in PitBackupSwitch.Status
	Enable bool `json:"enable"`
}

type BucketSetting struct {
	DualRegionEnable bool                  `json:"dual_region_enable"`
	RTCEnabled       *bool                 `json:"rtc_enabled,omitempty"`
	Buckets          []BackupSettingBucket `json:"buckets"`
	Prefix           *string               `json:"prefix"`
}

type BackupExtra struct {
	On3LayerArch bool `json:"on_3layer_arch,omitempty"`

	// BackupCRNameVersion holds the version of the snapshot backup CR name passed to cluster-server.
	// * legacy (empty): "{schedule_name}-{backup_id}"
	// * v1: "backup-{backup_id}" (hashed with UniqueIdentifierLte63 when needed)
	BackupCRNameVersion string `json:"backup_cr_name_version,omitempty"`
}

type LogBackupExtra struct {
	On3LayerArch bool `json:"on_3layer_arch,omitempty"`

	// BackupCRNameVersion holds the version of the log backup CR name passed to cluster-server.
	// * legacy (empty): "log-{snapshot_backup_schedule_name}-{log_backup_id}"
	// * v1: "log-{log_backup_id}"
	BackupCRNameVersion string `json:"backup_cr_name_version,omitempty"`
}

type RestoreExtra struct {
	On3LayerArch bool `json:"on_3layer_arch,omitempty"`

	// RestoreCRNameVersion holds the version of the restore CR name.
	// The legacy name may generate a very long CR name which may exceed Kubernetes' limit.
	// * legacy(empty string): "cluster-{clusterID}-restore-{restoreID}-{hash}"
	// * v1: "restore-{restoreID}-{hash}"
	RestoreCRNameVersion string `json:"restore_cr_name_version,omitempty"`

	// CompactBackupID holds the dataflow-service entity ID of the CompactBackup created for
	// this PiTR restore. Non-zero when compaction-accelerated PiTR is active.
	// The actual CR name lives in entity.CompactBackup.DownstreamCompactID after the
	// DagCreateCompactBackup workflow completes.
	CompactBackupID uint64 `json:"compact_backup_id,omitempty"`
}

type TableFilter []string

func (tf TableFilter) Value() (driver.Value, error) {
	b, err := json.Marshal(tf)
	return string(b), err
}

func (tf *TableFilter) Scan(input interface{}) error {
	return json.Unmarshal(input.([]byte), tf)
}

type BackupSettingBucket struct {
	Regions  []string `json:"regions"`
	Master   bool     `json:"master"`
	BucketId uint64   `json:"bucketId"`
}

type BackupSettingAction struct {
	CurrentActions []string `json:"current_actions"`
}

type RestoreKernelMetrics struct {
	Status     *RestoreKernelStatus
	Progresses []RestoreKernelProgress
}

type RestoreKernelProgress struct {
	Step             string
	Progress         int32
	LastTransitionTS int64
}

type PitBackupSwitchAction struct {
	CurrentActions []string `json:"current_actions"`
}

type TiCDCClusterSettingAction struct {
	CurrentActions []string `json:"current_actions"`
}

// gen:qs
type (
	Replay struct {
		Updatable
		Versioned
		SoftDelete

		TenantID      ID     `gorm:"type:bigint unsigned;not null;index:idx_tenant_project,priority:1"`
		ProjectID     ID     `gorm:"type:bigint unsigned;index:idx_tenant_project,priority:2"`
		CloudProvider string `gorm:"type:enum('aws', 'gcp', 'azure', 'aliyun');not null"`
		Region        string `gorm:"type:varchar(255);not null;index:idx_region"`

		DesiredState ReplayDesiredState `gorm:"type:ENUM('DEPLOY','ACTIVE','PAUSE','DELETE');not null;comment:User desired state"`
		CurrentState ReplayCurrentState `gorm:"type:ENUM('PENDING','DEPLOYING', 'DEPLOYED', 'STARTING', 'RUNNING', 'PAUSING', 'PAUSED', 'DELETING', 'DELETED');not null;comment:Runtime state"`

		SourceClusterID ID `gorm:"type:bigint unsigned;index:idx_source_cluster"`
		TargetClusterID ID `gorm:"type:bigint unsigned;not null;index:idx_target_cluster"`

		ReplayTasks ReplayTaskList `gorm:"type:JSON;serializer:json"`

		ReplayConfig          *ReplayCommonConfig  `gorm:"type:JSON;serializer:json"`
		TaskResources         *ReplayTaskResources `gorm:"type:JSON;serializer:json"`
		ReplayImage           string               `gorm:"type:VARCHAR(255)"`
		SourceAuditCredential *AuditLogAddr        `gorm:"type:JSON;serializer:json"`
		Replicas              int32                `gorm:"type:int(11);not null;default:1"`

		ReplayStatus *ReplayStatus `gorm:"type:JSON;serializer:json"`

		StartingAt *time.Time `gorm:"type:timestamp;starting_at"`
		PausingAt  *time.Time `gorm:"type:timestamp;pausing_at"`
		DeletingAt *time.Time `gorm:"type:timestamp;deleting_at"`
	}
)

type DataNode struct {
	ClusterType string `json:"cluster_type"` // DEDICATED/STARTER/ESSENTIAL/PREMIUM/ONPREMISE
	ClusterID   string `json:"cluster_id"`
	Provider    string `json:"provider"` // AWS/GCP/AZURE
	Region      string `json:"region"`
	TenantID    string `json:"tenant_id"`
	ProjectID   string `json:"project_id"`
}

type ReplayTaskList []ReplayTask

type ReplayTask struct {
	Name             string               `json:"name"`
	DataSource       ReplayDataSource     `json:"data_source"`
	DataTarget       ReplayDataTarget     `json:"data_target"`
	Replicas         int32                `json:"replicas,omitempty"`
	ReplayStartTime  string               `json:"replay_start_time,omitempty"`
	RunStartTime     string               `json:"run_start_time,omitempty"`
	Resources        *ReplayTaskResources `json:"resources,omitempty"`
	PerReplayConfigs []ReplayConfig       `json:"per_replay_configs,omitempty"`
	Image            string               `json:"image,omitempty"`
}

type ReplayDataSource struct {
	SourceAuditLogAddrs []AuditLogAddr `json:"source_audit_log_addrs"`
}

type ReplayDataTarget struct {
	Endpoint string  `json:"endpoint"`
	Username *string `json:"username,omitempty"`
	Password *string `json:"password,omitempty"`
}

type AuditLogAddr struct {
	AWS    *AWSAuditLog    `json:"aws,omitempty"`
	GCP    *GCPAuditLog    `json:"gcp,omitempty"`
	Aliyun *AliyunAuditLog `json:"aliyun,omitempty"`
}

type AWSAuditLog struct {
	S3URL      string  `json:"s3_url"`
	RoleARN    string  `json:"role_arn"`
	ExternalID *string `json:"external_id,omitempty"`
}

type GCPAuditLog struct {
	GCSURL             string  `json:"gcs_url"`
	ServiceAccountJSON *string `json:"service_account_json,omitempty"`
}

type AliyunAuditLog struct {
	BucketURL       string `json:"bucket_url"`
	AccessKeyID     string `json:"access_key_id,omitempty"`
	SecretAccessKey string `json:"secret_access_key,omitempty"`
}

type ReplayCommonConfig struct {
	ReplayConfig ReplayConfig `json:"replay_config,omitempty"`
	Username     string       `json:"username,omitempty"`
	Password     string       `json:"password,omitempty"`
}

type ReplayConfig struct {
	ReplayStartTime *string           `json:"replay_start_time,omitempty"`
	RunStartTime    *string           `json:"run_start_time,omitempty"`
	ReplayIndex     *int32            `json:"replay_index,omitempty"`
	Format          *string           `json:"format,omitempty"`
	IgnoreErrs      *bool             `json:"ignore_errs,omitempty"`
	PsClose         *string           `json:"ps_close,omitempty"`
	BufSize         *int32            `json:"buf_size,omitempty"`
	Speed           *string           `json:"speed,omitempty"`
	DynamicInput    *bool             `json:"dynamic_input,omitempty"`
	ReadOnly        *bool             `json:"read_only,omitempty"`
	CustomArguments map[string]string `json:"custom_arguments,omitempty"`
}

type ReplayTaskResources struct {
	InstanceType string `json:"instance_type,omitempty"`
	StorageSize  string `json:"storage_size,omitempty"`
}

type ReplayStatus struct {
	Phase          ReplayPhase        `json:"phase"`
	Message        string             `json:"message,omitempty"`
	Tasks          []ReplayTaskStatus `json:"tasks,omitempty"`
	LastUpdateTime *time.Time         `json:"last_update_time,omitempty"`
	AWSNodeRoleArn string             `json:"aws_node_role_arn,omitempty"`
}

type ReplayTaskStatus struct {
	Name            string                `json:"name"`
	ReplicaStatuses []ReplayReplicaStatus `json:"replica_statuses,omitempty"`
	Message         string                `json:"message,omitempty"`
}

type ReplayReplicaStatus struct {
	PodName  string          `json:"pod_name"`
	Index    int32           `json:"index"`
	Phase    ReplayTaskPhase `json:"phase"`
	Ready    bool            `json:"ready"`
	Message  string          `json:"message,omitempty"`
	TaskInfo *ReplayTaskInfo `json:"task_info,omitempty"`
}

type ReplayTaskInfo struct {
	Type                 string         `json:"type,omitempty"`
	Status               string         `json:"status,omitempty"`
	StartTime            string         `json:"start_time,omitempty"`
	EndTime              string         `json:"end_time,omitempty"`
	Progress             int32          `json:"progress,omitempty"`
	Error                string         `json:"error,omitempty"`
	LastCommandTimestamp string         `json:"last_command_timestamp,omitempty"`
	SyncTime             string         `json:"sync_time,omitempty"`
	SourceAuditLogAddrs  []AuditLogAddr `json:"source_audit_log_addrs,omitempty"`
	TotalCommands        int64          `json:"total_commands,omitempty"`
	ReplayedCommands     int64          `json:"replayed_commands,omitempty"`
	FailedCommands       int64          `json:"failed_commands,omitempty"`
	TotalBytes           int64          `json:"total_bytes,omitempty"`
	ProcessedBytes       int64          `json:"processed_bytes,omitempty"`
}

// IsInTerminalState check if the replay is in the terminal state
func (r *Replay) IsInTerminalState() bool {
	return r.CurrentState == ReplayCurrentStateDeleted
}
