package br

import (
	"context"
	"fmt"
	"path"
	"time"

	v1alpha1br "github.com/pingcap/tidb-operator/api/v2/br/v1alpha1"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	dplanemgrv1 "github.com/tidbcloud/cluster-service-ng/api/dataplane-manager/v1"
	"github.com/tidbcloud/cluster-service-ng/config"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow"
	cert2 "github.com/tidbcloud/cluster-service-ng/pkg/cert"
	"github.com/tidbcloud/cluster-service-ng/pkg/clusterconfig"
	"github.com/tidbcloud/cluster-service-ng/pkg/dataplane"
	"github.com/tidbcloud/cluster-service-ng/pkg/k8s"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	schemav1 "github.com/tidbcloud/cluster-service-ng/pkg/storage/etcd/schema/v1"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo"
	templateversion "github.com/tidbcloud/cluster-service-ng/pkg/template/version"
	"github.com/tidbcloud/cluster-service-ng/pkg/tioperator"
	"github.com/tidbcloud/cluster-service-ng/pkg/utils"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

type createTiBRAutoBackupAction struct {
	dataplaneManager    dataplane.Manager
	sharedPoolRepo      repo.SharedPoolRepo
	lwRestoreRepo       repo.LightweightRestoreRepo
	templateVersionSvc  *templateversion.Service
	imageConfig         *config.ImageConfig
	bizConfig           *config.BizConfig
	dpServiceCli        dplanemgrv1.DataPlaneServiceClient
	clusterConfigClient clusterconfig.Client
}

const CreateTiBRAutoBackupActionName = "create-tibr-autobackup"

func NewCreateTiBRAutoBackupAction(
	sharedPoolRepo repo.SharedPoolRepo,
	lwRestoreRepo repo.LightweightRestoreRepo,
	dataplaneManager dataplane.Manager,
	templateVersionSvc *templateversion.Service,
	imageConfig *config.ImageConfig,
	bizConfig *config.BizConfig,
	dpServiceCli dplanemgrv1.DataPlaneServiceClient,
	clusterConfigClient clusterconfig.Client,
) actions.Action {
	return actions.CreateActionFromAsync(&createTiBRAutoBackupAction{
		sharedPoolRepo:      sharedPoolRepo,
		lwRestoreRepo:       lwRestoreRepo,
		dataplaneManager:    dataplaneManager,
		templateVersionSvc:  templateVersionSvc,
		imageConfig:         imageConfig,
		bizConfig:           bizConfig,
		dpServiceCli:        dpServiceCli,
		clusterConfigClient: clusterConfigClient,
	}, 1*time.Minute, 30*time.Minute)
}

func (a *createTiBRAutoBackupAction) Name() string {
	return CreateTiBRAutoBackupActionName
}

func (a *createTiBRAutoBackupAction) Start(args actions.ActionArgs) error {
	ctx := utils.GenerateContext(args)
	tilog.Info(ctx, "create tibr...")

	targetSharedPool, remoteCli, err := a.getActionContextInfos(args)
	if err != nil {
		return fmt.Errorf("failed to get create tibr context infos: %w", err)
	}

	cert := cert2.TiBRCert(tioperator.DefaultTiBRName, targetSharedPool.GetDataPlaneNamespace())
	if err := k8s.CreateIfNotExist(ctx, remoteCli, cert); err != nil {
		tilog.Error(ctx, "failed to create tibr cert", zap.Any("cert", cert), zap.Error(err))
		return err
	}

	err = a.createTiBRCRIfNotExist(ctx, targetSharedPool)
	if err != nil {
		return fmt.Errorf("create tibr CR failed: %s", err.Error())
	}

	return nil
}

func (a *createTiBRAutoBackupAction) CheckFinish(args actions.ActionArgs) (bool, error) {
	ctx := utils.GenerateContext(args)
	tilog.Info(ctx, "check tibr creation finished...")

	targetSharedPool, remoteCli, err := a.getActionContextInfos(args)
	if err != nil {
		return false, fmt.Errorf("failed to get create tibr context infos: %w", err)
	}

	tibrCR := &v1alpha1br.TiBR{}
	tibrName := tioperator.DefaultTiBRName
	namespace := targetSharedPool.GetDataPlaneNamespace()
	err = remoteCli.Get(ctx, types.NamespacedName{
		Name:      tibrName,
		Namespace: namespace,
	}, tibrCR)
	if err != nil {
		tilog.Error(ctx, "failed to get tibr", zap.String("name", tibrName), zap.String("namespace", namespace), zap.Error(err))
		return false, fmt.Errorf("failed to get tibr: %w", err)
	}

	if !tioperator.IsTiBRSyncedReady(tibrCR) {
		tilog.Error(ctx, "tibr is not ready and synced", zap.String("name", tibrName), zap.String("namespace", namespace))
		return false, nil
	}

	tilog.Info(ctx, "tibr is ready and synced", zap.String("name", tibrName), zap.String("namespace", namespace))

	return true, nil
}

func (a *createTiBRAutoBackupAction) getActionContextInfos(args actions.ActionArgs) (targetSharedPool *entity.SharedPool, remoteCli client.Client, err error) {
	ctx := utils.GenerateContext(args)
	tilog.Info(ctx, "get create tibr context infos...")

	targetSharedPoolId, err := args.GetString(workflow.SharedPoolIdKey)
	if err != nil {
		err = fmt.Errorf("failed to get target sharedPool id: %w", err)
		return
	}

	targetSharedPool, err = a.sharedPoolRepo.Load(ctx, targetSharedPoolId, false)
	if err != nil {
		err = fmt.Errorf("failed to load target sharedPool: %w", err)
		return
	}

	remoteCli, err = a.dataplaneManager.GetClient(targetSharedPool.GetDataplaneName())
	if err != nil {
		err = fmt.Errorf("failed to get remote client for target shared pool: %w", err)
		return
	}
	tilog.Info(ctx, "get create tibr context infos success", zap.Any("args", args))

	return
}

func (a *createTiBRAutoBackupAction) createTiBRCRIfNotExist(ctx context.Context, targetSharedPool *entity.SharedPool) error {
	cpu, memory, err := getResourcesForTiBRAutoBackup(a.bizConfig)
	if err != nil {
		return fmt.Errorf("failed to get resources for tibr: %w", err)
	}

	tenant, placement, err := getTiBRNodePlacement(ctx, a.dpServiceCli, targetSharedPool.GetDataplaneName(), cpu, memory)
	if err != nil {
		return fmt.Errorf("failed to resolve node placement for tibr auto backup: %w", err)
	}

	image, cfg, err := a.getImageAndConfig(ctx, targetSharedPool, tenant)
	if err != nil {
		return fmt.Errorf("failed to get image and config for tibr auto backup: %w", err)
	}

	yamlStr, err := a.renderTiBRCR(ctx, targetSharedPool, tenant, image, cfg, cpu, memory, placement)
	if err != nil {
		tilog.Error(ctx, "failed to render tibr CR yaml", zap.Error(err))
		return fmt.Errorf("failed to render tibr CR yaml: %s", err.Error())
	}

	resourceCr, _, err := k8s.GenerateUnstructuredFromYaml(yamlStr)
	if err != nil {
		tilog.Error(ctx, "failed to generate unstructured from yaml", zap.String("yaml", yamlStr), zap.Error(err))
		return fmt.Errorf("failed to generate unstructured from yaml: %s", err.Error())
	}

	remoteCli, err := a.dataplaneManager.GetClient(targetSharedPool.GetDataplaneName())
	if err != nil {
		tilog.Error(ctx, "failed to get remote client for target shared pool", zap.String("dataplaneName", targetSharedPool.GetDataplaneName()), zap.Error(err))
		return fmt.Errorf("failed to get remote client for target shared pool: %w", err)
	}

	err = k8s.CreateIfNotExist(ctx, remoteCli, resourceCr)
	if err != nil {
		tilog.Error(ctx, "failed to create tibr", zap.String("yaml", yamlStr), zap.Error(err))
		return fmt.Errorf("failed to create tibr: %s", err.Error())
	}

	tilog.Info(ctx, " create tibr success", zap.String("yaml", yamlStr))

	return nil
}

func (a *createTiBRAutoBackupAction) renderTiBRCR(
	ctx context.Context,
	targetSharedPool *entity.SharedPool,
	tenant, image, cfg, cpu, memory string,
	placement TiBRNodePlacement,
) (string, error) {
	// common args
	tplArgs := map[string]string{
		"Name":           tioperator.DefaultTiBRName,
		"Namespace":      targetSharedPool.GetDataPlaneNamespace(),
		"ResourceCPU":    cpu,
		"ResourceMemory": memory,
		"Image":          image,
		"DfsPrefix":      targetSharedPool.GetDfsPrefix(),
		"DfsBucket":      targetSharedPool.GetObjectStorage().GetBucket(),
		"DfsRegion":      targetSharedPool.GetObjectStorage().GetRegion(),
		"ComponentGroup": getComponentGroupName4TiBR(targetSharedPool),
		"Tenant":         tenant,
		"ResourcePool":   targetSharedPool.GetId(),
		"Config":         cfg,
		"NodeSpec":       placement.NodeSpec,
		"NodeVersion":    placement.NodeVersion,
	}

	if targetSharedPool.GetDfxEndpoint() != "" {
		tplArgs["DfsEndpoint"] = targetSharedPool.GetDfxEndpoint()
	}

	if err := workflow.AddAzureDfsArgs(targetSharedPool, tplArgs); err != nil {
		return "", err
	}

	if targetSharedPool.GetProvider().GetType() == apiv1.ProviderType_ALICLOUD {
		tplArgs["AliCloudRegion"] = targetSharedPool.GetProvider().GetRegion()
	}

	yamlStr, _, _, err := templateversion.RenderWithVersion(ctx, a.templateVersionSvc, "tibr_autobackup", "", tplArgs)
	if err != nil {
		return "", fmt.Errorf("failed to render template: %w", err)
	}
	return yamlStr, nil
}

func getResourcesForTiBRAutoBackup(bizConfig *config.BizConfig) (cpu, memory string, err error) {
	cpuCore := DefaultTiBRCPU
	if bizConfig != nil && bizConfig.DefaultTiBRConfig.CPU != "" {
		cpuCore = bizConfig.DefaultTiBRConfig.CPU
	}
	milliCPU, err := ParseCPU(cpuCore)
	if err != nil {
		return "", "", fmt.Errorf("failed to parse cpu %s: %w", cpuCore, err)
	}
	cpu = fmt.Sprintf("%d", ComputeAvailableCPU(uint64(milliCPU)/1000))

	memValue := DefaultTiBRMemory
	if bizConfig != nil && bizConfig.DefaultTiBRConfig.Memory != "" {
		memValue = bizConfig.DefaultTiBRConfig.Memory
	}
	memoryInMiB, err := ParseToMiB(memValue)
	if err != nil {
		return "", "", fmt.Errorf("failed to parse memory %s: %w", memValue, err)
	}
	availableMemory := tioperator.ComputeAvailableMemory(memoryInMiB)
	memory = fmt.Sprintf("%dMi", availableMemory)

	return cpu, memory, nil
}

func (a *createTiBRAutoBackupAction) getImageAndConfig(ctx context.Context, targetSharedPool *entity.SharedPool, tenantId string) (string, string, error) {
	if a.bizConfig.DefaultTiBRConfig.IntegrateConfigCenter {
		return a.getImageAndConfigFromConfigCenter(ctx, targetSharedPool, tenantId)
	}

	image, err := GetImage(targetSharedPool, a.imageConfig)
	if err != nil {
		return "", "", fmt.Errorf("failed to get image for tibr: %w", err)
	}

	defaultConfig := schemav1.NewTomlConfig(map[string]any{})
	defaultConfig.Set("native-br.backup-interval", "60s")
	defaultConfig.Set("native-br.backup-tolerate-err", true)

	configFile, err := defaultConfig.MarshalTOML()
	if err != nil {
		return "", "", fmt.Errorf("failed to marshal default config: %w", err)
	}

	return image, string(configFile), nil
}

func (a *createTiBRAutoBackupAction) getImageAndConfigFromConfigCenter(ctx context.Context, targetSharedPool *entity.SharedPool, tenantId string) (string, string, error) {
	_, cfg, err := a.clusterConfigClient.GetBRComponentVersionAndConfig(ctx, targetSharedPool, tenantId, apiv1.ComponentType_TIBR_AUTOBACKUP)
	if err != nil {
		return "", "", fmt.Errorf("failed to get %s version and config: %w", apiv1.ComponentType_TIBR_AUTOBACKUP.String(), err)
	}

	tomlConfig := schemav1.NewTomlConfig(map[string]any{})
	if err := tomlConfig.UnmarshalTOML([]byte(cfg.GetConfigurationFileContent())); err != nil {
		return "", "", fmt.Errorf("failed to unmarshal tibr auto backup config: %w", err)
	}

	tomlConfig.Set("native-br.backup-interval", "60s")
	cfgFile, err := tomlConfig.MarshalTOML()
	if err != nil {
		return "", "", fmt.Errorf("failed to marshal tibr auto backup config: %w", err)
	}

	image := fmt.Sprintf("%s/%s/%s:%s",
		a.imageConfig.Registry,
		a.imageConfig.RepoPrefix,
		path.Base(cfg.GetImageRepo()),
		cfg.GetImageTag(),
	)
	return image, string(cfgFile), nil
}
