package base

import (
	"encoding/json"
	"errors"
	"fmt"
	"strconv"

	"github.com/shiningrush/fastflow/pkg/entity/run"
	"github.com/tidbcloud/dataflow-service/app/internal/cluster_job"
	"github.com/tidbcloud/dataflow-service/app/internal/feature_gate"
	"github.com/tidbcloud/dataflow-service/app/models/domain"
	backupRepo "github.com/tidbcloud/dataflow-service/app/repo/backup"
	"github.com/tidbcloud/dataflow-service/app/repo/entity"
	"github.com/tidbcloud/dataflow-service/app/workflow"
	"github.com/tidbcloud/dataflow-service/app/workflow/action"
	"github.com/tidbcloud/dataflow-service/pkg/grpc/grpcli"
	infraapi "github.com/tidbcloud/dataflow-service/pkg/infra-api"
	"github.com/tidbcloud/pkgv2/db"
	mgmt "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"go.uber.org/zap"
)

type BaseAction struct {
	TransProvider         db.TransProvider
	BackupRepo            backupRepo.BackupRepo
	BackupScheduleRepo    backupRepo.BackupScheduleRepo
	LogBackupRepo         backupRepo.LogBackupRepo
	BackupRegionalFacade  infraapi.BackupFacade
	ClusterJob            cluster_job.Model
	MgmtCli               grpcli.MgmtSvcGrpcClient
	PitBackupSwitcherRepo backupRepo.PitBackupSwitchRepo
	FeatureGate           feature_gate.FeatureGate
}

func NewBaseAction(
	transProvider db.TransProvider,
	backupRepo backupRepo.BackupRepo,
	backupScheduleRepo backupRepo.BackupScheduleRepo,
	logBackupRepo backupRepo.LogBackupRepo,
	backupRegionalFacade infraapi.BackupFacade,
	clusterJob cluster_job.Model,
	mgmtCli grpcli.MgmtSvcGrpcClient,
	pitBackupSwitcherRepo backupRepo.PitBackupSwitchRepo,
	featureGate feature_gate.FeatureGate,
) BaseAction {
	return BaseAction{
		TransProvider:         transProvider,
		BackupRepo:            backupRepo,
		BackupScheduleRepo:    backupScheduleRepo,
		LogBackupRepo:         logBackupRepo,
		BackupRegionalFacade:  backupRegionalFacade,
		ClusterJob:            clusterJob,
		MgmtCli:               mgmtCli,
		PitBackupSwitcherRepo: pitBackupSwitcherRepo,
		FeatureGate:           featureGate,
	}
}

func (b *BaseAction) QuerySnapshotBackup(ctx run.ExecuteContext, logger *zap.Logger) (*entity.Backup, error) {
	backupID, err := action.GetUint64FromRunVar(ctx, workflow.KeyBackupID)
	if err != nil {
		logger.Error("get backup id from context error", zap.Error(err))
		return nil, err
	}
	backup, err := b.BackupRepo.Load(ctx.Context(), backupID)
	if err != nil {
		logger.Error("load backup error", zap.Any("backup id", backupID), zap.Error(err))
		return nil, err
	}
	return backup, err
}

func (b *BaseAction) GetBackupFromShareData(ctx run.ExecuteContext, logger *zap.Logger) (*entity.Backup, error) {
	backupID, err := b.GetBackupIDFromShareData(ctx, logger)
	if err != nil {
		return nil, err
	}
	backup, err := b.BackupRepo.Load(ctx.Context(), backupID)
	if err != nil {
		logger.Error("get backup error", zap.Error(err))
		return nil, err
	}
	return backup, nil
}

func (b *BaseAction) GetBackupIDFromShareData(ctx run.ExecuteContext, logger *zap.Logger) (uint64, error) {
	backupIDStr, ok := ctx.ShareData().Get(workflow.KeyBackupID)
	if !ok {
		return 0, errors.New("get backup id from shared data error maybe shared data not set")
	}
	logger.Info("get backup id from shared data", zap.String("backup_id", backupIDStr))
	backupID, err := strconv.ParseUint(backupIDStr, 10, 64)
	if err != nil {
		logger.Error("parse backup id error", zap.Error(err))
		return 0, err
	}
	return backupID, nil
}

func (b *BaseAction) QueryLogBackup(
	ctx run.ExecuteContext, logger *zap.Logger, keyLogBackupID string,
) (*entity.LogBackup, error) {
	backupID, err := action.GetUint64FromRunVar(ctx, keyLogBackupID)
	if err != nil {
		logger.Error("get log backup id from context error", zap.Error(err))
		return nil, err
	}
	logBackup, err := b.LogBackupRepo.Load(ctx.Context(), backupID)
	if err != nil {
		logger.Error("load log backup error", zap.Uint64("log backup id", backupID), zap.Error(err))
		return nil, err
	}
	if logBackup.BackupSchedule == nil && logBackup.BackupScheduleID != 0 {
		schedule, err := b.BackupScheduleRepo.Load(ctx.Context(), logBackup.BackupScheduleID.Uint64())
		if err != nil {
			logger.Error(
				"load schedule error", zap.Uint64("schedule id", logBackup.BackupScheduleID.Uint64()), zap.Error(err))
			return nil, err
		}
		logBackup.BackupSchedule = schedule
	}
	return logBackup, nil
}

func (b *BaseAction) QueryPitBackupSwitch(
	ctx run.ExecuteContext, logger *zap.Logger,
) (*entity.PitBackupSwitch, error) {
	pitBackupSwitchID, err := action.GetUint64FromRunVar(ctx, workflow.KeyPitBackupSwitchID)
	if err != nil {
		logger.Error("get pit backup switch id from context error", zap.Error(err))
		return nil, err
	}
	pitBackupSwitch, err := b.PitBackupSwitcherRepo.Load(ctx.Context(), pitBackupSwitchID)
	if err != nil {
		logger.Error("get PitBackupSwitch error", zap.Error(err))
		return nil, err
	}
	return pitBackupSwitch, nil
}

func (b *BaseAction) GetAsyncCreateReqFromRunVar(
	ctx run.ExecuteContext, logger *zap.Logger,
) (*domain.AsyncCreateBackupReq, error) {
	key := workflow.KeyAsyncCreateBackupReq
	jsonStr, ok := ctx.GetVar(key)
	if !ok {
		msg := fmt.Sprintf("can not find %s from run var", key)
		logger.Error(msg)
		return nil, fmt.Errorf("%s", msg)
	}

	req := &domain.AsyncCreateBackupReq{}
	if err := json.Unmarshal([]byte(jsonStr), req); err != nil {
		msg := fmt.Sprintf("unmarshal %s error", key)
		logger.Error(msg, zap.Error(err))
		return nil, err
	}
	return req, nil
}

func (b *BaseAction) GetCluster(ctx run.ExecuteContext, logger *zap.Logger, clusterID uint64) (*mgmt.Cluster, error) {
	cluster, err := b.MgmtCli.GetAnyDedicatedClusterBasicByID(ctx.Context(), clusterID)
	if err != nil {
		logger.Error("get cluster error", zap.Uint64("cluster id", clusterID), zap.Error(err))
		return nil, err
	}
	return cluster, nil
}

// EnhanceLoggerWithBackupContext adds backup-related context information to the logger
// This helps with troubleshooting by including cluster_id, project_id, tenant_id in all log entries
func (b *BaseAction) EnhanceLoggerWithBackupContext(logger *zap.Logger, backup *entity.Backup) *zap.Logger {
	return logger.With(
		zap.Uint64("backup_id", backup.ID.Uint64()),
		zap.Uint64("cluster_id", backup.ClusterID.Uint64()),
		zap.Uint64("project_id", backup.ProjectID.Uint64()),
		zap.Uint64("tenant_id", backup.TenantID.Uint64()),
	)
}

// EnhanceLoggerWithLogBackupContext adds log backup-related context information to the logger
func (b *BaseAction) EnhanceLoggerWithLogBackupContext(logger *zap.Logger, logBackup *entity.LogBackup) *zap.Logger {
	return logger.With(
		zap.Uint64("log_backup_id", logBackup.ID.Uint64()),
		zap.Uint64("cluster_id", logBackup.ClusterID.Uint64()),
		zap.Uint64("project_id", logBackup.ProjectID.Uint64()),
		zap.Uint64("tenant_id", logBackup.TenantID.Uint64()),
	)
}

// EnhanceLoggerWithContextIDs adds basic context IDs when only IDs are available
func (b *BaseAction) EnhanceLoggerWithContextIDs(
	logger *zap.Logger, backupID, clusterID, projectID, tenantID uint64,
) *zap.Logger {
	return logger.With(
		zap.Uint64("backup_id", backupID),
		zap.Uint64("cluster_id", clusterID),
		zap.Uint64("project_id", projectID),
		zap.Uint64("tenant_id", tenantID),
	)
}
