package server

import (
	"context"
	"fmt"

	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow"
	braction "github.com/tidbcloud/cluster-service-ng/domain/workflow/action/br"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow/dag"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/mysql/schema"
	"github.com/tidbcloud/cluster-service-ng/pkg/utils"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

type CreateManualBackupInput struct {
	*apiv1.CreateManualBackupRequest
}

func (s *serverImpl) CreateManualBackup(ctx context.Context, input *CreateManualBackupInput) error {
	backup, err := s.manualBackupRepo.Load(ctx, input.ClusterId, input.BackupId, true)
	if err != nil && status.Code(err) != codes.NotFound {
		return fmt.Errorf("failed to load manual backup: %w", err)
	}
	if backup != nil {
		if backup.DeletedAt.Valid {
			return fmt.Errorf("backup id conflict with a deleted manual backup, please use a new backup id")
		}
		return nil
	}

	tidbCluster, err := s.tidbClusterRepo.Load(ctx, input.ClusterId, false)
	if err != nil {
		return fmt.Errorf("failed to load tidb cluster %s: %w", input.ClusterId, err)
	}

	sharedPool, err := s.sharedPoolRepo.Load(ctx, tidbCluster.SharedPoolId, false)
	if err != nil {
		return fmt.Errorf("failed to load sharedPool %s: %w", tidbCluster.SharedPoolId, err)
	}
	if err := guardSharedPoolMutation(sharedPool, sharedPoolMutationBlockedWhilePaused); err != nil {
		return err
	}
	cpu, memory, err := braction.GetTiKVCpuMemorySpec(ctx, sharedPool, s.dataplaneManager)
	if err != nil {
		return fmt.Errorf("failed to get tikv cpu memory spec: %w", err)
	}
	storage := braction.DefaultTiBRStorageSize
	if s.bizCfg != nil && s.bizCfg.DefaultTiBRConfig.StorageSize != "" {
		storage = s.bizCfg.DefaultTiBRConfig.StorageSize
	}

	backupRecord := schema.ManualBackup{
		BackupID:     input.BackupId,
		ClusterID:    input.ClusterId,
		Status:       schema.ManualBackupStatusRunning,
		SharedPoolID: tidbCluster.SharedPoolId,
	}

	workflowArgs := actions.ActionArgs{
		utils.TraceIdKey:           utils.GetTraceId(ctx),
		workflow.ManualBackupIdKey: actions.ActionArgString(input.BackupId),
		workflow.TiDBClusterIdKey:  actions.ActionArgString(input.ClusterId),
		workflow.SharedPoolIdKey:   actions.ActionArgString(tidbCluster.SharedPoolId),
		workflow.TiBRCPUKey:        actions.ActionArgString(cpu),
		workflow.TiBRMemoryKey:     actions.ActionArgString(memory),
		workflow.TiBRStorageKey:    actions.ActionArgString(storage),
	}

	var dagExecId string
	var dagSubmitted bool
	err = s.transactioner.Transaction(ctx, func(ctx context.Context) error {
		if ierr := s.manualBackupRepo.Create(ctx, &entity.ManualBackup{ManualBackup: backupRecord}); ierr != nil {
			return fmt.Errorf("failed to save manual backup record: %w", ierr)
		}

		gcSetting, ierr := s.backupGcSettingRepo.Load(ctx, backupRecord.SharedPoolID)
		if ierr == nil {
			if gcSetting.HasManualBackup == nil || !*gcSetting.HasManualBackup {
				gcSetting.HasManualBackup = new(bool)
				*gcSetting.HasManualBackup = true
				if ierr = s.backupGcSettingRepo.Save(ctx, gcSetting); ierr != nil {
					return fmt.Errorf("failed to update has_manual_backup=true for shared pool %s: %w", backupRecord.SharedPoolID, ierr)
				}
			}
		} else if status.Code(ierr) != codes.NotFound {
			return fmt.Errorf("failed to load backup gc setting for shared pool %s: %w", backupRecord.SharedPoolID, ierr)
		}

		dagExecId = manualBackupCreationTaskId(&backupRecord)
		if ierr := s.dagExecutor.Submit(ctx, dag.PlanCreateManualBackup, workflowArgs, dagExecId); ierr != nil {
			tilog.Info(ctx, "failed to submit dag instance for manual backup", zap.Any("input", input))
			return fmt.Errorf("failed to submit dag, dagName: %s, dagInstanceName: %s, err: %w", dag.PlanCreateManualBackup, dagExecId, ierr)
		}
		dagSubmitted = true
		return nil
	})
	if err != nil {
		if dagSubmitted {
			if abortErr := s.dagExecutor.Abort(ctx, dagExecId); abortErr != nil {
				tilog.Error(ctx, "failed to abort dag after transaction failure",
					zap.String("dagExecId", dagExecId),
					zap.Error(abortErr))
			}
		}
		return fmt.Errorf("failed to create manual backup: %w", err)
	}

	return nil
}

func manualBackupCreationTaskId(backupRecord *schema.ManualBackup) string {
	return fmt.Sprintf("mb-%s-%s", backupRecord.ClusterID, backupRecord.BackupID)
}
