package domain

import (
	"bytes"
	"encoding/gob"
	"time"

	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	"github.com/tidbcloud/dataflow-service/app/config"
	"github.com/tidbcloud/dataflow-service/app/repo/entity"
	"github.com/tidbcloud/dataflow-service/idl/pbgen/proto/dataflow"
	bizerr_wrapper "github.com/tidbcloud/dataflow-service/pkg/bizerr-wrapper"
	"github.com/tidbcloud/pkgv2/bizerr"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/timestamppb"
)

type BackupScheduleType string

const (
	SnapshotSchedule BackupScheduleType = "snapshot"
	PiTRSchedule     BackupScheduleType = "PiTR"
)

type MarkBackupScheduleAsDeletedReq struct {
	ClusterId *uint64
}

type BackupType string

const (
	BackupType_All    BackupType = "all"
	BackupType_Auto   BackupType = "auto"
	BackupType_Manual BackupType = "manual"
)

// BackupStatus is the status of the backup
type BackupStatus string

const (
	BackupStatusUnspecified BackupStatus = "unspecified"
	BackupStatusPending     BackupStatus = "pending"
	BackupStatusRunning     BackupStatus = "running"
	BackupStatusFailed      BackupStatus = "failed"
	BackupStatusSuccess     BackupStatus = "success"
	BackupStatusSkipped     BackupStatus = "skipped"
	BackupStatusTerminated  BackupStatus = "terminated"
)

type CreateBackupSettingReqV2 struct {
	TenantID  *uint64
	ProjectID *uint64
	ClusterID *uint64

	CloudProvider *InfraProviderType
}

type DeleteBackupSettingReqV2 struct {
	ClusterID *uint64
}

type DeleteBackupSettingRespV2 struct{}

type QueryMasterBucketReqV2 struct {
	ClusterID *uint64
}

type QueryMasterBucketRespV2 struct {
	BucketID  *uint64
	BucketURI *string
}

type QueryBackupSettingRespV2 struct {
	BackupSetting *entity.BackupSetting
}

type QueryBackupSettingForAPIResp struct {
	BackupSetting   *entity.BackupSetting
	PitBackupSwitch *entity.PitBackupSwitch

	Status       entity.BackupSettingStatus
	AllowActions []entity.BackupSettingActionType

	CloudProvider          string
	SnapshotBackupSchedule *SnapshotBackupSchedule
	BucketSetting          *BucketSetting
	PiTRSetting            *PiTRSetting
	CRRFStatus             entity.CRRFSettingStatus
}

type SnapshotBackupSchedule struct {
	Disable           bool                       `json:"disable,omitempty"`
	ScheduleType      SnapshotBackupScheduleType `json:"schedule_type,omitempty"`
	DailyScheduleTime string                     `json:"daily_schedule_time,omitempty"`
	WeeklySchedule    *WeeklySchedule            `json:"weekly_schedule,omitempty"`
	RetentionPolicy   *RetentionPolicy           `json:"retention_policy,omitempty"`
	NextBackupTime    *time.Time                 `json:"next_backup_time,omitempty"`
}

type ContinuousBackupSchedule struct {
	Enable bool `json:"enable,omitempty"`
}

type WeeklySchedule struct {
	Days         []WeekDay `json:"days,omitempty"`
	ScheduleTime string    `json:"schedule_time,omitempty"`
}

type RetentionPolicy struct {
	RetentionPeriod int `json:"retention_period,omitempty"`
}

type SnapshotBackupScheduleType string

const (
	SnapshotBackupScheduleTypeDaily  SnapshotBackupScheduleType = "daily"
	SnapshotBackupScheduleTypeWeekly SnapshotBackupScheduleType = "weekly"
)

type WeekDay string

const (
	WeekDaySun WeekDay = "Sun"
	WeekDayMon WeekDay = "Mon"
	WeekDayTue WeekDay = "Tue"
	WeekDayWed WeekDay = "Wed"
	WeekDayThu WeekDay = "Thu"
	WeekDayFri WeekDay = "Fri"
	WeekDaySat WeekDay = "Sat"
)

type BucketSetting struct {
	DualRegionEnable bool
	Buckets          []BackupSettingBucket
	Prefix           *string
}

type BackupSettingBucket struct {
	Regions  []RegionInfo
	Master   bool
	BucketId uint64
}

type PiTRSetting struct {
	Enable bool `json:"enable,omitempty"`
}

type ListBackupSettingFilter struct {
	ClusterIDs []uint64
}

type ListBackupSettingReq struct {
	Filter *ListBackupSettingFilter
}

type ListBackupSettingResp struct {
	Items []ListBackupSettingRespItem
	Total int64
}

type ListBackupSettingRespItem struct {
	ID            uint64
	BackupSetting *entity.BackupSetting
}

type RegionSettingModificationReqV2 struct {
	ClusterID *uint64
	Enable    bool
	// first region is primary region
	Regions []string
	DryRun  bool
}

type ReplicationRTCModificationReq struct {
	ClusterID uint64
	Enable    bool

	// BucketID This avoids reading a temporarily-zero BucketId
	// that occurs during dual-region disable transitions. The bucket is still validated to
	// belong to the same project as the backup setting.
	BucketID uint64

	DryRun bool
}

type RegionSettingModificationRespV2 struct {
	WorkflowID *string
}

type AsyncCreateBackupReq struct {
	Name             string              `json:"name"`
	Description      string              `json:"description"`
	ProjectID        uint64              `json:"project_id"`
	ClusterID        uint64              `json:"cluster_id"`
	UserID           uint64              `json:"user_id"`
	BackupType       dataflow.BackupType `json:"backup_type"`
	DryRun           bool                `json:"-"`
	TableFilter      []string            `json:"table_filter"`
	ExpectedBackupTS string              `json:"expected_backup_ts"` // specify start tso of backup
	BucketID         uint64              `json:"bucket_id"`
	Prefix           string              `json:"prefix"`
	IsInternal       bool                `json:"is_internal"`
	IsWeekly         bool                `json:"is_weekly"`

	AutoBackupScheduleID *uint64 `json:"auto_backup_schedule_id,omitempty"`
}

func (req *AsyncCreateBackupReq) Clone() (*AsyncCreateBackupReq, error) {
	buf := bytes.NewBuffer(nil)
	cloned := &AsyncCreateBackupReq{}
	err := gob.NewEncoder(buf).Encode(req)
	if err != nil {
		return nil, err
	}
	err = gob.NewDecoder(buf).Decode(cloned)
	if err != nil {
		return nil, err
	}
	return cloned, nil
}

func (req *AsyncCreateBackupReq) ValidateReq(logger *zap.Logger) bizerr.BizError {
	// user input check should return validation error, this will be a 400 response to user
	if req.Name == "" {
		logger.Warn("backup name is empty", zap.Any("req", req))
		return bizerr_wrapper.NewInvalidInputError(
			config.ErrMsgAsyncCreateBackupReqNotValid,
			req.ClusterID, "backup name is empty")
	}
	// other unexpected input should(maybe caused by caller bug) return internal error,
	// this will be a 500 response to user
	if req.BackupType != dataflow.BackupType_AUTO && req.BackupType != dataflow.BackupType_MANUAL {
		logger.Error("backup type is invalid", zap.Any("req", req))
		return bizerr_wrapper.NewInternalErr(config.ErrMsgInternalError, req.ClusterID)
	}
	if req.BackupType == dataflow.BackupType_AUTO && req.AutoBackupScheduleID == nil {
		logger.Error("auto backup schedule id is empty", zap.Any("req", req))
		return bizerr_wrapper.NewInternalErr(config.ErrMsgInternalError, req.ClusterID)
	}
	return nil
}

type AsyncCreateBackupResp struct {
	WorkflowID       string
	BackupID         uint64 // only when snapshot backup
	LogBackupID      uint64 // only when logBackup
	BackupScheduleID uint64
}

type AsyncDeleteBackupReq struct {
	BackupID uint64
}

type AsyncDeleteBackupResp struct {
	WorkflowID string
}

type PitSettingModificationReq struct {
	ClusterID uint64
	Enable    bool
	DryRun    bool
}

type SwitchPitBackupReq struct {
	ClusterID uint64
	DryRun    bool
}

type GetOrCreatePitBackupSwitchResp struct {
	PitBackupSwitch *entity.PitBackupSwitch

	AllowAction []entity.PitBackupSwitchActionType
}

type EnablePitBackupReq struct {
	SwitchPitBackupReq
}

type EnablePitBackupResp struct {
	PitBackupSwitchID uint64
}

type DisablePitBackupReq struct {
	SwitchPitBackupReq
}

type DisablePitBackupResp struct {
	WorkflowID        string
	PitBackupSwitchID uint64
}

type RestartPitBackupReq struct {
	SwitchPitBackupReq
}

type RestartPitBackupResp struct {
	WorkflowID        string
	PitBackupSwitchID uint64
}

type CancelRestartPitBackupReq struct {
	SwitchPitBackupReq
}

type QueryBackupReq struct {
	Filter *ListBackupFilter
}

type ListBackupFilter struct {
	TenantID   uint64
	ProjectID  uint64
	ClusterIDs []uint64
	BackupIDs  []uint64
	Statuses   []BackupStatus
	Region     *string
}

type ListBackupReq struct {
	Filter *ListBackupFilter
	Pager  *Pager
}

type ListBackupScheduleFilter struct {
	ClusterIDs []uint64
	Name       string
	UserId     uint64
}

type ListByScheduleFilterReq struct {
	Filter *ListBackupScheduleFilter
}

type ListBackupResp struct {
	Items []ListBackupRespItem
	Total int64
}

type ListBackupRespItem struct {
	ID             uint64
	ClusterID      uint64
	ClusterConfig  ClusterConfig
	Name           string
	Description    string
	CreatedTime    time.Time
	Size           uint64
	Status         BackupStatus
	Type           BackupType
	ExpirationTime *time.Time
	BackupTs       *int64
	Regions        []RegionInfo
	ClusterVersion string
	TableFilter    []string
}

type QueryBackupResp struct {
	BackupEntity *entity.Backup
	BackupID     uint64
	ClusterID    uint64
}

type ListRecycleBinClustersReq struct {
	TenantID  uint64
	ProjectID uint64
	Pager     *Pager
}

type ListRecycleBinClustersV2Req struct {
	TenantID    uint64
	ProjectIDs  []uint64
	ClusterName string
	Regions     []string
	StartTime   *time.Time
	EndTime     *time.Time
	Pager       *Pager
}

type ListRecycleBinClustersResp struct {
	Items []*RecycleBinCluster
	Total int64
}

type RecycleBinCluster struct {
	ProjectID     uint64
	ClusterID     uint64
	ClusterName   string
	CloudProvider CloudProviderType
	Region        string
	RegionName    string
	DeleteTime    *time.Time
}

func (c *RecycleBinCluster) ToDataflowRecycleBinCluster() *dataflow.RecycleBinCluster {
	cluster := &dataflow.RecycleBinCluster{
		ProjectId:  c.ProjectID,
		Id:         c.ClusterID,
		Name:       c.ClusterName,
		Provider:   string(c.CloudProvider),
		Region:     c.Region,
		RegionName: c.RegionName,
	}
	if c.DeleteTime != nil {
		cluster.DeleteTime = timestamppb.New(*c.DeleteTime)
	}
	return cluster
}

func Cluster2RecycleBinCluster(cluster *core.Cluster) *RecycleBinCluster {
	recycleBinCluster := &RecycleBinCluster{
		ProjectID:     cluster.ProjectId,
		ClusterID:     cluster.Id,
		ClusterName:   cluster.Name,
		CloudProvider: CloudProviderType(cluster.CloudProviderName),
		Region:        cluster.RegionName,
		RegionName:    cluster.RegionDisplayName,
	}
	if cluster.DeletedAt != nil {
		deleteTime := time.Unix(*cluster.DeletedAt, 0)
		recycleBinCluster.DeleteTime = &deleteTime
	}
	return recycleBinCluster
}

type QueryBackupRegionOptionsReq struct {
	ClusterID uint64
	TenantID  uint64
	ProjectID uint64
}

type QueryBackupRegionOptionsResp struct {
	ClusterID           uint64
	ClusterRegion       *RegionInfo
	BackupRegionOptions []BackupRegionOption
}

type RegionInfo struct {
	Name        string
	DisplayName string
}

type BackupRegionOption struct {
	Region      *RegionInfo
	Continent   string
	Description string
}

type ModifyAutoBackupSettingReq struct {
	ClusterID uint64
	SnapshotBackupSchedule
	DryRun bool
}

type ModifyAutoBackupSettingResp struct{}

type DeleteLogBackupReq struct {
	LogBackupID uint64
}

type DeleteLogBackupResp struct {
	WorkflowID string
}

type PiTRTimeRangeListResp struct {
	Items []*PiTRRegionTimeRange
	Msg   string
}

type CreateFinalBackupReq struct {
	ClusterID uint64
}

// backup export begin //
type GetBackupExportProfileReq struct {
	BackupID uint64
}

type GetBackupExportProfileResp struct {
	GCPServiceAccount string
	S3RoleArn         string
	AzBlobRoleArn     string
}

// backup export end //

// CompactBackup related domain objects

type AsyncCreateCompactBackupReq struct {
	ClusterID   uint64 `json:"cluster_id"`
	LogBackupID uint64 `json:"log_backup_id"`
	UserID      uint64 `json:"user_id"`
	ProjectID   uint64 `json:"project_id"`
	DryRun      bool   `json:"-"`
	IsInternal  bool   `json:"is_internal"`
	// BucketID specifies which bucket (primary or secondary) to use for compaction.
	// Defaults to 0, meaning the primary bucket from the log backup storage is used.
	BucketID uint64 `json:"bucket_id,omitempty"`
	// StartTS and EndTS are optional pre-computed TSO strings for the compaction time range.
	// When set, createDBEntity uses them directly and skips log-backup-derived values.
	// When either StartTS or EndTS is set, it is used directly; the model derives missing values from log backup if either is empty.
	StartTS string `json:"start_ts,omitempty"`
	EndTS   string `json:"end_ts,omitempty"`
}

func (req *AsyncCreateCompactBackupReq) ValidateReq(logger *zap.Logger) bizerr.BizError {
	if req.ClusterID == 0 {
		logger.Warn("cluster id is empty", zap.Any("req", req))
		return bizerr_wrapper.NewInvalidInputError(
			config.ErrMsgAsyncCreateBackupReqNotValid,
			req.ClusterID, "cluster id is empty")
	}
	if req.LogBackupID == 0 {
		logger.Warn("log backup id is empty", zap.Any("req", req))
		return bizerr_wrapper.NewInvalidInputError(
			config.ErrMsgAsyncCreateBackupReqNotValid,
			req.ClusterID, "log backup id is empty")
	}
	return nil
}

type AsyncCreateCompactBackupResp struct {
	WorkflowID      string
	CompactBackupID uint64
}

type AsyncDeleteCompactBackupReq struct {
	CompactBackupID uint64
}

type AsyncDeleteCompactBackupResp struct {
	WorkflowID string
}
