package server

import (
	"context"

	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/dag"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func (s *serverImpl) GetTiDBCluster(ctx context.Context, tidbClusterId string) (*entity.TiDBCluster, error) {

	if tidbClusterId == "" {
		return nil, status.Errorf(codes.InvalidArgument, "tidbClusterId is empty")
	}

	tidbCluster, err := s.tidbClusterRepo.Load(ctx, tidbClusterId, false)
	if err != nil {
		if status.Code(err) != codes.NotFound {
			return nil, status.Errorf(codes.Internal, "failed to load tidbCluster: %s", err)
		}

		// not found, check if it's in creating
		return nil, status.Errorf(codes.NotFound, "tidbCluster %s not found", tidbClusterId)
	}
	err = s.setTiDBClusterStatus(ctx, tidbCluster)
	if err != nil {
		if status.Code(err) == codes.NotFound {
			return nil, status.Errorf(codes.NotFound, "tidbCluster %s not found", tidbClusterId)
		}
		return nil, status.Errorf(codes.Internal, "failed to get tidbCluster status: %s", err)
	}
	return tidbCluster, nil

}

func (s *serverImpl) setTiDBClusterStatus(ctx context.Context, tidbCluster *entity.TiDBCluster) error {
	if tidbCluster.GetPhase() == apiv1.Cluster_CREATING {
		// if it's in creating, return directly
		return nil
	}

	relaxTiProxyHealthGate, err := s.shouldRelaxTiProxyHealthGate(ctx, tidbCluster)
	if err != nil {
		return err
	}

	// If Phase is UNAVAILABLE (set by repo layer), return directly
	if tidbCluster.GetPhase() == apiv1.Cluster_UNAVAILABLE && !relaxTiProxyHealthGate {
		return nil
	}

	// If Phase is MODIFYING (set by repo layer based on TiProxy status), preserve it
	if tidbCluster.GetPhase() == apiv1.Cluster_MODIFYING && !relaxTiProxyHealthGate {
		return nil
	}

	// check tidb group status
	var hasAvailable bool
	tidbGroups, err := s.ListTiDBGroups(ctx, tidbCluster.GetId())
	if err != nil {
		tidbCluster.Phase = apiv1.Cluster_UNAVAILABLE
		return status.Errorf(codes.Internal, "failed to list tidb groups: %s", err)
	}

	if len(tidbGroups) == 0 {
		tidbCluster.Phase = apiv1.Cluster_AVAILABLE
		hasAvailable = true
	}

	for _, tg := range tidbGroups {
		if tg.GetPhase() == apiv1.TiDBGroup_AVAILABLE {
			hasAvailable = true
			break
		}
	}

	if !hasAvailable {
		tilog.Warn(ctx, "no available tidb group found for tidb cluster",
			zap.String("tidbClusterId", tidbCluster.GetId()),
			zap.Int("tidbGroupCount", len(tidbGroups)),
		)
		tidbCluster.Phase = apiv1.Cluster_MODIFYING
		return nil
	}

	dagIns, err := s.getRunningDagIns(ctx, []string{dag.PlanUpdateTiDBCluster, dag.PlanCreateTiDBCluster, dag.PlanUpdateTiDBGroup, dag.PlanDeleteTiDBGroup}, nil, nil, []string{tidbCluster.GetId()}, nil)
	if err != nil {
		return status.Errorf(codes.Internal, "failed to get running update tidb cluster dag instance: %s", err)
	}

	for _, ins := range dagIns {
		switch ins.ExecTask.RootName {
		case dag.PlanCreateTiDBCluster:
			tidbCluster.Phase = apiv1.Cluster_CREATING
		case dag.PlanUpdateTiDBCluster, dag.PlanUpdateTiDBGroup, dag.PlanDeleteTiDBGroup:
			tilog.Info(ctx, "found running dag instance for tidb cluster, set phase to modifying", zap.String("dagInsId", ins.ExecTask.ExecId), zap.String("plan", ins.ExecTask.RootName))
			// if there is a running update dag instance, set phase to modifying
			tidbCluster.Phase = apiv1.Cluster_MODIFYING
		}
	}
	return nil
}

func (s *serverImpl) shouldRelaxTiProxyHealthGate(ctx context.Context, tidbCluster *entity.TiDBCluster) (bool, error) {
	if tidbCluster.GetCapacityMode() != apiv1.CapacityMode_TOPOLOGY_DRIVEN {
		return false, nil
	}

	sharedPool, err := s.sharedPoolRepo.Load(ctx, tidbCluster.GetSharedPoolId(), false)
	if err != nil {
		if status.Code(err) == codes.NotFound {
			return false, status.Errorf(codes.NotFound, "sharedPool %s not found", tidbCluster.GetSharedPoolId())
		}
		return false, status.Errorf(codes.Internal, "failed to load sharedPool: %s", err)
	}

	for _, comp := range sharedPool.GetComponentGroups() {
		if comp.GetComponentType() == apiv1.ComponentType_TIDB_WORKER {
			if comp.GetNodeQuota().GetNodeCount() > 0 {
				return false, nil
			}
		}
	}

	return true, nil
}

func (s *serverImpl) GetTiDBClusterTiProxyNodeQuota(ctx context.Context, tidbClusterId string) (*entity.NodeQuota, error) {
	if tidbClusterId == "" {
		return nil, status.Errorf(codes.InvalidArgument, "tidbClusterId is empty")
	}

	tidbCluster, err := s.tidbClusterRepo.Load(ctx, tidbClusterId, false)
	if err != nil {
		if status.Code(err) != codes.NotFound {
			return nil, status.Errorf(codes.Internal, "failed to load tidbCluster: %s", err)
		}
		// not found, check if it's in creating
		return nil, status.Errorf(codes.NotFound, "tidbCluster %s not found", tidbClusterId)
	}

	if tidbCluster.GetPhase() == apiv1.Cluster_UNAVAILABLE {
		return nil, status.Errorf(codes.FailedPrecondition, "tidb cluster is not available")
	}

	// check shared pool status
	sharedPool, err := s.sharedPoolRepo.Load(ctx, tidbCluster.GetSharedPoolId(), false)
	if err != nil {
		tidbCluster.Phase = apiv1.Cluster_UNAVAILABLE
		if status.Code(err) != codes.NotFound {
			return nil, status.Errorf(codes.Internal, "failed to load shared pool: %s", err)
		}
		return nil, status.Errorf(codes.Internal, "failed to load shared pool: %s", err)
	}

	// merge sharedPool status from dag task
	err = s.mergeSharedPoolStatusFromDagTask(ctx, sharedPool)
	if err != nil {
		tilog.Error(ctx, "failed to merge sharedPool status from dag task", zap.Error(err))
		return nil, status.Errorf(codes.Internal, "failed to merge sharedPool status from dag task: %s", err)
	}

	if sharedPool.GetPhase() == apiv1.SharedPool_UNAVAILABLE {
		tidbCluster.Phase = apiv1.Cluster_UNAVAILABLE
		return nil, status.Errorf(codes.FailedPrecondition, "shared pool is not available")
	}

	if component := entity.FindTiProxyComponent(sharedPool, tidbCluster.GetId()); component != nil {
		return component.GetNodeQuota(), nil
	}

	return nil, status.Errorf(codes.NotFound, "tidb cluster tiProxy node quota not found")
}
