package action

import (
	"context"
	"errors"
	"fmt"
	"strconv"
	"time"

	"github.com/tidbcloud/fastflow/pkg/entity/run"
	"github.com/tidbcloud/pkgv2/db"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/adapter/regionalserver"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	brrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/br"
	clusterrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/cluster"
	regionrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/region"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/resourcepool"
	brsvc "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/br"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/workflow/common"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/workflow/metric"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/pkg/config"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

const (
	CreateBackupSetting = "create_backup_setting"
)

type CreateBackupSettingAction struct {
	backupSettingRepo brrepo.BackupSettingRepo
	backupPolicyRepo  brrepo.BackupPolicyRepo
	clusterRepo       clusterrepo.Repo
	resourcePoolRepo  resourcepool.Repo
	regionRepo        regionrepo.Repo
	regionalCli       regionalserver.Client
	transaction       db.Transactioner
	cfg               *config.Config
}

func NewCreateBackupSettingAction(
	backupSettingRepo brrepo.BackupSettingRepo,
	backupPolicyRepo brrepo.BackupPolicyRepo,
	clusterRepo clusterrepo.Repo,
	resourcePoolRepo resourcepool.Repo,
	regionRepo regionrepo.Repo,
	regionalCli regionalserver.Client,
	transaction db.Transactioner,
	cfg *config.Config,
) *CreateBackupSettingAction {
	return &CreateBackupSettingAction{
		backupSettingRepo: backupSettingRepo,
		backupPolicyRepo:  backupPolicyRepo,
		clusterRepo:       clusterRepo,
		resourcePoolRepo:  resourcePoolRepo,
		regionRepo:        regionRepo,
		regionalCli:       regionalCli,
		transaction:       transaction,
		cfg:               cfg,
	}
}

func (a *CreateBackupSettingAction) Name() string {
	return CreateBackupSetting
}

func (a *CreateBackupSettingAction) ParameterNew() any {
	return &common.ClusterIdParameter{}
}

func (a *CreateBackupSettingAction) Run(ctx run.ExecuteContext, params any) error {
	cctx, span := workflow.GenerateContext(ctx)

	clusterIdParam, ok := params.(*common.ClusterIdParameter)
	if !ok || clusterIdParam == nil {
		tilog.Error(cctx, "failed to get cluster id param", zap.Any("params", params))
		return errors.New("failed to get cluster id param")
	}
	clusterDisplayName, _ := ctx.GetVar(common.KeyClusterDisplayName)

	tilog.Info(
		cctx,
		"start to create backup setting",
		zap.Uint64("cluster_id", clusterIdParam.ClusterId),
		zap.String("cluster_display_name", clusterDisplayName),
	)
	defer func() {
		tilog.Info(
			cctx,
			"end to create backup setting",
			zap.Uint64("cluster_id", clusterIdParam.ClusterId),
			zap.String("cluster_display_name", clusterDisplayName),
		)
		span.End()
	}()

	err := a.transaction.Transaction(cctx, func(txCtx context.Context) error {
		cluster, berr := a.clusterRepo.Get(txCtx, clusterIdParam.ClusterId, false)
		if berr != nil {
			tilog.Error(cctx, "failed to get cluster", zap.Error(berr))
			return berr
		}

		sharedPoolId, err := strconv.ParseUint(cluster.SharedPoolId, 10, 64)
		if err != nil {
			return err
		}

		backupSetting := &schema.BackupSetting{
			State:                 schema.BackupSettingStateCreated,
			LastScheduleUpdatedAt: time.Now(),
			ClusterId:             clusterIdParam.ClusterId,
			SharedPoolId:          sharedPoolId,
			TenantId:              cluster.OrganizationId,
			Name:                  clusterDisplayName,
			RegionId:              globalcommonv1.Region_Name_name[int32(cluster.Cluster.RegionId)],
			CloudProvider:         globalcommonv1.CloudProvider_Name_name[int32(cluster.Cluster.CloudProviderId)],
			ServicePlan:           globalcommonv1.ServicePlan_name[int32(cluster.ServicePlan)],
			ClusterVersion:        cluster.Version,
		}
		backupSetting.BackupPolicy = schema.BackupPolicyNameTIER1
		backupPolicy, berr := a.backupPolicyRepo.Get(txCtx, schema.BackupPolicyNameTIER1)
		if berr != nil {
			return berr.Chain("failed to get backup policy")
		}
		setDefaultBackupSetting(backupSetting, cluster.ServicePlan, backupPolicy.BackupPolicy)

		berr = a.backupSettingRepo.Create(txCtx, &entity.BackupSetting{
			BackupSetting: backupSetting,
		})
		if berr != nil {
			return berr.Chain("CreateBackupSetting")
		}

		// update regional backup gc setting
		berr = brsvc.UpdateSharedPoolBackupGCSetting(
			cctx,
			sharedPoolId,
			a.backupSettingRepo,
			a.backupPolicyRepo,
			a.resourcePoolRepo,
			a.regionRepo,
			a.regionalCli,
		)
		if berr != nil {
			return berr.Chain("failed to update regional backup gc setting")
		}

		berr = brsvc.UpdateAutoBackupMetering(
			cctx,
			&entity.BackupSetting{
				BackupSetting: backupSetting,
			},
			a.clusterRepo,
			a.resourcePoolRepo,
			a.regionRepo,
			a.regionalCli,
		)
		if berr != nil {
			return berr.Chain("failed to update auto backup metetering")
		}
		return nil
	})
	if err != nil {
		tilog.Error(
			cctx,
			"failed to create backup setting",
			zap.Uint64("cluster_id", clusterIdParam.ClusterId),
			zap.Error(err),
		)
		metric.CreateBackupSettingActionFailuresTotal.WithLabelValues(fmt.Sprintf("%d", clusterIdParam.ClusterId)).Inc()
		return err
	}

	return nil
}

func setDefaultBackupSetting(
	backupSetting *schema.BackupSetting,
	servicePlan globalcommonv1.ServicePlan,
	backupPolicy *schema.BackupPolicy,
) {
	backupSetting.RetentionDays = brsvc.StandardRetentionDays
	backupSetting.DailyBackupTimeUTC = brsvc.DefaultDailyBackupTimeUTC
	backupSetting.Schedule = schema.AutoSchedule{
		At:                     0,
		DailyBackupMinuteOfDay: 0,
	}

	if servicePlan == globalcommonv1.ServicePlan_Essential_V2 {
		backupSetting.Mode = schema.BackupModeFlexibleRetention
		backupSetting.Schedule.Type = "per-day"
		backupSetting.Schedule.NonZeroClockRetentionDays = brsvc.StandardRetentionDays
		backupSetting.Schedule.ZeroClockRetentionDays = brsvc.StandardRetentionDays
		return
	}

	backupSetting.Mode = schema.BackupModeStandardBundle
	backupSetting.Schedule.Type = "per-hour"
	backupSetting.Schedule.NonZeroClockRetentionDays = backupPolicy.Pitr.RetentionDays
	backupSetting.Schedule.ZeroClockRetentionDays = backupPolicy.DailyBackup.RetentionDays
}
