package entity

import (
	"database/sql/driver"
	"encoding/json"
	"errors"
	"strings"
	"time"

	"github.com/pingcap/dbaas/pkg/capacity"
	"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	"github.com/tidbcloud/central-sdk/config"
)

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

// base
type (
	// uint64 ID for all tables
	ID uint64

	// base
	Base struct {
		ID        ID        `gorm:"primary_key;not null"`
		CreatedAt time.Time `gorm:"created_at;type:TIMESTAMP;not null"`
	}

	// updatable table
	Updatable struct {
		Base
		UpdatedAt *time.Time `gorm:"updated_at;type:TIMESTAMP"`
	}

	// enum
	ProviderStatus         string
	RegionStatus           string
	TenantStatus           string
	TenantPlan             string
	EmailReminderType      string
	ClusterStatus          string
	ComponentMachineStatus string
	UserStatus             string
	UserRole               string
	MachineStatus          string
	RestoreStatus          string
	ImportStatus           string
	ClusterConnectionType  string
	ConnectionStatus       string
	BackupStatus           string
	BackupType             string
	BackupMethod           string
	MachineArch            string

	DataFormat uint32
	DataSource uint32

	ClusterOperationType         string
	ClusterOperationStatus       string
	ScaleOperationTrackingStatus string

	DiscountType uint32
)

// TODO drop type =
type ComponentName string

func StringToComponentName(in string) (ComponentName, error) {
	var cn ComponentName
	switch strings.ToLower(in) {
	case "pd":
		cn = PD
	case "tikv", "kv":
		cn = TiKV
	case "tidb", "mysql":
		cn = TiDB
	case "tiflash":
		cn = TiFlash
	default:
		return cn, errors.New("could not parse as ComponentName: " + in)
	}
	return cn, nil
}

func (s ImportStatus) String() string {
	return string(s)
}

// enums
const (
	ClusterConnectionTypePrivate ClusterConnectionType = "private"
	ClusterConnectionTypePublic  ClusterConnectionType = "public"

	// ClusterStatusInitializing initialize the infra.
	ClusterStatusInitializing ClusterStatus = "initializing"
	// ClusterStatusPreparing provisioning nodes
	ClusterStatusPreparing ClusterStatus = "preparing"
	// ClusterStatusLaunching set up the tidb cluster
	ClusterStatusLaunching ClusterStatus = "launching"
	ClusterStatusNormal    ClusterStatus = "normal"
	ClusterStatusScaling   ClusterStatus = "scaling"
	// ClusterStatusUpgrading  ClusterStatus = "upgrading"
	ClusterStatusRecovering ClusterStatus = "recovering"
	// ClusterStatusDeleted indicate cluster is deleting, cluster
	// worker will list cluster in deleted status, then delete all resources
	ClusterStatusDeleted ClusterStatus = "deleted"
	// ClusterStatusCleared indicate all reosurce already clean up
	ClusterStatusCleared ClusterStatus = "cleared"
	// ClusterStatusDeletecomplete indicate cluster resources already deleted, but backup cleanup worker still in the process
	ClusterStatusDeletecomplete ClusterStatus = "deletecomplete"

	ProviderStatusActive   ProviderStatus = "active"
	ProviderStatusInactive ProviderStatus = "inactive"
	RegionStatusActive     RegionStatus   = "active"
	RegionStatusInactive   RegionStatus   = "inactive"

	PD      ComponentName = "pd"      // Placement Driver
	TiKV    ComponentName = "tikv"    // TiKV
	TiDB    ComponentName = "tidb"    // TiDB
	TiFlash ComponentName = "tiflash" // TiFlash

	ActiveComponent   ComponentMachineStatus = "active"
	CreatingComponent ComponentMachineStatus = "creating"
	DeletedComponent  ComponentMachineStatus = "deleted"

	TenantStatusPending  TenantStatus = "pending"
	TenantStatusActive   TenantStatus = "active"
	TenantStatusInactive TenantStatus = "inactive"
	TenantStatusDeleted  TenantStatus = "deleted"
	TenantStatusCleared  TenantStatus = "cleared"

	TenantPlanNewUser   TenantPlan = "new_user"
	TenantPlanFreeTrial TenantPlan = "free_trial"
	TenantPlanPOC       TenantPlan = "poc"
	TenantPlanOnDemand  TenantPlan = "on_demand"

	EmailReminderTypeHalfway              EmailReminderType = "halfway"
	EmailReminderTypeFeedback             EmailReminderType = "feedback"
	EmailReminderTypeOptional             EmailReminderType = "optional"
	EmailReminderTypePocWillEnd           EmailReminderType = "poc_will_end"
	EmailReminderTypePocEnd               EmailReminderType = "poc_end"
	EmailReminderTypePocEndBackupDeletion EmailReminderType = "poc_end_backup"

	UserStatusActive   UserStatus = "active"
	UserStatusPending  UserStatus = "pending"
	UserStatusInactive UserStatus = "inactive"

	UserRoleOwner        UserRole = "owner"
	UserRoleMember       UserRole = "member"
	UserRoleBillingAdmin UserRole = "billing_admin"

	ImportStatusUnknown   ImportStatus = "unknown"
	ImportStatusPreparing ImportStatus = "preparing"
	ImportStatusImporting ImportStatus = "importing"
	ImportStatusCompleted ImportStatus = "completed"
	ImportStatusFailed    ImportStatus = "failed"
	ImportStatusCanceling ImportStatus = "canceling"
	ImportStatusCanceled  ImportStatus = "canceled"

	DataSourceTypeUnknown DataSource = 0
	DataSourceTypeS3      DataSource = 1
	DataSourceTypeGCS     DataSource = 2

	DataFormatUnknown DataFormat = 0
	DataFormatSQL     DataFormat = 1
	DataFormatParquet DataFormat = 2

	PingCAPTenantID ID = 1

	MachineArchARM MachineArch = "arm"
	MachineArchX86 MachineArch = "x86"
	// Backup Method
	BackupMethod_Full        BackupMethod = "full"
	BackupMethod_Incremental BackupMethod = "incremental"

	// Backup Type
	BackupType_All    BackupType = "all"
	BackupType_Auto   BackupType = "auto"
	BackupType_Manual BackupType = "manual"

	// Backup Status
	BackupStatus_PENDING BackupStatus = "pending"
	BackupStatus_RUNNING BackupStatus = "running"
	BackupStatus_FAILED  BackupStatus = "failed"
	BackupStatus_SUCCESS BackupStatus = "success"

	// Restore Status
	RestoreStatus_PENDING RestoreStatus = "pending"
	RestoreStatus_RUNNING RestoreStatus = "running"
	RestoreStatus_FAILED  RestoreStatus = "failed"
	RestoreStatus_SUCCESS RestoreStatus = "success"

	// Connection Status
	ConnectionStatus_PENDING  ConnectionStatus = "pending"
	ConnectionStatus_Creating ConnectionStatus = "creating"
	ConnectionStatus_FAILED   ConnectionStatus = "failed"
	ConnectionStatus_SUCCESS  ConnectionStatus = "success"

	ClusterOperationTypeScale       ClusterOperationType         = "scale"
	ClusterOperationStatusCreated   ClusterOperationStatus       = "created"
	ClusterOperationStatusDone      ClusterOperationStatus       = "done"
	ScaleOperationTrackingScaleInit ScaleOperationTrackingStatus = "scale-init"
	ScaleOperationTrackingScalePod  ScaleOperationTrackingStatus = "scale-pod"
	ScaleOperationTrackingScaleNode ScaleOperationTrackingStatus = "scale-node"
	ScaleOperationTrackingDone      ScaleOperationTrackingStatus = "done"

	// Machine Status
	MachineStatus_CREATING MachineStatus = "creating"
	MachineStatus_RUNNING  MachineStatus = "running"

	Normal DiscountType = 1
)

// gen:qs
type (
	Provider struct {
		Updatable

		Name    config.Cloud   `gorm:"uniqueIndex:idx_name;not null;type:ENUM('aws', 'gcp')"`
		Regions []*Region      `gorm:"foreignkey:ProviderID"`
		Status  ProviderStatus `gorm:"not null;type:ENUM('active', 'inactive')"`
	}

	Region struct {
		Updatable

		Name        string       `gorm:"type:VARCHAR(255);uniqueIndex:idx_name_provider;not null" valid:"required,lowercase"`
		DisplayName string       `gorm:"type:VARCHAR(255);not null"`
		ProviderID  ID           `gorm:"index;uniqueIndex:idx_name_provider;not null" valid:"required"`
		Provider    *Provider    `gorm:"foreignkey:ProviderID"`
		Zones       []*Zone      `gorm:"foreignkey:RegionID"`
		Status      RegionStatus `gorm:"not null;type:ENUM('active', 'inactive')"`
	}

	Zone struct {
		Updatable

		Name        string  `gorm:"type:VARCHAR(255);uniqueIndex:idx_name_region;not null"`
		DisplayName string  `gorm:"type:VARCHAR(255);not null"`
		RegionID    ID      `gorm:"index;uniqueIndex:idx_name_region;not null"`
		Region      *Region `gorm:"foreignkey:RegionID"`
	}

	RegionMachine struct {
		Updatable

		Name             string          `gorm:"type:VARCHAR(255);not null"`
		DisplayName      string          `gorm:"type:VARCHAR(255);not null"`
		RegionID         ID              `gorm:"uniqueIndex:idx_machine_region;not null"`
		Region           *Region         `gorm:"foreignkey:RegionID"`
		MachineProfileID ID              `gorm:"uniqueIndex:idx_machine_region;not null"`
		MachineProfile   *MachineProfile `gorm:"foreignkey:MachineProfileID"`
	}

	RegionMachinePrice struct {
		Base

		EffectedAt      time.Time      `gorm:"type:TIMESTAMP;not null"`
		Price           float64        `gorm:"type:DECIMAL(10,3);not null"`
		RegionMachineID ID             `gorm:"index;not null"`
		RegionMachine   *RegionMachine `gorm:"foreignkey:RegionMachineID"`
	}

	MachineProfile struct {
		Updatable

		MachineArch  MachineArch          `gorm:"type:ENUM('x86', 'arm');not null;"`
		InstanceType capacity.MachineType `gorm:"type:VARCHAR(255);uniqueIndex:idx_type_provider;not null" valid:"required,lowercase"`
		ProviderID   ID                   `gorm:"uniqueIndex:idx_type_provider;not null" valid:"required"`
		Provider     *Provider            `gorm:"foreignkey:ProviderID"`
		Memory       uint32               `gorm:"not null"`
		CPU          uint32               `gorm:"not null"`
	}

	DiskProfile struct {
		Updatable

		Type capacity.DiskType `gorm:"type:ENUM('ssd_nvme', 'ssd_scsi', 'HDD', 'ssd_network', 'hdd_network', 'gp3');uniqueIndex:idx_type;not null"`
	}

	RegionDisk struct {
		Base

		Name          string       `gorm:"type:VARCHAR(255);not null"`
		DisplayName   string       `gorm:"type:VARCHAR(255);not null"`
		Capacity      uint32       `gorm:"uniqueIndex:c_r_d;not null"`
		RegionID      ID           `gorm:"uniqueIndex:c_r_d;not null"`
		Region        *Region      `gorm:"foreignkey:RegionID"`
		DiskProfileID ID           `gorm:"uniqueIndex:c_r_d;not null"`
		DiskProfile   *DiskProfile `gorm:"foreignkey:DiskProfileID"`
	}

	RegionDiskPrice struct {
		Base

		EffectedAt   time.Time   `gorm:"type:TIMESTAMP;not null"`
		Price        float64     `gorm:"type:decimal(10,3);not null"`
		RegionDiskID ID          `gorm:"not null"`
		RegionDisk   *RegionDisk `gorm:"foreignkey:RegionDiskID"`
	}

	// Temporary table for mark the profile related to machine
	// TODO: to be deleted
	ComponentProfile struct {
		Updatable

		ComponentName ComponentName `gorm:"type:ENUM('pd', 'tikv', 'tidb', 'tispark', 'tiflash');uniqueIndex:idx_comp_region;not null" valid:"required,lowercase"`

		// Replicas is the default instance number of the specific component
		Replicas uint32 `gorm:"not null" valid:"required"`

		ClusterProfile   *ClusterProfile `gorm:"foreignkey:ClusterProfileID"`
		ClusterProfileID ID              `gorm:"uniqueIndex:idx_comp_region;not null" valid:"required"`

		MachineProfile   *MachineProfile `gorm:"foreignkey:MachineProfileID"`
		MachineProfileID ID              `valid:"required"`

		RegionDisk   *RegionDisk `gorm:"foreignkey:RegionDiskID"`
		RegionDiskID ID          `valid:"required"`
	}

	// Temporary table for mark the profile related to region
	// TODO: to be deleted
	ClusterProfile struct {
		Updatable

		Name        string  `gorm:"type:VARCHAR(255);uniqueIndex:idx_name_region;not null" valid:"required,lowercase"`
		Description string  `gorm:"type:VARCHAR(255)"`
		Status      string  `gorm:"type:VARCHAR(255);not null" valid:"required,lowercase"`
		Region      *Region `gorm:"foreignkey:RegionID"`
		RegionID    ID      `gorm:"uniqueIndex:idx_name_region;not null"`
	}
)

// gen:qs
type (
	RegistrationLog struct {
		Base

		Name        string    `gorm:"type:VARCHAR(255);not null"`
		Email       string    `gorm:"type:VARCHAR(255);index;not null"`
		FirstName   string    `gorm:"type:VARCHAR(255);not null"`
		LastName    string    `gorm:"type:VARCHAR(255);not null"`
		UserID      ID        `gorm:"index;not null"`
		User        *User     `gorm:"foreignkey:UserID"`
		InvitedAt   time.Time `gorm:"type:TIMESTAMP;not null"`
		InvitedByID ID        `gorm:"index;not null"`
		InvitedBy   *User     `gorm:"foreignkey:InvitedByID"`
		Source      string    `gorm:"type:VARCHAR(255);not null"`
		IPAddress   string    `gorm:"type:VARCHAR(255);not null"`
	}

	EmailProviderBlocklist struct {
		Base

		Email string `gorm:"type:VARCHAR(255);uniqueIndex:idx_email;not null"`
	}

	Tenant struct {
		Updatable

		Name             string       `gorm:"type:VARCHAR(255);not null" valid:"required"`
		Email            string       `gorm:"type:VARCHAR(255);uniqueIndex:idx_email;not null" valid:"required"`
		Country          string       `gorm:"type:VARCHAR(255);not null"`
		TimeZone         string       `gorm:"type:VARCHAR(255);not null"`
		Status           TenantStatus `gorm:"type:ENUM('pending', 'active', 'inactive', 'deleted', 'cleared');not null"`
		Plan             TenantPlan   `gorm:"type:ENUM('new_user', 'free_trial', 'poc', 'on_demand');not null"`
		AcceptService    bool         `gorm:"not null"`
		BindCreditCardAt *time.Time   `gorm:"type:TIMESTAMP"`
		ZendeskID        ID
		Quotas           []*Quota   `gorm:"foreignkey:TenantID"`
		BillingEmail     string     `gorm:"type:VARCHAR(255);not null"`
		Projects         []*Project `gorm:"foreignkey:TenantID"`
	}

	TenantPlanChangeLog struct {
		Base

		TenantID ID         `gorm:"not null"`
		Tenant   *Tenant    `gorm:"foreignkey:TenantID"`
		Plan     TenantPlan `gorm:"type:ENUM('new_user', 'free_trial', 'poc');not null"`
		NewPlan  TenantPlan `gorm:"type:ENUM('free_trial', 'poc', 'on_demand');not null"`
	}

	Project struct {
		Updatable

		TenantID ID      `gorm:"not null;uniqueIndex:tenant_project"`
		Tenant   *Tenant `gorm:"foreignkey:TenantID"`
		Name     string  `gorm:"type:VARCHAR(255);not null;uniqueIndex:tenant_project" valid:"required"`
		Users    []User  `gorm:"many2many:user_projects;"`
	}

	EmailReminder struct {
		Base

		TenantID ID                `gorm:"uniqueIndex:idx_tenant_type;not null"`
		Tenant   *Tenant           `gorm:"foreignkey:TenantID"`
		Type     EmailReminderType `gorm:"type:ENUM('halfway','feedback','optional','poc_will_end','poc_end','poc_end_backup');uniqueIndex:idx_tenant_type;not null"`
		Amount   uint32            `gorm:"not null"`
	}

	Quota struct {
		Updatable

		TenantID      ID            `gorm:"index;not null"`
		Tenant        *Tenant       `gorm:"foreignkey:TenantID"`
		ComponentName ComponentName `gorm:"type:ENUM('pd', 'tikv', 'tidb', 'tispark', 'tiflash');not null"`
		Quota         uint32        `gorm:"not null"`
	}

	FreeTrialPoint struct {
		Base

		TenantID    ID      `gorm:"index;not null"`
		Tenant      *Tenant `gorm:"foreignkey:TenantID"`
		Amount      int64   `gorm:"not null"`
		Description string  `gorm:"type:VARCHAR(255);not null"`
	}

	Discount struct {
		Base

		TenantID     ID           `gorm:"not null"`
		Tenant       *Tenant      `gorm:"foreignkey:TenantID"`
		Discount     float64      `gorm:"type:DECIMAL(10,3);not null"`
		Description  string       `gorm:"type:VARCHAR(255);not null"`
		DiscountType DiscountType `gorm:"not null"`
		StartTime    time.Time    `gorm:"type:TIMESTAMP;not null"`
		EndTime      time.Time    `gorm:"type:TIMESTAMP;not null"`
	}

	GlobalDiscount struct {
		Base

		Discount    float64   `gorm:"type:DECIMAL(10,3);not null"`
		Description string    `gorm:"type:VARCHAR(255);not null"`
		StartTime   time.Time `gorm:"type:TIMESTAMP;not null"`
		EndTime     time.Time `gorm:"type:TIMESTAMP;not null"`
	}

	QuotaChangeLog struct {
		Base

		QuotaID     ID     `gorm:"index;not null"`
		Quota       Quota  `gorm:"foreignkey:QuotaID"`
		NewQuota    uint64 `gorm:"not null"`
		ChangedByID ID     `gorm:"not null"`
		ChangedBy   *User  `gorm:"foreignkey:ChangedByID"`
	}

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

		TenantID    ID         `gorm:"index;not null"`
		Tenant      *Tenant    `gorm:"foreignkey:TenantID"`
		FirstName   string     `gorm:"type:VARCHAR(255);not null"`
		LastName    string     `gorm:"type:VARCHAR(255);not null"`
		Email       string     `gorm:"type:VARCHAR(255);uniqueIndex:idx_email;not null"`
		Status      UserStatus `gorm:"type:ENUM('inactive', 'active', 'pending');not null"`
		InvitedAt   *time.Time `gorm:"type:TIMESTAMP"`
		Job         string     `gorm:"type:VARCHAR(255);not null"`
		PhoneNumber string     `gorm:"type:VARCHAR(255);not null"`
		Roles       []Role     `gorm:"many2many:user_roles;"`
		Role        UserRole   `gorm:"type:ENUM('owner', 'member', 'billing_admin');not null; default:'member'"`
		Projects    []Project  `gorm:"many2many:user_projects;"`
	}

	ImpersonatedUser struct {
		Base

		UserID   ID      `gorm:"uniqueIndex:idx_impersonated_user;not null"`
		User     *User   `gorm:"foreignkey:UserID"`
		TenantID ID      `gorm:"not null"`
		Tenant   *Tenant `gorm:"foreignkey:TenantID"`
	}

	Role struct {
		Updatable

		TenantID   ID         `gorm:"uniqueIndex:idx_tenant_name,priority:1;not null"`
		Tenant     *Tenant    `gorm:"foreignkey:TenantID"`
		Name       string     `gorm:"type:VARCHAR(255);uniqueIndex:idx_tenant_name,priority:2;not null" valid:"required"`
		Privileges Privileges `gorm:"type:JSON"`
	}

	UserChangeLog struct {
		Base

		UserID       ID     `gorm:"index;not null"`
		User         *User  `gorm:"foreignkey:UserID"`
		Operation    string `gorm:"type:TEXT;not null"`
		OperatedByID ID     `gorm:"index;not null"`
		OperatedBy   *User  `gorm:"foreignkey:OperatedByID"`
	}

	UserInviteToken struct {
		Base

		Method    string    `gorm:"type:VARCHAR(255)"`
		Email     string    `gorm:"index;type:VARCHAR(255)"`
		TenantID  ID        `gorm:"index;tenant_id;not null"`
		BeginAt   time.Time `gorm:"type:TIMESTAMP;not null"`
		ExpireAt  time.Time `gorm:"type:TIMESTAMP;not null"`
		IsOneTime bool      `gorm:"default:false"`
	}

	AuditLog struct {
		Base

		TenantID ID      `gorm:"index;not null"`
		Tenant   *Tenant `gorm:"foreignkey:TenantID"`
		User     string  `gorm:"type:VARCHAR(255);not null"`
		Content  string  `gorm:"type:TEXT;not null"`
	}

	AccessLog struct {
		Base

		Accessor  string    `gorm:"index:idx_accessor_scene_ts,priority:1;type:VARCHAR(255);NOT NULL"`
		Scene     string    `gorm:"index:idx_accessor_scene_ts,priority:2;type:VARCHAR(255);NOT NULL"`
		Timestamp time.Time `gorm:"index:idx_accessor_scene_ts,priority:3;type:TIMESTAMP;NOT NULL"`
		Target    string    `gorm:"type:TEXT"`
	}

	NodeUsage struct {
		Updatable

		TenantID  ID      `gorm:"not null; uniqueIndex:idx_tenant;"`
		Tenant    *Tenant `gorm:"foreignkey:TenantID"`
		StartTime int64   `gorm:"not null"`
		EndTime   int64   `gorm:"not null"`
		Usage     int64   `gorm:"not null"`
	}
)

// gen:qs
type (
	Cluster struct {
		Updatable
		DeletedTime          *time.Time `gorm:"type:TIMESTAMP;deleted_time"`
		CreationFinishedTime *time.Time `gorm:"type:TIMESTAMP;creation_finished_time"`

		Name                  string        `gorm:"type:VARCHAR(255);index:name_tenant;not null"`
		TenantID              ID            `gorm:"index:name_tenant;not null"`
		Tenant                *Tenant       `gorm:"foreignkey:TenantID"`
		CreatedByID           ID            `gorm:"not null"`
		CreatedBy             *User         `gorm:"foreignkey:CreatedByID"`
		Status                ClusterStatus `gorm:"type:ENUM('created', 'initializing', 'preparing', 'launching', 'normal', 'scaling', 'upgrading','recovering', 'deleted', 'cleared', 'deletecomplete');not null"`
		EncryptedRootPassword string        `gorm:"type:VARCHAR(255);not null;default ''"`
		PasswordProviderID    string        `gorm:"type:VARCHAR(255);default:''"`
		Components            []Component   `gorm:"foreignkey:ClusterID"`
		Connections           []Connection  `gorm:"foreignkey:ClusterID"`
		KMSs                  []KMS         `gorm:"foreignkey:ClusterID"`
		Replicas              uint          `gorm:"not null"`
		ProviderID            ID            `gorm:"not null"`
		Provider              *Provider     `gorm:"foreignkey:ProviderID"`
		PocDeleted            bool          `gorm:"default:false"`
		ProjectID             ID            `gorm:"index;not null"`
		Project               *Project      `gorm:"foreignkey:ProjectID"`

		// Not null if it's importing.
		// we set this as the importing id when user create a import
		// and set as null when user exit the importing mode
		ImportID *uint64 `gorm:"index"`

		//todo: delete it after cluster tier
		ClusterProfile   *ClusterProfile `gorm:"foreignkey:ClusterProfileID"`
		ClusterProfileID ID              `gorm:"index;not null"`
	}

	// cluster connections
	Connection struct {
		Updatable

		ClusterID     ID                    `gorm:"index;not null"`
		Cluster       *Cluster              `gorm:"foreignkey:ClusterID"`
		Type          ClusterConnectionType `gorm:"type:ENUM('private','public');not null"`
		DSN           string                `gorm:"type:VARCHAR(255);not null"`
		CreateSuccess *bool
	}

	// cluster KMS
	KMS struct {
		Updatable

		ClusterID ID       `gorm:"not null; uniqueIndex:idx_cluster_region;"`
		Cluster   *Cluster `gorm:"foreignkey:ClusterID"`
		RegionID  ID       `gorm:"not null; uniqueIndex:idx_cluster_region;"`
		Region    *Region  `gorm:"foreignkey:RegionID"`
		KmsID     string   `gorm:"type:VARCHAR(255);not null"`
		KmsArn    string   `gorm:"type:VARCHAR(255);not null"`
	}

	// cluster component
	Component struct {
		Updatable

		ComponentName     ComponentName       `gorm:"not null; uniqueIndex:cluster_component,length:255,priority:2" sql:"type:ENUM('pd', 'tikv', 'tidb', 'tispark', 'tiflash')"`
		ClusterID         ID                  `gorm:"not null; uniqueIndex:cluster_component,priority:1"`
		Cluster           *Cluster            `gorm:"foreignkey:ClusterID"`
		ComponentMachines []*ComponentMachine `gorm:"foreignkey:ComponentID"`
	}

	ComponentMachine struct {
		Updatable

		ComponentID ID                     `gorm:"index;not null"`
		Component   *Component             `gorm:"foreignkey:ComponentID"`
		MachineID   ID                     `gorm:"index;not null"`
		Machine     *Machine               `gorm:"foreignkey:MachineID"`
		Status      ComponentMachineStatus `gorm:"type:ENUM('active','creating','deleted');not null"`
	}

	// machine on cloud provider
	Machine struct {
		Updatable

		Name string `gorm:"type:VARCHAR(255);not null"` // pingcap-test1-worker-1
		// wait for worker
		InstanceID       string          `gorm:"type:VARCHAR(255);not null"` // uuid
		MachineProfileID ID              `gorm:"not null"`
		MachineProfile   *MachineProfile `gorm:"foreignkey:MachineProfileID"`
		ZoneID           ID              `gorm:"not null"`
		Zone             *Zone           `gorm:"foreignkey:ZoneID"`
		Disks            []*Disk         `gorm:"foreignkey:MachineID"`
		Status           MachineStatus   `gorm:"type:ENUM('creating', 'running');not null"`
	}

	Disk struct {
		Updatable

		MachineID    ID          `gorm:"not null"`
		Machine      *Machine    `gorm:"foreignkey:MachineID"`
		InstanceID   string      `gorm:"type:VARCHAR(255);not null;"`
		RegionDiskID ID          `gorm:"not null"`
		RegionDisk   *RegionDisk `gorm:"foreignkey:RegionDiskID"`
	}

	ClusterOperation struct {
		Updatable

		ClusterID   ID                     `gorm:"index;not null"`
		Cluster     *Cluster               `gorm:"foreignkey:ClusterID"`
		Type        ClusterOperationType   `gorm:"type:ENUM('scale');not null"`
		CompletedAt *time.Time             `gorm:"type:TIMESTAMP"`
		Status      ClusterOperationStatus `gorm:"type:ENUM('created','done');not null"`
		Content     string                 `gorm:"type:TEXT;not null"`
	}

	ScaleOperationTracking struct {
		Updatable

		ComponentID        uint64                       `gorm:"not null" valid:"required"`
		Component          *Component                   `gorm:"foreignkey:ComponentID"`
		ClusterOperationID ID                           `gorm:"index;not null"`
		ClusterOperation   *ClusterOperation            `gorm:"foreignkey:ClusterOperationID"`
		Status             ScaleOperationTrackingStatus `gorm:"type:ENUM('scale-init', 'scale-pod','scale-node','done');not null"`
		Replicas           uint32
	}

	BackupSchedule struct {
		Updatable
		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"`
		Cluster        *Cluster     `gorm:"foreignkey:ClusterID"`
		MaxBackups     uint64       `gorm:"not null"`
		Schedule       string       `gorm:"type:VARCHAR(255);not null"`
		Targets        string       `gorm:"type:VARCHAR(4096);not null"` // filter expression
		UserID         ID           `gorm:"not null"`
		User           *User        `gorm:"foreignkey:UserID"`
		Method         BackupMethod `gorm:"type:ENUM('full','incremental');not null"` // enum
		Description    string       `gorm:"type:VARCHAR(4096);not null"`
		NextBackupTime *time.Time   `gorm:"type:TIMESTAMP"`
	}

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

		BackupScheduleID         ID                     `gorm:"index;not null"`
		BackupSchedule           *BackupSchedule        `gorm:"foreignkey:BackupScheduleID"`
		Targets                  string                 `gorm:"type:VARCHAR(4096);not null"`                                // filter expression
		ClusterVersion           string                 `gorm:"type:VARCHAR(255);not null"`                                 // not used anymore
		Status                   BackupStatus           `gorm:"type:ENUM('pending','running','success','failed');not null"` // enum
		ClusterConfig            *ClusterConfigurations `gorm:"type:TEXT"`                                                  // not used anymore
		CleanedUp                *bool                  `gorm:"default:false"`
		PdComponentMachines      IDList                 `gorm:"type:JSON"`
		TidbComponentMachines    IDList                 `gorm:"type:JSON"`
		TikvComponentMachines    IDList                 `gorm:"type:JSON"`
		TiflashComponentMachines IDList                 `gorm:"type:JSON"`
		OldBackupName            string                 `gorm:"type:VARCHAR(255);not null"`
	}

	BackupTracking struct {
		Base

		BackupID    ID           `gorm:"index;not null"`
		Backup      *Backup      `gorm:"foreignkey:BackupID"`
		Status      BackupStatus `gorm:"type:ENUM('pending','running','success','failed');not null"` // enum
		Description string       `gorm:"type:VARCHAR(4096);not null"`
	}

	Restore struct {
		Updatable

		Name      string        `gorm:"type:VARCHAR(255);not null"`
		ClusterID ID            `gorm:"index;not null"`
		Cluster   *Cluster      `gorm:"foreignkey:ClusterID"`
		Targets   string        `gorm:"type:VARCHAR(4096);not null"` // filter expression
		BackupID  ID            `gorm:"not null"`
		Backup    *Backup       `gorm:"foreignkey:BackupID"`
		Status    RestoreStatus `gorm:"type:ENUM('pending','running','success','failed');not null"` // enum
	}

	RestoreTracking struct {
		Base

		RestoreID   ID            `gorm:"index;not null"`
		Restore     *Restore      `gorm:"foreignkey:RestoreID"`
		Status      RestoreStatus `gorm:"type:ENUM('pending','running','success','failed');not null"` // enum
		Description string        `gorm:"type:VARCHAR(4096);not null"`
	}

	CidrTemplate struct {
		Updatable

		Cidr      TrafficFilters `gorm:"type:JSON"`
		ProjectID ID             `gorm:"index;not null"`
		Project   *Project       `gorm:"foreignkey:ProjectID"`
	}

	TrafficFilters struct {
		Cidrs []TrafficFilter `json:"cidrs"`
	}

	TrafficFilter struct {
		Cidr        string `gorm:"type:VARCHAR(255)" json:"cidr"`
		Description string `gorm:"type:VARCHAR(255)" json:"description"`
	}

	ClusterCidr struct {
		Updatable

		ClusterID ID             `gorm:"index;not null"`
		Cidr      TrafficFilters `gorm:"type:JSON"`
	}

	// Gcp Vpc Peering
	GcpVpcPeering struct {
		Updatable

		Region            string   `gorm:"type:VARCHAR(255);uniqueIndex:project_region"`
		TenantProjectID   string   `gorm:"type:VARCHAR(255);not null"`
		TenantNetworkName string   `gorm:"type:VARCHAR(255);not null"`
		TenantVpcRegion   string   `gorm:"type:VARCHAR(255);not null"`
		NetworkName       string   `gorm:"type:VARCHAR(255);not null"`
		Status            string   `gorm:"type:VARCHAR(255);not null"`
		ProjectID         ID       `gorm:"uniqueIndex:project_region"`
		Project           *Project `gorm:"foreignkey:ProjectID"`
	}

	// Aws Vpc Peering
	AwsVpcPeering struct {
		Updatable

		Region          string   `gorm:"type:VARCHAR(255);uniqueIndex:project_region"`
		TenantAccountID string   `gorm:"type:VARCHAR(255);not null"`
		TenantRegion    string   `gorm:"type:VARCHAR(255);not null"`
		TenantVpcID     string   `gorm:"type:VARCHAR(255);not null"`
		TenantVpcCIDR   string   `gorm:"type:VARCHAR(255);not null"`
		VpcPeeringID    string   `gorm:"type:VARCHAR(255);not null"`
		PrivateHostZone string   `gorm:"type:VARCHAR(255);not null"`
		Status          string   `gorm:"type:VARCHAR(255);not null"`
		ProjectID       ID       `gorm:"uniqueIndex:project_region"`
		Project         *Project `gorm:"foreignkey:ProjectID"`
	}

	// Gardener shoot
	Shoot struct {
		Updatable

		VpcID     string   `gorm:"type:VARCHAR(255);uniqueIndex:idx_vpc;not null"`
		Name      string   `gorm:"type:VARCHAR(255);uniqueIndex:idx_name;not null"`
		PhzID     string   `gorm:"type:VARCHAR(255)"`
		ProjectID ID       `gorm:"index;not null"`
		Project   *Project `gorm:"foreignkey:ProjectID"`
	}

	Import struct {
		Updatable

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

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

		BucketRegion string `gorm:"type:VARCHAR(255)"`
		// 1: sql statement(dumpling default format
		// 2: aurora snapshot(parquet)
		DataFormat DataFormat
		// 1: aws s3
		// 2: GCS
		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(255)"`

		Filter string `gorm:"type:VARCHAR(255)"`

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

		// In progress info
		CompletedTables  uint32
		PendingTables    uint32
		CompletedPercent uint32 // 0 ~100
	}

	VerifyCode struct {
		Base
		CodeKey    string    `gorm:"type:VARCHAR(255); uniqueIndex:idx_code_key;not null"`
		ExpireTime time.Time `gorm:"not null;"`
		Code       string    `gorm:"type:VARCHAR(255)"`
	}
)

// gen:qs
type RateLimit struct {
	Updatable
	LimitKey        string  `gorm:"type:VARCHAR(255);uniqueIndex:idx_limit_key;not null" valid:"required"`
	RequestsPerUnit uint32  `gorm:"not null"`
	Active          bool    `gorm:"default:false"`
	Methods         Methods `gorm:"type:JSON"`
}

type Methods struct {
	Methods []Method `json:"methods"`
}

type Method struct {
	Name string
}

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

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

var Models = []interface{}{
	//cluster profiles
	&Provider{}, &Region{}, &Zone{}, &RegionMachine{}, &RegionMachinePrice{},
	&MachineProfile{}, &DiskProfile{}, &RegionDisk{}, &RegionDiskPrice{},
	&ClusterProfile{}, &ComponentProfile{},

	//tenant
	&RegistrationLog{}, &EmailProviderBlocklist{}, &Tenant{}, &Quota{}, &QuotaChangeLog{},
	&User{}, &Role{}, &UserChangeLog{}, &UserInviteToken{}, &AuditLog{}, &FreeTrialPoint{},
	&GlobalDiscount{}, &Discount{}, &EmailReminder{}, &APIKey{}, &ImpersonatedUser{},
	&NodeUsage{},

	//cluster
	&Cluster{}, &Connection{}, &KMS{}, &Component{}, &ComponentMachine{},
	&Machine{}, &Disk{}, &ClusterOperation{}, &ScaleOperationTracking{}, &Import{},
	&VerifyCode{},

	//backup and restore
	&BackupSchedule{}, &Backup{}, &BackupTracking{},
	&Restore{}, &RestoreTracking{},

	//old schema
	&CidrTemplate{}, &ClusterCidr{}, &GcpVpcPeering{}, &AwsVpcPeering{},
	&Shoot{}, RateLimit{},
}

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

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

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)
}

// Scan unmarshal json
// BackupConfigurationMap record the cluster's conponent configurations.
// In order to be compatible with different systems, we make the structure into a map to record
// the configuration of different tc object in a cluster.
type ClusterConfigurations struct {
	Configurations map[string]ClusterConfiguration `json:"clusterConfiguration,omitempty"`
}

type ClusterConfiguration struct {
	PDConfig      *v1alpha1.PDConfigWraper      `json:"pdConfig,omitempty"`
	TiDBConfig    *v1alpha1.TiDBConfigWraper    `json:"tidbConfig,omitempty"`
	TiKVConfig    *v1alpha1.TiKVConfigWraper    `json:"tikvConfig,omitempty"`
	TiFlashConfig *v1alpha1.TiFlashConfigWraper `json:"tiflashConfig,omitempty"`
}

// gen:qs
type APIKey struct {
	Updatable

	// The tenant that this API can be used for.
	TenantID ID
	// The user is inside tenant DB. Before DB schema refactoring we will not use it to join the user table.
	// CreatedBy     *User  `gorm:"foreignkey:CreatedByID"`
	CreatedByID ID     `gorm:"not null" valid:"required"`
	Name        string `gorm:"not null;size:50" valid:"required"`
	KeyHash     string `gorm:"not null;size:128;unique" valid:"required"` // The SHA256 of the API key.
}

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

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

type IDList struct {
	IDs []ID `json:"ids"`
}

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

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

type Privileges struct {
	Privileges []Privilege `json:"privileges"`
}

type Privilege struct {
	Resource  string `json:"resource"`
	Operation string `json:"operation"`
}

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

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

func (id ID) Uint64() uint64 {
	return uint64(id)
}

var (
	CreatingClusterStatus         []ClusterStatus       = []ClusterStatus{ClusterStatusInitializing, ClusterStatusPreparing, ClusterStatusLaunching}
	DeletedClusterStatus          []ClusterStatus       = []ClusterStatus{ClusterStatusDeleted, ClusterStatusDeletecomplete}
	DeletedOrClearedClusterStatus []ClusterStatus       = []ClusterStatus{ClusterStatusDeleted, ClusterStatusDeletecomplete, ClusterStatusCleared}
	ReadyForBackupClusterStatus   []ClusterStatus       = []ClusterStatus{ClusterStatusNormal}
	ValidUserRoles                map[UserRole]struct{} = map[UserRole]struct{}{UserRoleOwner: {}, UserRoleMember: {}, UserRoleBillingAdmin: {}}
)

// Creating returns true if we are still creating the cluster
func (s ClusterStatus) Creating() bool {
	for _, tmp := range CreatingClusterStatus {
		if s == tmp {
			return true
		}
	}
	return false
}

// Deleted returns true if cluster is deleted
func (s ClusterStatus) Deleted() bool {
	for _, tmp := range DeletedClusterStatus {
		if s == tmp {
			return true
		}
	}
	return false
}

// DeletedOrCleared returns true if cluster is deleted or cleared
func (s ClusterStatus) DeletedOrCleared() bool {
	for _, tmp := range DeletedOrClearedClusterStatus {
		if s == tmp {
			return true
		}
	}
	return false
}

// ReadyForBackup returns true is cluster is ready for backup
func (c Cluster) ReadyForBackup() bool {
	for _, tmp := range ReadyForBackupClusterStatus {
		if c.Status == tmp {
			return true
		}
	}
	if c.Status == ClusterStatusDeleted && c.PocDeleted {
		return true
	}
	return false
}
