package br

import (
	"context"
	"strconv"
	"time"

	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/known/timestamppb"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func (s *serviceImpl) assembleBackupSetting(
	ctx context.Context,
	clusterId string,
) (*globalngv1.BackupSetting, bizerr.BizError) {
	clusterIdUint64, err := strconv.ParseUint(clusterId, 10, 64)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster_id is invalid")
	}

	backupSettingInDB, err := s.backupSettingRepo.Get(ctx, clusterIdUint64)
	if err != nil {
		return nil, bizerr.WrapError(err, "failed to get backup setting from db")
	}

	backupSetting := &globalngv1.BackupSetting{
		ClusterId:          strconv.FormatUint(backupSettingInDB.ClusterId, 10),
		RetentionDays:      proto.Int32(int32(backupSettingInDB.RetentionDays)),
		DailyBackupTimeUtc: proto.String(backupSettingInDB.DailyBackupTimeUTC),
	}
	if backupSettingInDB.Mode == schema.BackupModeFlexibleRetention {
		backupSetting.Mode = globalngv1.BackupSetting_FLEXIBLE_RETENTION
	} else {
		backupSetting.Mode = globalngv1.BackupSetting_STANDARD_BUNDLE
		backupSetting.RetentionDays = proto.Int32(StandardRetentionDays)
		backupSetting.DailyBackupTimeUtc = proto.String(DefaultDailyBackupTimeUTC)
	}

	backupSetting.LastModifyTime = timestamppb.New(backupSettingInDB.LastScheduleUpdatedAt)
	if backupSettingInDB.DeletedAt.Valid {
		backupSetting.DeleteTime = timestamppb.New(backupSettingInDB.DeletedAt.Time)
	}

	switch backupSettingInDB.BackupPolicy {
	case schema.BackupPolicyNameTIER1:
		backupSetting.BackupPolicy = globalngv1.BackupSetting_BackupPolicy_TIER1
	case schema.BackupPolicyNameTIER2:
		backupSetting.BackupPolicy = globalngv1.BackupSetting_BackupPolicy_TIER2
	case schema.BackupPolicyNameTIER3:
		backupSetting.BackupPolicy = globalngv1.BackupSetting_BackupPolicy_TIER3
	}

	// caculate pitr start and end time
	pitrStartTime, pitrEndTime := s.calculatePitrTimes(ctx, backupSettingInDB, time.Now(), s.cfg.BizConfig.BackupConfig.ScheduleConfig.PitrRPO)
	backupSetting.PitrStartTime = pitrStartTime
	backupSetting.PitrEndTime = pitrEndTime

	return backupSetting, nil
}

func (s *serviceImpl) calculatePitrTimes(
	_ context.Context,
	backupSettingInDB *entity.BackupSetting,
	now time.Time,
	pitrRPO time.Duration,
) (start, end *timestamppb.Timestamp) {
	lastMoment := now
	if backupSettingInDB.DeletedAt.Valid {
		lastMoment = backupSettingInDB.DeletedAt.Time
	}

	endTime := lastMoment.Add(-pitrRPO)

	startTime := backupSettingInDB.CreatedAt.Add(time.Minute)
	retentionDays := backupSettingInDB.Schedule.NonZeroClockRetentionDays
	if retentionDays == 0 {
		retentionDays = StandardRetentionDays
	}
	minRetentionTime := now.Add(-24 * time.Duration(retentionDays) * time.Hour)
	if minRetentionTime.After(startTime) {
		startTime = minRetentionTime
	}

	if startTime.After(endTime) {
		return nil, nil
	}

	return timestamppb.New(startTime), timestamppb.New(endTime)
}

func convertStringArrayToUint64Array(ids []string) ([]uint64, error) {
	parsedIds := make([]uint64, len(ids))
	for i := range ids {
		parsed, err := strconv.ParseUint(ids[i], 10, 64)
		if err != nil {
			return nil, err
		}
		parsedIds[i] = parsed
	}
	return parsedIds, nil
}

func convertBackupStatesProtoToSchema(states []globalngv1.Backup_State) []schema.BackupState {
	schemaStates := []schema.BackupState{}
	for _, state := range states {
		switch state {
		case globalngv1.Backup_STATE_UNKNOWN:
			schemaStates = append(schemaStates, schema.BackupStateUnknown)
		case globalngv1.Backup_STATE_PENDING:
			schemaStates = append(schemaStates, schema.BackupStatePending, schema.BackupStateSubmitting)
		case globalngv1.Backup_STATE_RUNNING:
			schemaStates = append(schemaStates, schema.BackupStateRunning)
		case globalngv1.Backup_STATE_SUCCESS:
			schemaStates = append(schemaStates, schema.BackupStateSuccess)
		case globalngv1.Backup_STATE_FAILED:
			schemaStates = append(schemaStates, schema.BackupStateFailed)
		case globalngv1.Backup_STATE_CANCELLED:
			schemaStates = append(schemaStates, schema.BackupStateCancelled)
		}
	}
	return schemaStates
}

func convertServicePlansProtoToSchema(servicePlans []globalcommonv1.ServicePlan) []string {
	schemaServicePlans := []string{}
	for _, servicePlan := range servicePlans {
		schemaServicePlans = append(schemaServicePlans, servicePlan.String())
	}
	return schemaServicePlans
}

func convertBackupTriggerTypesProtoToSchema(triggerTypes []globalngv1.Backup_TriggerType) []schema.BackupTriggerType {
	schemaTriggerTypes := []schema.BackupTriggerType{}
	for _, triggerType := range triggerTypes {
		switch triggerType {
		case globalngv1.Backup_TRIGGER_TYPE_MANUAL:
			schemaTriggerTypes = append(schemaTriggerTypes, schema.BackupTriggerTypeManual)
		case globalngv1.Backup_TRIGGER_TYPE_AUTO:
			schemaTriggerTypes = append(schemaTriggerTypes, schema.BackupTriggerTypeAuto)
		}
	}
	return schemaTriggerTypes
}

func convertTiDBStateToBackupSettingStates(
	state globalngv1.ListBackupsRequest_ClusterState,
) []schema.BackupSettingState {
	backupSettingStates := []schema.BackupSettingState{}
	switch state {
	case globalngv1.ListBackupsRequest_ACTIVE:
		backupSettingStates = append(backupSettingStates, schema.BackupSettingStateCreated)
	case globalngv1.ListBackupsRequest_DELETED:
		backupSettingStates = append(backupSettingStates, schema.BackupSettingStatesClusterDeletedStillHaveBackups...)
	}
	return backupSettingStates
}

func convertBackupToProtoBackup(backup *entity.Backup) *globalngv1.Backup {
	createTime := backup.BackupTime
	if createTime.IsZero() {
		createTime = backup.CreatedAt
	}

	var expirationTime *timestamppb.Timestamp
	if backup.TriggerType != schema.BackupTriggerTypeManual {
		expirationTime = timestamppb.New(backup.ExpirationTime)
	}

	return &globalngv1.Backup{
		Id:              strconv.FormatUint(backup.ID, 10),
		ClusterId:       strconv.FormatUint(backup.ClusterId, 10),
		OrganizationId:  strconv.FormatUint(backup.TenantId, 10),
		ServicePlan:     globalcommonv1.ServicePlan(globalcommonv1.ServicePlan_value[backup.ServicePlan]),
		DisplayName:     backup.DisplayName,
		Description:     backup.Description,
		CreateTime:      timestamppb.New(createTime),
		Size:            int64(backup.BackupSizeBytes),
		State:           convertBackupStateSchemaToProto(backup.State),
		Type:            convertBackupTypeSchemaToProto(backup.Type),
		TriggerType:     convertBackupTriggerTypeSchemaToProto(backup.TriggerType),
		ExpirationTime:  expirationTime,
		BackupTs:        strconv.FormatUint(backup.BackupTs, 10),
		RegionId:        backup.RegionId,
		CustomerManaged: backup.CustomerManaged,
		EncryptionKeyId: backup.EncryptionKeyId,
	}
}

func convertBackupStateSchemaToProto(state schema.BackupState) globalngv1.Backup_State {
	switch state {
	case schema.BackupStatePending, schema.BackupStateSubmitting:
		return globalngv1.Backup_STATE_PENDING
	case schema.BackupStateRunning:
		return globalngv1.Backup_STATE_RUNNING
	case schema.BackupStateSuccess:
		return globalngv1.Backup_STATE_SUCCESS
	case schema.BackupStateFailed:
		return globalngv1.Backup_STATE_FAILED
	case schema.BackupStateCancelled:
		return globalngv1.Backup_STATE_CANCELLED
	default:
		return globalngv1.Backup_STATE_UNKNOWN
	}
}

func convertBackupTypeSchemaToProto(backupType schema.BackupType) globalngv1.Backup_Type {
	switch backupType {
	case schema.BackupTypeSnapshot:
		return globalngv1.Backup_TYPE_SNAPSHOT
	default:
		return globalngv1.Backup_TYPE_UNSPECIFIED
	}
}

func convertBackupTriggerTypeSchemaToProto(triggerType schema.BackupTriggerType) globalngv1.Backup_TriggerType {
	switch triggerType {
	case schema.BackupTriggerTypeManual:
		return globalngv1.Backup_TRIGGER_TYPE_MANUAL
	case schema.BackupTriggerTypeAuto:
		return globalngv1.Backup_TRIGGER_TYPE_AUTO
	default:
		return globalngv1.Backup_TRIGGER_TYPE_UNSPECIFIED
	}
}
