package schema

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

	"gorm.io/gorm"
)

type BackupPolicyName string

const (
	BackupPolicyNameTIER1 BackupPolicyName = "TIER1"
	BackupPolicyNameTIER2 BackupPolicyName = "TIER2"
	BackupPolicyNameTIER3 BackupPolicyName = "TIER3"
)

type BackupPolicy struct {
	ID        uint64 `gorm:"primaryKey;autoIncrement;not null"`
	CreatedAt time.Time
	UpdatedAt time.Time

	Name         BackupPolicyName          `gorm:"type:ENUM('TIER1', 'TIER2', 'TIER3');not null; unique"`
	Description  string                    `gorm:"type:VARCHAR(255);not null"`
	Pitr         *BackupPolicyPiTR         `gorm:"serializer:json;type:JSON;not null"`
	HourlyBackup *BackupPolicyHourlyBackup `gorm:"serializer:json;type:JSON;not null"`
	DailyBackup  *BackupPolicyDailyBackup  `gorm:"serializer:json;type:JSON;not null"`
}

type BackupPolicyPiTR struct {
	RetentionDays int `gorm:"not null"`
}

type BackupPolicyHourlyBackup struct {
	RetentionDays int `gorm:"not null"`
}

type BackupPolicyDailyBackup struct {
	RetentionDays int    `gorm:"not null"`
	StartTimeUtc  string `gorm:"type:VARCHAR(10);not null"`
}

type BackupSettingState string

type BackupMode string

const (
	BackupModeStandardBundle    BackupMode = "standard_bundle"
	BackupModeFlexibleRetention BackupMode = "flexible_retention"
)

const (
	BackupSettingStatePending BackupSettingState = "pending"
	BackupSettingStateCreated BackupSettingState = "created"
	BackupSettingStateDeleted BackupSettingState = "deleted"
	BackupSettingStateExpired BackupSettingState = "expired"
	BackupSettingStateCleanup BackupSettingState = "cleanup"
)

var (
	BackupSettingStatesForDeletedCluster = []BackupSettingState{
		BackupSettingStateDeleted,
		BackupSettingStateExpired,
		BackupSettingStateCleanup,
	}

	BackupSettingStatesClusterDeletedNoAutoBackups = []BackupSettingState{
		BackupSettingStateExpired,
		BackupSettingStateCleanup,
	}

	BackupSettingStatesClusterDeletedStillHaveBackups = []BackupSettingState{
		BackupSettingStateDeleted,
		BackupSettingStateExpired,
	}

	BackupSettingStatesForClusterHasBackup2Expired = []BackupSettingState{
		BackupSettingStatePending,
		BackupSettingStateCreated,
		BackupSettingStateDeleted,
	}
)

type BackupSetting struct {
	Versioned

	ID        uint64 `gorm:"primaryKey;autoIncrement;not null"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt gorm.DeletedAt `gorm:"index"`
	// deleted means the cluster is deleted by user, deleted_at is not nil
	// cleanup means all backups of the cluster is deleted
	// expired means all auto backups of the cluster is expired
	State BackupSettingState `gorm:"type:ENUM('pending', 'created', 'deleted', 'expired', 'cleanup'); not null;index"`
	// last_clean_at means the last time the auto backup of the cluster is expired
	LastCleanAt           *time.Time
	LastAutoBackupAt      *time.Time
	LastScheduleUpdatedAt time.Time `gorm:"index"`

	// owner of the backup setting
	ClusterId uint64 `gorm:"not null;unique"`
	// the new backup schedule field
	BackupPolicy       BackupPolicyName `gorm:"type:ENUM('TIER1', 'TIER2', 'TIER3');"`
	Mode               BackupMode       `gorm:"type:ENUM('standard_bundle', 'flexible_retention');not null;default:'standard_bundle'"`
	RetentionDays      int              `gorm:"not null;default:7"`
	DailyBackupTimeUTC string           `gorm:"type:CHAR(5);not null;default:'00:00'"`
	// the original schedule field, will be turned into internal schedule field when the backup policy is set, and keep working since its ability exceeds the new requirement
	Schedule AutoSchedule `gorm:"type:JSON;not null"`

	// redundant information of the cluster
	SharedPoolId   uint64 `gorm:"not null"`
	TenantId       uint64 `gorm:"not null;index"`
	Name           string `gorm:"type:VARCHAR(64);not null;default '';index"`
	RegionId       string `gorm:"type:VARCHAR(50);not null;index"`
	CloudProvider  string `gorm:"type:VARCHAR(50); not null"`
	ServicePlan    string `gorm:"type:VARCHAR(100);not null;index"`
	ClusterVersion string `gorm:"type:VARCHAR(100);not null"`
}

// BackupReconciliationExclusion records a time range in which scheduled
// backups were intentionally suspended. Missing-backup reconciliation excludes
// these ranges, while normal synchronization remains free to ingest any real
// Regional backup object on either side of the range.
type BackupReconciliationExclusion struct {
	ID           uint64 `gorm:"primaryKey;autoIncrement;not null"`
	CreatedAt    time.Time
	UpdatedAt    time.Time
	SharedPoolId uint64     `gorm:"not null;index:idx_backup_reconciliation_exclusion_range,priority:1;uniqueIndex:uk_backup_reconciliation_exclusion_operation,priority:1"`
	OperationId  string     `gorm:"type:VARCHAR(64);not null;uniqueIndex:uk_backup_reconciliation_exclusion_operation,priority:2"`
	StartAt      time.Time  `gorm:"not null;index:idx_backup_reconciliation_exclusion_range,priority:2"`
	EndAt        *time.Time `gorm:"index"`
}

type AutoSchedule struct {
	Type string `gorm:"type:ENUM('per-day', 'per-hour', 'per-minute');not null"`
	// the number of minute in a day for per-day, the number of minute in an hour for per-hour, useless for per-minute
	At int `gorm:"not null"`
	// the retention days for old schedule
	RetentionDays int `gorm:"not null"`
	// the retention days for New backup schedule
	NonZeroClockRetentionDays int `gorm:"not null"`
	ZeroClockRetentionDays    int `gorm:"not null"`
	// DailyBackupMinuteOfDay identifies the daily snapshot in UTC. For an hourly
	// schedule its minute component also determines the hourly sampling offset.
	DailyBackupMinuteOfDay int `gorm:"not null"`
}

// Value implements the driver.Valuer interface for JSON serialization
func (a AutoSchedule) Value() (driver.Value, error) {
	return json.Marshal(a)
}

// Scan implements the sql.Scanner interface for JSON deserialization
func (a *AutoSchedule) Scan(value any) error {
	if value == nil {
		return nil
	}

	bytes, ok := value.([]byte)
	if !ok {
		return nil
	}

	return json.Unmarshal(bytes, a)
}

type BackupState string

const (
	BackupStateUnknown    BackupState = "unknown"
	BackupStatePending    BackupState = "pending"
	BackupStateSubmitting BackupState = "submitting"
	BackupStateRunning    BackupState = "running"
	BackupStateSuccess    BackupState = "success"
	BackupStateFailed     BackupState = "failed"
	BackupStateCancelled  BackupState = "cancelled"
)

type BackupTriggerType string

const (
	BackupTriggerTypeAuto   BackupTriggerType = "auto"
	BackupTriggerTypeManual BackupTriggerType = "manual"
)

type AutoBackupType string

const (
	AutoBackupTypeUnknown AutoBackupType = "unknown"
	AutoBackupTypeHourly  AutoBackupType = "hourly"
	AutoBackupTypeDaily   AutoBackupType = "daily"
)

type BackupType string

const (
	BackupTypeSnapshot BackupType = "snapshot"
)

type BackupPurpose string

const (
	BackupPurposeFinal BackupPurpose = "final"
)

// TODO: will need to optimize the index in the future based on the query pattern
type Backup struct {
	Versioned

	ID             uint64 `gorm:"primaryKey;autoIncrement;not null"`
	CreatedAt      time.Time
	UpdatedAt      time.Time
	DeletedAt      gorm.DeletedAt `gorm:"index"`
	State          BackupState    `gorm:"type:ENUM('unknown', 'pending', 'submitting', 'running', 'success', 'failed', 'cancelled');index:idx_backup_state"`
	ExpirationTime time.Time      `gorm:"index:idx_backup_expiration"`

	// backup inputs
	Type           BackupType        `gorm:"type:ENUM('snapshot'); not null;index:idx_backup_type"`
	TriggerType    BackupTriggerType `gorm:"type:ENUM('auto', 'manual'); not null;index:idx_backup_trigger"`
	AutoBackupType AutoBackupType    `gorm:"type:ENUM('unknown', 'hourly', 'daily');not null;default:'unknown'"`
	DisplayName    string            `gorm:"type:VARCHAR(64);not null"`
	Description    string            `gorm:"type:VARCHAR(255);not null"`

	// kernel infos
	KernelBackupId   uint64    `gorm:""`
	KernelBackupName string    `gorm:"type:VARCHAR(255)"`
	BackupTime       time.Time `gorm:"index:idx_backup_backup_time"`
	BackupTs         uint64    `gorm:""`
	BackupSizeBytes  uint64    `gorm:""`
	PdClusterId      uint64    `gorm:""`
	DfsPrefix        string    `gorm:"type:VARCHAR(255)"`
	DfsBucket        string    `gorm:"type:VARCHAR(100)"`
	DfsRegion        string    `gorm:"type:VARCHAR(50)"`

	// owner of the backup
	ClusterId uint64 `gorm:"not null;index:idx_backup_cluster;uniqueIndex:idx_pause_operation_cluster;index:idx_backup_purpose_cluster"`
	// redundant information of the cluster
	ClusterVersion string `gorm:"type:VARCHAR(100);not null"`
	SharedPoolId   uint64 `gorm:"not null"`
	TenantId       uint64 `gorm:"not null;index:idx_backup_tenantid"`
	RegionId       string `gorm:"type:VARCHAR(50);not null;index:idx_backup_regionid"`
	CloudProvider  string `gorm:"type:VARCHAR(50);not null;index:idx_backup_provider"`
	ServicePlan    string `gorm:"type:VARCHAR(100);not null;index:idx_backup_serviceplan"`

	CustomerManaged bool   `gorm:"not null;default:false"`
	EncryptionKeyId uint64 `gorm:"not null;default:0"`

	// PauseOperationId is set only for manual backups owned by a Pause
	// preparation. NULL keeps ordinary manual backups outside the unique key.
	PauseOperationId *string `gorm:"type:VARCHAR(128);uniqueIndex:idx_pause_operation_cluster"`
	// Purpose identifies system-owned manual backups. Final-backup creation is
	// serialized by the ResourcePool row lock; this stays non-unique so a final
	// backup can be recreated after its previous record is soft-deleted.
	Purpose *BackupPurpose `gorm:"type:VARCHAR(32);index:idx_backup_purpose_cluster"`
}

type RestoreMode string

const (
	// restore from premium data
	RestoreModeSnapshot RestoreMode = "snapshot"
	RestoreModePitr     RestoreMode = "pitr"
	// restore from dedicated data
	RestoreModeDedicatedSnapshot RestoreMode = "dedicated_snapshot"
	// restore from classic snapshot backup data
	RestoreModeClassicSnapshot RestoreMode = "classic_snapshot"
)

type Restore struct {
	Versioned

	ID        uint64 `gorm:"primaryKey;autoIncrement;not null"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt gorm.DeletedAt `gorm:"index"`
	State     string         `gorm:"type:ENUM('pending', 'running', 'success', 'failed', 'cancelled');index"`

	// restore inputs
	RestoreMode RestoreMode `gorm:"type:ENUM('snapshot', 'pitr', 'dedicated_snapshot', 'classic_snapshot');not null"`
	// for snapshot restore, backup with backup_id will be restored
	// for PiTR restore, it will be 0
	BackupId uint64 `gorm:"not null;index"`
	// for PiTR restore, we use this point_in_time to restore the cluster
	// for snapshot restore, this field is nil
	PointInTime *time.Time `gorm:""`
	// for classic snapshot restore, we use this classic_backup_auth to restore the cluster
	ClassicBackupAuth *ClassicBackupAuth `gorm:"type:JSON;serializer:json;"`
	TargetClusterId   uint64             `gorm:"not null;unique"`

	TenantId uint64 `gorm:"not null;index"`
	// only use for snapshot restore
	ExpectRestoreDataSize uint64 `gorm:""`
	// redundant info of target cluster
	TargetSharedPoolId  uint64 `gorm:"not null;index"`
	TargetRegionId      string `gorm:"type:VARCHAR(50);not null"`
	TargetCloudProvider string `gorm:"type:VARCHAR(50);not null"`
	TargetServicePlan   string `gorm:"type:VARCHAR(100);not null"`
	TargetMinRcu        int64  `gorm:""`
	TargetMaxRcu        int64  `gorm:""`
	// redundant info of source cluster
	SourceClusterId      uint64 `gorm:"not null;index"`
	SourceSharedPoolId   uint64 `gorm:"not null;index"` // only used for premium restore
	SourceRegionId       string `gorm:"type:VARCHAR(50);not null"`
	SourceCloudProvider  string `gorm:"type:VARCHAR(50); not null"`
	SourceServicePlan    string `gorm:"type:VARCHAR(100);not null"`
	SourceBackupStorage  string `gorm:"type:VARCHAR(1024)"` // used for dedicated_snapshot and classic_snapshot
	SourceBucketId       uint64 `gorm:""`                   // only used for dedicated_snapshot
	SourceBackupKmsArn   string `gorm:"type:VARCHAR(2048)"` // source backup CMEK for dedicated_snapshot
	SourceRoleArn        string `gorm:"type:VARCHAR(2048)"` // temporary Dedicated source role for CMEK restore
	SourceRoleExternalId string `gorm:"type:VARCHAR(1224)"`
}

type ClassicBackupAuth struct {
	AccessKeyId     string `json:"access_key_id"`
	AccessKeySecret string `json:"access_key_secret"`
}
