package entity

import (
	"time"

	"github.com/tidbcloud/serverless-service/internal/domain/constant"
	"gorm.io/gorm"
)

type BackupJob struct {
	ID              int64                   `gorm:"type:bigint(20);primaryKey;autoIncrement;not null"`
	CreatedAt       *time.Time              `gorm:"type:timestamp;not null;autoCreateTime"`
	UpdatedAt       *time.Time              `gorm:"type:timestamp;default:null;autoUpdateTime"`
	StartTime       *time.Time              `gorm:"type:timestamp;not null;index"`
	State           constant.BackupJobState `gorm:"type:enum('succeeded','failed','pending');default:pending"`
	ExecuteTimes    int                     `gorm:"type:int;default:1"`
	EndTime         *time.Time              `gorm:"type:timestamp;null;"`
	SucceededNumber int                     `gorm:"type:int;default:0"`
	FailedNumber    int                     `gorm:"type:int;default:0"`
	LogID           string                  `gorm:"log_id;type:varchar(255);null"`
}

func (b *BackupJob) TableName() string {
	return "backup_jobs"
}

type BackupSchedule struct {
	ID               int64          `gorm:"type:bigint(20);primaryKey;autoIncrement;not null"`
	CreatedAt        *time.Time     `gorm:"type:timestamp;not null;autoCreateTime"`
	UpdatedAt        *time.Time     `gorm:"type:timestamp;default:null;autoUpdateTime"`
	DeletedAt        gorm.DeletedAt `gorm:"type:timestamp;index"`
	TenantID         string         `gorm:"type:varchar(255);not null"`
	ClusterID        string         `gorm:"type:varchar(255);not null;uniqueIndex"`
	Region           string         `gorm:"type:varchar(255);not null"`
	NextScheduleTime *time.Time     `gorm:"type:timestamp;not null;index"`
	ScheduleCron     string         `gorm:"type:varchar(255);not null"`
	// Retention in hours
	Retention                 int    `gorm:"type:int(11);not null"`
	Kubernetes                string `gorm:"type:varchar(255);default:''"`
	CloudProvider             string `gorm:"type:varchar(255);default:''"`
	SourceToken               string `gorm:"type:varchar(255);default:null"`
	EnhancedEncryptionEnabled *bool  `gorm:"type:tinyint(1);default:null"`
	ZoneRedundantEnabled      *bool  `gorm:"type:tinyint(1);default:null"`
	ServicePlan               string `gorm:"type:varchar(64);default:null"`
}

func (b *BackupSchedule) TableName() string {
	return "backup_schedules"
}

type BackupScheduleHistory struct {
	ID            int64      `gorm:"type:bigint(20);primaryKey;autoIncrement;not null"`
	CreatedAt     *time.Time `gorm:"type:timestamp;not null;autoCreateTime"`
	UpdatedAt     *time.Time `gorm:"type:timestamp;default:null;autoUpdateTime"`
	ScheduleID    int64      `gorm:"type:bigint(20);not null;index"`
	TenantID      string     `gorm:"type:varchar(255);not null;index:idx_bsh_tenant_valid_to,priority:1"`
	ClusterID     string     `gorm:"type:varchar(255);not null;index:idx_bsh_cluster_valid_from,priority:1;index:idx_bsh_cluster_valid_to,priority:1"`
	Region        string     `gorm:"type:varchar(255);not null"`
	ScheduleCron  string     `gorm:"type:varchar(255);not null"`
	Retention     int        `gorm:"type:int(11);not null"`
	Kubernetes    string     `gorm:"type:varchar(255);default:''"`
	CloudProvider string     `gorm:"type:varchar(255);default:''"`
	SourceToken   string     `gorm:"type:varchar(255);default:null"`

	EnhancedEncryptionEnabled *bool `gorm:"type:tinyint(1);default:null"`
	ZoneRedundantEnabled      *bool `gorm:"type:tinyint(1);default:null"`

	ValidFrom *time.Time `gorm:"type:timestamp;not null;index:idx_bsh_cluster_valid_from,priority:2"`
	ValidTo   *time.Time `gorm:"type:timestamp;default:null;index:idx_bsh_cluster_valid_to,priority:2;index:idx_bsh_tenant_valid_to,priority:2"`
	DeletedAt *time.Time `gorm:"type:timestamp;default:null"`
}

func (b *BackupScheduleHistory) TableName() string {
	return "backup_schedule_histories"
}

type BackupVirtualizationMetadata struct {
	ID        uint8     `gorm:"type:tinyint unsigned;primaryKey;not null"`
	CutoverAt time.Time `gorm:"type:timestamp;not null"`
}

func (b *BackupVirtualizationMetadata) TableName() string {
	return "backup_virtualization_metadata"
}

func NewBackupScheduleHistory(schedule *BackupSchedule, validFrom time.Time) *BackupScheduleHistory {
	return &BackupScheduleHistory{
		ScheduleID:                schedule.ID,
		TenantID:                  schedule.TenantID,
		ClusterID:                 schedule.ClusterID,
		Region:                    schedule.Region,
		ScheduleCron:              schedule.ScheduleCron,
		Retention:                 schedule.Retention,
		Kubernetes:                schedule.Kubernetes,
		CloudProvider:             schedule.CloudProvider,
		SourceToken:               schedule.SourceToken,
		EnhancedEncryptionEnabled: schedule.EnhancedEncryptionEnabled,
		ZoneRedundantEnabled:      schedule.ZoneRedundantEnabled,
		ValidFrom:                 &validFrom,
	}
}

// NewBackupScheduleHistoryFromSchedule creates a history record for the current
// schedule. It computes ValidFrom from the schedule creation time, the
// virtualization cutover, and the most recent previous history's ValidTo,
// optionally bounded above by validTo (used when archiving).
func NewBackupScheduleHistoryFromSchedule(
	schedule *BackupSchedule,
	cutover time.Time,
	previousHistories []*BackupScheduleHistory,
	validTo *time.Time,
) *BackupScheduleHistory {
	validFrom := time.Time{}
	if schedule.CreatedAt != nil {
		validFrom = schedule.CreatedAt.UTC()
	}
	if cutover.After(validFrom) {
		validFrom = cutover.UTC()
	}
	for _, history := range previousHistories {
		if history == nil || history.ValidTo == nil {
			continue
		}
		if validTo != nil && history.ValidTo.After(*validTo) {
			continue
		}
		if history.ValidTo.After(validFrom) {
			validFrom = history.ValidTo.UTC()
		}
	}
	history := NewBackupScheduleHistory(schedule, validFrom)
	history.ValidTo = validTo
	return history
}

// EffectiveFrom returns the earliest instant this history can produce virtual
// backups, accounting for the virtualization cutover. The result is in UTC.
func (b *BackupScheduleHistory) EffectiveFrom(cutover time.Time) time.Time {
	if b.ValidFrom == nil {
		return cutover.UTC()
	}
	if b.ValidFrom.Before(cutover) {
		return cutover.UTC()
	}
	return b.ValidFrom.UTC()
}

// EffectiveEnd returns the latest instant this history can produce virtual
// backups, accounting for the history close time and cluster deletion. The
// physical backup slot is the only readiness gate. The result is exclusive and
// in UTC.
func (b *BackupScheduleHistory) EffectiveEnd(now time.Time) time.Time {
	end := now.UTC()
	if b.ValidTo != nil && b.ValidTo.Before(end) {
		end = b.ValidTo.UTC()
	}
	if b.DeletedAt != nil && b.DeletedAt.Before(end) {
		end = b.DeletedAt.UTC()
	}
	return end
}

// Contains reports whether t falls inside this history's raw validity
// interval. It does not re-validate the cron expression; the schedule time is
// trusted because the virtual backup ID already encodes it.
func (b *BackupScheduleHistory) Contains(t time.Time) bool {
	if b.ValidFrom != nil && t.Before(b.ValidFrom.UTC()) {
		return false
	}
	if b.ValidTo != nil && !t.Before(b.ValidTo.UTC()) {
		return false
	}
	if b.DeletedAt != nil && !t.Before(b.DeletedAt.UTC()) {
		return false
	}
	return true
}

func (b *BackupScheduleHistory) GetKubernetes() string {
	if b.Kubernetes == "" {
		return DefaultKubernetesName(b.Region)
	}
	return b.Kubernetes
}

func (b *BackupScheduleHistory) GetCloudProvider() string {
	if b.CloudProvider == "" {
		return DefaultCloudProvider
	}
	return b.CloudProvider
}

func (b *BackupSchedule) GetKubernetes() string {
	// for backward compatibility
	if b.Kubernetes == "" {
		return DefaultKubernetesName(b.Region)
	}
	return b.Kubernetes
}

func (b *BackupSchedule) GetCloudProvider() string {
	if b.CloudProvider == "" {
		return DefaultCloudProvider
	}
	return b.CloudProvider
}

// IsPending reports whether the schedule is waiting for its Kubernetes target
// to be resolved. The "unknown" placeholder is set when a schedule is created
// before the cluster-created event provides the real kubernetes.
func (b *BackupSchedule) IsPending() bool {
	return b.Kubernetes == constant.KubernetesUnknown
}

type ListBackupScheduleOptions struct {
	ClusterId                   string
	ClustersIn                  []string
	IncludeDeleted              bool
	IncompleteSnapshot          bool
	IncompleteSnapshotBatchSize int
}

type ListBackupScheduleHistoryOptions struct {
	ClusterId          string
	TenantId           string
	ClustersIn         []string
	ValidBeforeOrEqual *time.Time
	ValidAfter         *time.Time
	DeletedOnly        bool
	RegionPairs        []ProviderRegion
}

type UserBackup struct {
	ID           string               `gorm:"type:varchar(255);primaryKey;not null"`
	State        constant.BackupState `gorm:"type:enum('succeeded','failed','running');not null;"`
	CreatedAt    *time.Time           `gorm:"type:timestamp;not null;autoCreateTime"`
	UpdatedAt    *time.Time           `gorm:"type:timestamp;default:null;autoUpdateTime"`
	TenantID     string               `gorm:"type:varchar(255);not null"`
	ClusterID    string               `gorm:"type:varchar(255);index;not null"`
	Region       string               `gorm:"type:varchar(255);not null"`
	Type         constant.BackupType  `gorm:"type:enum('auto','manual');default:null;"`
	BackupTime   *time.Time           `gorm:"type:timestamp;index;default:null"`
	ExpireTime   *time.Time           `gorm:"type:timestamp;index;default:null"`
	ScheduleTime *time.Time           `gorm:"type:timestamp;default:null"`
	BackupID     uint64               `gorm:"type:bigint(20);default:0"`
	BackupName   string               `gorm:"type:varchar(255);default:null"`
}

func (b *UserBackup) TableName() string {
	return "user_backups"
}

type UserBackupDeleteOptions struct {
	ExpireTimeLt *time.Time
	Limit        int
}

type BackupTombstone struct {
	ID           int64      `gorm:"type:bigint(20);primaryKey;autoIncrement;not null"`
	CreatedAt    *time.Time `gorm:"type:timestamp;not null;autoCreateTime"`
	UpdatedAt    *time.Time `gorm:"type:timestamp;default:null;autoUpdateTime"`
	TenantID     string     `gorm:"type:varchar(255);not null;index"`
	ClusterID    string     `gorm:"type:varchar(255);not null;uniqueIndex:uk_cluster_schedule"`
	BackupID     string     `gorm:"type:varchar(255);not null"`
	ScheduleTime *time.Time `gorm:"type:timestamp;not null;index;uniqueIndex:uk_cluster_schedule"`
	DeletedAt    *time.Time `gorm:"type:timestamp;not null;index"`
	ExpireTime   *time.Time `gorm:"type:timestamp;default:null;index"`
}

func (b *BackupTombstone) TableName() string {
	return "backup_tombstones"
}

type ListBackupTombstoneOptions struct {
	ClusterId  string
	ClustersIn []string
}

type BackupTombstoneDeleteOptions struct {
	ExpireTimeLt           *time.Time
	PersistedExpireTimeGte *time.Time
	Limit                  int
}

// PhysicalBackupSlot is the Kubernetes-level physical backup selected for a
// half-hour restore slot. It is shared by every serverless cluster on the same
// Kubernetes and avoids materializing one row per cluster backup.
type PhysicalBackupSlot struct {
	ID            int64      `gorm:"type:bigint(20);primaryKey;autoIncrement;not null"`
	CreatedAt     *time.Time `gorm:"type:timestamp;not null;autoCreateTime"`
	CloudProvider string     `gorm:"type:varchar(255);not null;uniqueIndex:uk_physical_backup_slot,priority:1"`
	Region        string     `gorm:"type:varchar(255);not null;uniqueIndex:uk_physical_backup_slot,priority:2"`
	Kubernetes    string     `gorm:"type:varchar(255);not null;uniqueIndex:uk_physical_backup_slot,priority:3"`
	SlotTime      time.Time  `gorm:"type:timestamp;not null;uniqueIndex:uk_physical_backup_slot,priority:4"`
	BackupID      uint64     `gorm:"type:bigint(20);not null"`
	BackupName    string     `gorm:"type:varchar(255);not null"`
	BackupTime    time.Time  `gorm:"type:timestamp;not null;index"`
}

type PhysicalBackupSlotKey struct {
	CloudProvider string
	Region        string
	Kubernetes    string
	SlotTime      time.Time
}

type PhysicalBackupSlotScope struct {
	CloudProvider string
	Region        string
	Kubernetes    string
}

func (b *PhysicalBackupSlot) TableName() string { return "physical_backup_slots" }

type ListUserBackupOptions struct {
	ClusterId     string
	TenantId      string
	PageSize      *int32
	Offset        *int64
	OrderBy       string
	ExpireTimeGte *time.Time
	BackupTimeGte *time.Time
	States        []constant.BackupState
}

type RecycledInstance struct {
	ClusterID                 string     `gorm:"column:cluster_id"`
	ProjectID                 string     `gorm:"column:project_id"`
	Region                    string     `gorm:"column:region"`
	DisplayName               string     `gorm:"column:display_name"`
	DeletedAt                 *time.Time `gorm:"column:deleted_at"`
	EnhancedEncryptionEnabled bool       `gorm:"column:enhanced_encryption_enabled"`
	ZoneRedundantEnabled      bool       `gorm:"column:zone_redundant_enabled"`
	Provider                  string     `gorm:"column:provider"`
}

type ProviderRegion struct {
	Provider string
	Region   string
}

type ListRecycledInstanceOptions struct {
	ClusterId     string
	TenantId      string
	PageSize      *int32
	Offset        *int64
	OrderBy       string
	ExpireTimeGte *time.Time
	RegionPairs   []ProviderRegion
}

type RestoreJob struct {
	ID              string                   `gorm:"primary_key;not null"`
	CreatedAt       time.Time                `gorm:"created_at;type:TIMESTAMP;not null;<-:create"`
	UpdatedAt       *time.Time               `gorm:"updated_at;type:TIMESTAMP"`
	RestoreId       uint64                   `gorm:"type:bigint(20);null"`
	Region          string                   `gorm:"type:varchar(255);not null"`
	ClusterStatusId uint64                   `gorm:"type:bigint(20);null"`
	TenantId        uint64                   `gorm:"type:bigint(20);not null"`
	SourceClusterId string                   `gorm:"type:varchar(255);not null"`
	TargetClusterId string                   `gorm:"type:varchar(255);not null"`
	SourceToken     string                   `gorm:"type:varchar(255);null"`
	TargetToken     string                   `gorm:"type:varchar(255);null"`
	BackupId        string                   `gorm:"type:varchar(255);null"`
	BackupInfo      *UserBackup              `gorm:"type:json;null;serializer:json"`
	BackupTime      *time.Time               `gorm:"type:timestamp;null"`
	State           constant.RestoreJobState `gorm:"type:enum('pending','succeeded','failed','running','canceling','canceled');default:running"`
	SizeBytes       uint64                   `gorm:"type:bigint(20);default:0"`
	CreatedBy       string                   `gorm:"type:varchar(255);not null"`
	Progress        RestoreProgress          `gorm:"type:json;null;serializer:json"`
	Error           string                   `gorm:"type:text;null"`
	Kubernetes      string                   `gorm:"type:varchar(255);default:''"`
	CloudProvider   string                   `gorm:"type:varchar(255);default:''"`
}

func (r *RestoreJob) GetKubernetes() string {
	// for backward compatibility
	if r.Kubernetes == "" {
		return DefaultKubernetesName(r.Region)
	}
	return r.Kubernetes
}

func (r *RestoreJob) GetCloudProvider() string {
	if r.CloudProvider == "" {
		return DefaultCloudProvider
	}
	return r.CloudProvider
}

type RestoreProgress struct {
	Step             string `json:"step"`
	Percent          int32  `json:"percent"`
	LastTransitionTS int64  `json:"last_transition_ts"`
}

func (r *RestoreJob) TableName() string {
	return "restore_jobs"
}

type ListRestoreJobOptions struct {
	State constant.RestoreJobState
	// target cluster id
	ClusterId string
	PageSize  *int32
	Offset    *int64
	OrderBy   string
}

func (r *RestoreJob) IsFinalState() bool {
	return r.State == constant.RestoreJobStateSucceeded || r.State == constant.RestoreJobStateFailed
}

type RestoreJobStateCount struct {
	State constant.RestoreJobState
	Count int
}

func (r *RestoreJob) IsRestoreToNewCluster() bool {
	return r.SourceClusterId != r.TargetClusterId
}

type BackupScheduleCheckJob struct {
	ID           int64                                `gorm:"type:bigint(20);primaryKey;autoIncrement;not null"`
	CreatedAt    *time.Time                           `gorm:"type:timestamp;not null;autoCreateTime"`
	UpdatedAt    *time.Time                           `gorm:"type:timestamp;default:null;autoUpdateTime"`
	CheckDate    string                               `gorm:"type:varchar(255);not null;uniqueIndex"`
	State        constant.BackupScheduleCheckJobState `gorm:"type:enum('succeeded','failed','pending');default:pending"`
	ExecuteTimes int                                  `gorm:"type:int;default:1"`
	LogID        string                               `gorm:"log_id;type:varchar(255);null"`
}

func (b *BackupScheduleCheckJob) TableName() string {
	return "backup_schedule_check_jobs"
}
