package bizutils

import (
	"context"

	"github.com/tidbcloud/pkgv2/bizerr"

	"github.com/pingcap/errcode"
	"github.com/pingcap/errors"
	"github.com/tidbcloud/metrics-integration/global/internal/infrastructure/grpc/grpcli"

	dedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	mgmt "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
)

const internalErr = "Internal error, please retry later or contact support."

func getDedicatedClusterState(cluster *mgmt.Cluster) (dedicatedv1.Cluster_State, bool) {
	if cluster == nil {
		return dedicatedv1.Cluster_STATE_UNSPECIFIED, false
	}

	dedicatedCluster := cluster.GetDedicatedCluster()
	if dedicatedCluster == nil {
		return dedicatedv1.Cluster_STATE_UNSPECIFIED, false
	}

	return dedicatedCluster.State, true
}

// getClusterComponents extracts components from a cluster, handling different cluster types
func getClusterComponents(cluster *mgmt.Cluster) []*dedicatedv1.Cluster_ClusterComponent {
	if cluster == nil {
		return nil
	}

	// Handle dedicated clusters
	if dedicatedCluster := cluster.GetDedicatedCluster(); dedicatedCluster != nil {
		return dedicatedCluster.GetClusterComponents()
	}

	// For serverless and next-gen clusters, components might not be available
	// in the same format, so return nil
	return nil
}

// GetCluster gets the cluster information if the requested user has the right permission.
func GetCluster(ctx context.Context, orgId, projectId, clusterID uint64) (*mgmt.Cluster, error) {
	cluster, err := grpcli.GetClusterByID(ctx, clusterID)
	if err != nil {
		if bizerr.ErrCodeRecordNotFound == err.ErrCode() {
			return nil, errcode.NewNotFoundErr(errors.Errorf("Cluster id=%d not found", clusterID))
		}
		return nil, errcode.NewInternalErr(errors.New(internalErr))
	}

	if cluster.OrganizationId != orgId || cluster.ProjectId != projectId {
		return nil, errcode.NewForbiddenErr(errors.New("you do not have permission to access this resource"))
	}
	return cluster, nil
}

// CheckCluster checks ClusterId and ClusterStatus.
func CheckCluster(ctx context.Context, orgID, projID, clusterID uint64) error {
	cluster, err := GetCluster(ctx, orgID, projID, clusterID)
	if err != nil {
		return err
	}

	state, ok := getDedicatedClusterState(cluster)
	if !ok {
		return errcode.NewInvalidInputErr(errors.Errorf("Cluster id=%d is not a dedicated cluster", clusterID))
	}

	if !ClusterAvailable(cluster) {
		return errcode.NewInvalidInputErr(errors.Errorf("Cluster id=%d not in status normal/scaling, it is in %v", clusterID, state))
	}
	return nil
}

// CheckClusterOwner checks whether the cluster's tenantId and projectId are same as input.
func CheckClusterOwner(ctx context.Context, orgId, projectId, clusterID uint64) error {
	_, err := GetCluster(ctx, orgId, projectId, clusterID)
	return err
}

// CheckClusterId checks whether the cluster's status is NORMAL/SCALING.
func CheckClusterStatus(ctx context.Context, clusterID uint64) error {
	cluster, err := grpcli.GetClusterByID(ctx, clusterID)
	if err != nil {
		if bizerr.ErrCodeRecordNotFound == err.ErrCode() {
			return errcode.NewNotFoundErr(errors.Errorf("Cluster id=%d not found", clusterID))
		}
		return err
	}
	state, ok := getDedicatedClusterState(cluster)
	if !ok {
		return errcode.NewInvalidInputErr(errors.Errorf("Cluster ID %d is not a dedicated cluster", clusterID))
	}

	if state != dedicatedv1.Cluster_AVAILABLE {
		return errcode.NewInvalidInputErr(errors.Errorf("Cluster ID %d is not in status normal, please try again later.", clusterID))
	}
	return nil
}

func ClusterDeletedOrCleared(c *mgmt.Cluster) bool {
	state, ok := getDedicatedClusterState(c)
	return ok && state == dedicatedv1.Cluster_DELETED
}

// ClusterAvailable checks whether the cluster is available to connect to, i.e. it's in NORMAL/SCALING status
func ClusterAvailable(c *mgmt.Cluster) bool {
	state, ok := getDedicatedClusterState(c)
	return ok && (state == dedicatedv1.Cluster_AVAILABLE ||
		state == dedicatedv1.Cluster_SCALING)
}
