package cluster

import (
	"context"
	"errors"
	"fmt"

	workflowentity "github.com/tidbcloud/fastflow/pkg/entity"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"gorm.io/gorm"

	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/tidb-mgmt-service/domain/constant"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/service/workflow"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func (svc *service) GetCluster(
	ctx context.Context,
	clusterId uint64,
	view entity.ClusterView,
	includeDeleted bool,
) (*entity.Cluster, error) {
	ctx, span := tracer.Start(ctx, "ClusterService.GetCluster")
	defer span.End()

	cluster := &entity.Cluster{}

	if clusterId == 0 {
		return cluster, fmt.Errorf("cluster id is invalid")
	}

	if err := svc.transaction.Transaction(ctx, func(ctx context.Context) error {
		var err error
		cluster, err = svc.clusterRepo.GetCluster(ctx, clusterId, includeDeleted)
		if err != nil {
			return fmt.Errorf("failed to get cluster record: %w", err)
		}

		if cluster.DeployType != entity.DeployTypeNextGen {
			tilog.Warn(ctx, "cluster deploy type is not nextgen", zap.Uint64("cluster_id", clusterId))
			return gorm.ErrRecordNotFound
		}

		if err = svc.hydrateClusterTags(ctx, []*entity.Cluster{cluster}); err != nil {
			return fmt.Errorf("failed to get cluster tags: %w", err)
		}

		if view == entity.ClusterViewClusterViewLocal {
			return nil
		}
		runningDagInstances, err := svc.workflow.ListRunningDagInstances(ctx, []string{
			constant.DagNextGenCreateCluster,
		}, fmt.Sprintf("%d", clusterId))
		if err != nil {
			tilog.Error(ctx, "list running dag instances failed", zap.Error(err))
		}

		nextgenCluster, err := svc.nextGenCli.GetCluster(ctx, &globalngv1.GetClusterRequest{
			ClusterId:   clusterId,
			ShowDeleted: includeDeleted,
		})
		if err != nil {
			var berr bizerr.BizError
			if ok := errors.As(err, &berr); ok && berr != nil {
				if berr.ErrCode() == bizerr.ErrCodeRecordNotFound {
					// If nextgen cluster not found in global service
					// Mabye the cluster is creating in mgmt workflow
					// Return cluster with mgmt workflow cluster info
					tilog.Warn(ctx, "nextgen cluster not found", zap.Uint64("cluster_id", clusterId), zap.Error(berr))
					if len(runningDagInstances) != 0 {
						tilog.Warn(ctx, "nextgen cluster not found, but running dag instance found", zap.Uint64("cluster_id", clusterId))
						cluster = svc.mergeInWorkflowState(ctx, cvtNextGenCluster(cluster, defaultNextGenCluster(cluster)), runningDagInstances)
						return nil
					}
					tilog.Warn(ctx, "nextgen cluster not found and no running dag instance", zap.Uint64("cluster_id", clusterId))
				}
			}
			return err
		}
		cluster = cvtNextGenCluster(cluster, nextgenCluster)
		return nil
	}); err != nil {
		return nil, fmt.Errorf("failed to get cluster: %w", err)
	}

	return cluster, nil
}

func (svc *service) mergeInWorkflowState(
	ctx context.Context,
	cluster *entity.Cluster,
	runningDagInstances []*workflowentity.DagInstance,
) *entity.Cluster {
	if len(runningDagInstances) == 0 {
		return cluster
	}

	ins := runningDagInstances[0]

	if ins.DagID == constant.DagNextGenCreateCluster {
		out := workflow.Unmarshal(ctx, cluster.ClusterId, ins, &globalngv1.CreateClusterRequest{})
		if out != nil {
			globalCluster := out.(*globalngv1.CreateClusterRequest).Cluster
			globalCluster.State = globalngv1.Cluster_CREATING
			globalCluster.RegionDisplayName = globalCluster.GetRegionId().String()
			cluster.GlobalCluster = globalCluster

			regions, err := svc.nextGenCli.ListRegions(ctx, &globalngv1.ListRegionsRequest{
				CloudProviderId: globalCluster.GetCloudProviderId(),
				ServicePlan:     globalCluster.GetServicePlan(),
				OrganizationId:  cluster.OrganizationId,
				State:           globalcommonv1.Region_ACTIVE,
			})

			if err != nil {
				tilog.Warn(ctx, "get global region list failed", zap.Error(err))
			} else {
				for _, region := range regions.GetRegions() {
					if region.GetId() == globalCluster.GetRegionId() {
						globalCluster.RegionDisplayName = region.GetDisplayName()
					}
				}
			}
		}
	}
	return cluster
}

func defaultNextGenCluster(c *entity.Cluster) *globalngv1.Cluster {
	return &globalngv1.Cluster{
		ClusterId:         c.ClusterId,
		OrganizationId:    c.OrganizationId,
		State:             globalngv1.Cluster_CREATING,
		HealthState:       globalngv1.Cluster_UNHEALTHY,
		RegionDisplayName: c.Region,
	}
}
