package cloud

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/pingcap/log"
	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	"github.com/tidbcloud/serverless-service/idl/pbgen/proto/rpc"
	dedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	nextgenv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"go.buf.build/protocolbuffers/go/linuxgit/tidbcloud/base"
	"go.uber.org/zap"
	"k8s.io/utils/ptr"

	"github.com/pingcap-inc/clinic/pkg/auth/session"
	"github.com/pingcap-inc/clinic/pkg/datasource/internal/cloud/internal"
	"github.com/pingcap-inc/clinic/pkg/datasource/internal/cloud/internal/dbaas_core"
	model2 "github.com/pingcap-inc/clinic/pkg/datasource/model"
	"github.com/pingcap-inc/clinic/pkg/datasource/utils"
	"github.com/pingcap-inc/clinic/pkg/datasource/view"
	"github.com/pingcap-inc/clinic/pkg/db"
	"github.com/pingcap-inc/clinic/pkg/db/model"
	"github.com/pingcap-inc/clinic/pkg/nextgen_client"
	"github.com/pingcap-inc/clinic/pkg/o11y"
)

func ListClusters(ctx *session.CtxUser, filter view.QueryFilter) ([]db.Cluster, int64, error) {
	// if !isInternal(ctx) {
	// 	return nil, 0, errors.New("permission deny")
	// }

	cond, ok := filter.ExtraFilter.(*view.ListClusterFilter)
	if !ok {
		return nil, 0, errors.New("unsupport extra filter type")
	}

	org, orgErr := internal.GetOrganization(cond.OrgID)
	if orgErr != nil {
		return nil, 0, orgErr
	}

	if org.OrgType != string(view.CloudDataSource) {
		return nil, 0, errors.New("wrong data source")
	}

	clusters, total, err := model2.ListClusters(cond.Query, cond.OrgID, cond.ProjectID,
		filter.Page, filter.Limit, cond.HideServerlessTier, cond.HideDeleted)
	if err != nil {
		return nil, 0, err
	}
	return clusters, total, nil
}

func CountCloudClusters(ctx *session.CtxUser) (int64, error) {
	if !isInternal(ctx) {
		return 0, errors.New("permission deny")
	}
	return model2.CountCloudClusters()
}

func ListCloudClusters(ctx *session.CtxUser, page, perPage int) ([]db.Cluster, error) {
	if !isInternal(ctx) {
		return nil, errors.New("permission deny")
	}
	return model2.ListCloudClusters(page, perPage)
}

// func RealtimeListDedicatedClusters(ctxu *session.CtxUser, pager *base.Pager) (*core.ListClustersResp, error) {
// 	if !isInternal(ctxu) {
// 		return nil, errors.New("permission deny")
// 	}

// 	ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
// 	defer cancel()

// 	return internal.RealtimeListDedicatedClusters(ctx, "", nil, pager)
// }

func RealtimeListDedicatedClustersByOrg(ctxu *session.CtxUser, tenantID uint64) ([]*core.Cluster, []string, error) {
	if !isInternal(ctxu) {
		return nil, nil, errors.New("permission deny")
	}

	ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
	defer cancel()

	count, err := internal.CountDedicatedClustersByOrg(tenantID)
	if err != nil {
		return nil, nil, err
	}

	clusters := make([]*core.Cluster, 0)
	deployTypeV2s := make([]string, 0)
	for i := int64(1); i <= (count-1)/50+1; i++ {
		resp, err := internal.RealtimeListDedicatedClusters(ctx, "",
			&db.Organization{DbaasOrgID: uint(tenantID)},
			&base.Pager{Page: i, PageSize: 50})
		if err != nil {
			return nil, nil, err
		}
		clusterIds := make([]uint64, 0, len(resp.GetClusters()))
		for _, c := range resp.GetClusters() {
			clusters = append(clusters, c)
			clusterIds = append(clusterIds, c.Id)
		}

		mgmtClusters, err := internal.RealtimeListMgmtClusters(ctx, &mgmtv1.ClusterFilter{
			TenantIds:  []uint64{tenantID},
			ClusterIds: clusterIds,
		}, 50, 1)
		if err != nil {
			return nil, nil, err
		}

		for _, id := range clusterIds {
			deployTypeV2 := ""
			for _, c := range mgmtClusters {
				if c.ClusterId == id {
					deployTypeV2 = utils.GetMgmtDeployTypeV2(c)
					break
				}
			}
			deployTypeV2s = append(deployTypeV2s, deployTypeV2)
		}
	}

	return clusters, deployTypeV2s, nil
}

func RealtimeListServerlessClusters(ctxu *session.CtxUser, tenantID uint64, includeDeleted bool) ([]*mgmtv1.Cluster, error) {
	if !isInternal(ctxu) {
		return nil, errors.New("permission deny")
	}

	ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
	defer cancel()

	allClustesr := make([]*mgmtv1.Cluster, 0)
	pageSzie := uint32(50)
	for page := uint32(1); ; page++ {
		clusters, err := internal.RealtimeListMgmtClusters(ctx, &mgmtv1.ClusterFilter{
			TenantIds:   []uint64{tenantID},
			ClusterType: mgmtv1.DeployType_DEPLOY_TYPE_SERVERLESS.Enum(),
			Deleted:     &includeDeleted,
		}, pageSzie, page)
		if err != nil {
			return nil, err
		}
		allClustesr = append(allClustesr, clusters...)
		if len(clusters) < int(pageSzie) {
			break
		}
	}
	return allClustesr, nil
}

func RealtimeListServerlessBranches(ctxu *session.CtxUser, clusterID uint64) ([]*rpc.BasicBranch, error) {
	if !isInternal(ctxu) {
		return nil, errors.New("permission deny")
	}

	ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
	defer cancel()

	resp, err := serverlessC.ListBranches(ctx, clusterID)
	return resp.GetBranches(), err
}

// only support nextgen clusters currently
func RealtimeListMgmtClustersByOrg(ctxu *session.CtxUser, tenantID uint64) ([]*mgmtv1.Cluster, error) {
	if !isInternal(ctxu) {
		return nil, errors.New("permission deny")
	}

	count, err := internal.CountMgmtClustersByOrg(tenantID)
	if err != nil {
		return nil, err
	}
	if count == 0 {
		return []*mgmtv1.Cluster{}, nil
	}
	log.Info("count mgmt clusters by org",
		zap.Uint64("tenantID", tenantID),
		zap.Uint32("count", count),
	)

	clusters := make([]*mgmtv1.Cluster, 0)
	for i := uint32(1); i <= (count-uint32(1))/uint32(50)+uint32(1); i++ {
		ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
		defer cancel()
		resp, err := internal.RealtimeListMgmtClusters(ctx,
			&mgmtv1.ClusterFilter{
				TenantIds: []uint64{tenantID},
				// ClusterType: mgmtv1.DeployType_DEPLOY_TYPE_NEXTGEN.Enum(),
				Deleted: ptr.To(true),
			},
			50,
			i,
		)
		if err != nil {
			return nil, err
		}
		clusters = append(clusters, resp...)
	}

	return clusters, nil
}

func RealtimeGetMgmtCluster(ctxu *session.CtxUser, clusterID uint64) (*mgmtv1.Cluster, error) {
	if !isInternal(ctxu) {
		return nil, errors.New("permission deny")
	}
	ctx, f := context.WithTimeout(context.Background(), 15*time.Second)
	defer f()
	return internal.RealtimeGetMgmtCluster(ctx, clusterID)
}

func RealtimeGetMgmtClusters(ctxu *session.CtxUser, clusterIDs []uint64) ([]*mgmtv1.Cluster, error) {
	if !isInternal(ctxu) {
		return nil, errors.New("permission deny")
	}
	ctx, f := context.WithTimeout(context.Background(), 15*time.Second)
	defer f()
	return internal.RealtimeGetMgmtClusters(ctx, clusterIDs)
}

func RealtimeGetCluster(ctx *session.CtxUser, clusterID uint64) (*core.Cluster, string, error) {
	if !isInternal(ctx) {
		return nil, "", errors.New("permission deny")
	}

	cluster, err := internal.RealtimeGetCluster(clusterID)
	if err != nil {
		return nil, "", err
	}

	newCtx, f := context.WithTimeout(context.Background(), 15*time.Second)
	defer f()
	mgmtCluster, err := internal.RealtimeGetMgmtCluster(newCtx, clusterID)
	if err != nil {
		return cluster, "", nil
	}
	deployTypeV2 := utils.GetMgmtDeployTypeV2(mgmtCluster)
	return cluster, deployTypeV2, nil
}

func GetCluster(uctx *session.CtxUser, idStr string, orgID uint) (*db.Cluster, error) {
	cluster, err := model.GetClusterWithOrg(idStr, orgID)
	if err != nil {
		log.Error("get cluster from db failed, try to read from dbaas", zap.String("cluster id", idStr), zap.Uint("org id", orgID), zap.Error(err))
		cluster, err = internal.GetCloudCluster(idStr, orgID)
		if err != nil {
			return nil, err
		}
	}
	return cluster, nil
}

func ListRegions(uctx *session.CtxUser, provider []string) ([]string, error) {
	return model.ListRegions(provider)
}

func RealtimeGetFullCluster(uctx *session.CtxUser, idStr string, org *db.Organization) (*db.Cluster, error) {
	cluster, err := model.GetClusterWithOrg(idStr, org.ID)
	if err != nil {
		return nil, err
	}
	switch cluster.DeployType {
	case o11y.NgHostType, o11y.ByocHostType:
		return cluster, nil
	default:
		clusterInCloud, err := internal.GetCloudCluster(idStr, org.ID)
		if err != nil {
			log.Error("get cluster from dbaas failed, use cluster from db", zap.String("cluster id", idStr), zap.Error(err))
		} else {
			cluster = clusterInCloud
		}
		return cluster, nil
	}
}

func GetClusterPublicAddress(ctx context.Context, dbaasOrgID, dbaasClusterID uint64) (host string, port int32, err error) {
	return internal.GetClusterPublicAddress(ctx, dbaasOrgID, dbaasClusterID)
}

func isInternal(ctx *session.CtxUser) bool {
	if !ctx.Internal {
		return false
	}

	return strings.HasSuffix(ctx.Email, "@pingcap.com") || strings.HasSuffix(ctx.Email, "@pingcap.cn")
}

func IsUserDebugAuthEnable(ctx *session.CtxUser, orgID string) (bool, error) {
	if !isInternal(ctx) {
		return false, errors.New("permission deny")
	}

	// Returns false by default if the InternalPortal is not accessible
	if orgID == "" {
		return false, nil
	}

	featureList, code, err := dbaas_core.GetUserDebugFeatures(orgID)
	if err != nil || code >= 500 {
		log.Error("get userbug feature list failed", zap.Error(err))
		return false, nil
	}

	return featureList.IsUserDebugAuthEnable(), nil
}

func IsAccessDashboardAuthorized(ctx *session.CtxUser, orgID, clusterID, user string) (bool, error) {
	if !isInternal(ctx) {
		return false, errors.New("permission deny")
	}

	authCode, code, err := dbaas_core.GetUserDebugAuthCode(orgID, clusterID, user)
	if err != nil || code >= 500 {
		log.Error("get userbug auth code failed", zap.Error(err))
		return false, err
	}

	if authCode.Valid {
		return true, nil
	}

	return false, fmt.Errorf("userdebug auth code not found for cluster %s", clusterID)
}

func CountDedicatedClusters(ctx *session.CtxUser) (int64, error) {
	if !isInternal(ctx) {
		return 0, errors.New("permission deny")
	}
	c, err := internal.CountDedicatedClusters()
	if err != nil {
		return c, err
	}
	return c, nil
}

func CountServerlessClusters(ctx *session.CtxUser) (int64, error) {
	if !isInternal(ctx) {
		return 0, errors.New("permission deny")
	}
	c, err := internal.CountServerlessClusters()
	if err != nil {
		return c, err
	}
	return c, nil
}

func GetCloudClusterComponents(uctx *session.CtxUser, clusterID uint64) ([]*dedicatedv1.Cluster_ClusterComponent, string, string, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
	defer cancel()

	return internal.GetCloudClusterComponents(ctx, clusterID)
}

func GetResourcePoolComponents(uctx *session.CtxUser, clusterID uint64) ([]*view.ResourcePoolComponent, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
	defer cancel()

	return internal.GetResourcePoolComponents(ctx, clusterID)
}

func GetDedicatedClusterComponents(uctx *session.CtxUser, clusterID uint64) ([]*view.ResourcePoolComponent, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
	defer cancel()

	return internal.GetDedicatedClusterComponents(ctx, clusterID)
}

func GetPremiumClusterComponents(uctx *session.CtxUser, clusterID, sharedPoolID uint64) ([]*view.ResourcePoolComponent, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
	defer cancel()

	return internal.GetPremiumClusterComponents(ctx, clusterID, sharedPoolID)
}

func GetResourcePoolWithProfile(uctx *session.CtxUser, clusterID uint64) (*nextgenv1.ResourcePoolProfile, *nextgen_client.SharedPool, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
	defer cancel()

	return internal.GetResourcePoolWithProfile(ctx, clusterID)
}

func ListPlanReplayerFiles(ctx context.Context, clusterID uint64, pageSize int, pageToken string) (*nextgen_client.ListPlanReplayerFilesResponse, error) {
	return dbaas_core.ListPlanReplayerFiles(ctx, clusterID, pageSize, pageToken)
}

func GeneratePlanReplayerFileDownloadURL(
	ctx context.Context,
	clusterID uint64,
	req *nextgen_client.GeneratePlanReplayerFileDownloadURLRequest,
) (*nextgen_client.GeneratePlanReplayerFileDownloadURLResponse, error) {
	return dbaas_core.GeneratePlanReplayerFileDownloadURL(ctx, clusterID, req)
}

func CountResourcePoolClusters(ctx *session.CtxUser) (int64, error) {
	if !isInternal(ctx) {
		return 0, errors.New("permission deny")
	}
	return model2.CountResourcePoolClusters()
}

func ListResourcePoolClusters(ctx *session.CtxUser, page, perPage int) ([]db.Cluster, error) {
	if !isInternal(ctx) {
		return nil, errors.New("permission deny")
	}
	return model2.ListResourcePoolClusters(page, perPage)
}

func ListResourcePoolProfile(uctx *session.CtxUser, resourcePoolIds []uint64) ([]*nextgenv1.ResourcePoolProfile, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
	defer cancel()

	return internal.ListResourcePoolProfile(ctx, resourcePoolIds)
}
