package api

import (
	"context"
	"net/http"

	"github.com/tidbcloud/account/idl/pbgen/proto/account"

	"github.com/gin-gonic/gin"
	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	entity "github.com/tidbcloud/core-svc/model"
	"github.com/tidbcloud/internal-portal/core-portal/admin/forms"
	"github.com/tidbcloud/internal-portal/core-portal/central/grpc/grpcli"
	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/log"
	"go.buf.build/protocolbuffers/go/linuxgit/tidbcloud/base"
	"go.uber.org/zap"
)

var (
	deletedClusterStatuses = []core.ClusterStatus{
		core.ClusterStatus_CLUSTER_STATUS_DELETED, core.ClusterStatus_CLUSTER_STATUS_DELETECOMPLETE,
		core.ClusterStatus_CLUSTER_STATUS_CLEARED,
	}
)

// @Summary List Tenant Cluster
// @tags TenantCuster
// @Accept  json
// @Produce json
// @Param org_id path string true "org_id"
// @Param page query forms.PaginationReq  true "pagination"
// @Success 200 {object} forms.ListTenantClustersResp
// @Router /orgs/{org_id}/clusters [get]
func ListTenantClusters(c *gin.Context) {
	ctx := c.Request.Context()
	req := forms.GetOrgIDReq{}
	if err := c.ShouldBindUri(&req); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}
	logger := log.WithContextS(ctx).With(zap.Uint64("tenantID", req.OrgID))
	logger.Info("get clusters of tenant, begin")

	total, clusters, err := listTenantClusters(ctx, req.OrgID, GetPager(c))
	resp := forms.ListTenantClustersResp{}
	statusCode := http.StatusOK
	// add a middleware
	if err != nil {
		if err.IsException() {
			logger.Errorf("failed: %s", err)
		} else {
			logger.Infof("failed: %s", err)
		}
		err = err.Redact()
		resp.Code = int64(err.ErrCode())
		resp.Msg = err.ErrMsg()
		statusCode = err.HttpStatus()
	} else {
		resp.Total = total
		resp.Items = clusters
	}
	c.JSON(statusCode, resp)
}

func listTenantClusters(ctx context.Context, tenantID uint64, pager *base.Pager) (int64, []forms.TenantCluster, bizerr.BizError) {
	fitler := &core.ClusterFilter{
		TenantIds:       []uint64{tenantID},
		ExcludeStatuses: deletedClusterStatuses,
	}
	sorter := &base.Sorter{
		OrderBy: "created_at",
		Sort:    base.Sort_SORT_DESC,
	}
	listReq := &core.ListClustersReq{
		Filter: fitler,
		Pager:  pager,
		Sorter: sorter,
	}
	listResp, err := grpcli.ListClusters(ctx, listReq)
	if err != nil {
		return 0, nil, err
	}
	countReq := &core.CountClustersReq{
		Filter: fitler,
	}
	countResp, err := grpcli.CountClusters(ctx, countReq)
	if err != nil {
		return 0, nil, err
	}

	id2project := map[uint64]*account.Project{}
	projectFilter := &account.ProjectFilter{
		OrgIds: []uint64{tenantID},
		Status: []string{string(entity.ProjectStatusActive)},
	}
	projectsRes, err := grpcli.ListProjects(ctx, projectFilter)
	if err != nil {
		return 0, nil, err
	}
	for _, each := range projectsRes.Projects {
		project := each
		id2project[each.Id] = project
	}

	out := make([]forms.TenantCluster, 0, len(listResp.Clusters))
	for _, id := range listResp.OrderedClusterIds {
		item := listResp.Clusters[id]
		out = append(out, buildAPITenantClusterItem(item, id2project[item.ProjectId]))
	}
	return countResp.Count, out, nil
}

// @Summary Delete Tenant Cluster
// @tags TenantCuster
// @Accept  json
// @Produce json
// @Param org_id path string true "org_id"
// @Param cluster_id path string true "cluster_id"
// @Success 200 {object} forms.EmptyResponse
// @Router /orgs/{org_id}/clusters/{cluster_id} [delete]
func DeleteTenantCluster(c *gin.Context) {
	req := forms.GetOrgIDAndClusterIDReq{}
	if err := c.ShouldBindUri(&req); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	userContext := GetUserContext(c)
	if err := userContext.DeleteTenantCluster(req.OrgID, req.ClusterID); err != nil {
		Fail(c, err, http.StatusInternalServerError)
		return
	}

	Success(c, nil, http.StatusOK)
}

// @Summary Pause Tenant Cluster
// @tags TenantCuster
// @Accept  json
// @Produce json
// @Param org_id path string true "org_id"
// @Param cluster_id path string true "cluster_id"
// @Success 200 {object} forms.CodeMsgResponse
// @Router /orgs/{org_id}/clusters/{cluster_id}/pause [post]
func PauseTenantCluster(c *gin.Context) {
	req := forms.GetOrgIDAndClusterIDReq{}
	if err := c.ShouldBindUri(&req); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	userContext := GetUserContext(c)
	if err := userContext.PauseTenantCluster(c.Request.Context(), req.ClusterID); err != nil {
		FailFromBizErr(c, err)
		return
	}

	Success(c, &forms.CodeMsgResponse{}, http.StatusOK)
}

// @Summary Resume Tenant Cluster
// @tags TenantCuster
// @Accept  json
// @Produce json
// @Param org_id path string true "org_id"
// @Param cluster_id path string true "cluster_id"
// @Success 200 {object} forms.CodeMsgResponse
// @Router /orgs/{org_id}/clusters/{cluster_id}/resume [post]
func ResumeTenantCluster(c *gin.Context) {
	req := forms.GetOrgIDAndClusterIDReq{}
	if err := c.ShouldBindUri(&req); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	userContext := GetUserContext(c)
	if err := userContext.ResumeTenantCluster(c.Request.Context(), req.ClusterID); err != nil {
		FailFromBizErr(c, err)
		return
	}

	Success(c, &forms.CodeMsgResponse{}, http.StatusOK)
}

// @Summary Scale Tenant Cluster
// @tags TenantCuster
// @Accept  json
// @Produce json
// @Param org_id path string true "org_id"
// @Param cluster_id path string true "cluster_id"
// @Param scale body forms.ScaleComponentReq true "scale cluster component"
// @Success 200 {object} forms.EmptyResponse
// @Router /orgs/{org_id}/clusters/{cluster_id}/components [put]
func ScaleTenantCluster(c *gin.Context) {
	ctx := c.Request.Context()
	orgIDAndClusterID := forms.GetOrgIDAndClusterIDReq{}
	if err := c.ShouldBindUri(&orgIDAndClusterID); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	scaleReq := forms.ScaleComponentReq{}
	if err := c.ShouldBindJSON(&scaleReq); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	err := scaleTenantCluster(ctx, orgIDAndClusterID.ClusterID, &scaleReq)
	resp := forms.CodeMsgResponse{}
	statusCode := http.StatusOK
	if err != nil {
		err = err.Redact()
		resp.Code = int64(err.ErrCode())
		resp.Msg = err.ErrMsg()
		statusCode = err.HttpStatus()
	}
	c.JSON(statusCode, resp)
}

func scaleTenantCluster(ctx context.Context, clusterID uint64, scaleReq *forms.ScaleComponentReq) bizerr.BizError {
	components := make([]*core.ClusterComponent, 0, len(scaleReq.Components))
	for _, c := range scaleReq.Components {
		components = append(components, &core.ClusterComponent{
			ComponentType: core.ComponentType(core.ComponentType_value[c.ComponentType]),
			Replicas:      c.Replicas,
			StorageSizeGi: c.StorageSizeGi,
		})
	}
	coreReq := &core.ScaleClusterReq{
		ClusterId:           clusterID,
		NewComponents:       components,
		AllowScaleIn:        true,
		AllowScaleSecondary: false,
		AllowScaleDown:      false,
		BaseReq:             nil,
	}
	_, err := grpcli.ScaleCluster(ctx, coreReq)
	return err
}

// @Summary ListTenantClusterLifeCycle
// @tags Tenant
// @Accept  json
// @Produce json
// @Param org_id path string  true "org_id"
// @Param search query forms.ListTenantClusterLifeCycleReq  true "search"
// @Success 200 {object} forms.ListTenantClusterLifeCycleResp
// @Router /orgs/{org_id}/cluster-lifecycles [get]
func ListTenantClusterLifeCycle(c *gin.Context) {
	reqUri := forms.GetOrgIDReq{}
	if err := c.ShouldBindUri(&reqUri); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}
	req := forms.ListTenantClusterLifeCycleReq{}
	if err := c.ShouldBindQuery(&req); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	userContext := GetUserContext(c)

	cf, err := userContext.ListTenantClusterLifeCycle(reqUri.OrgID, req.Start, req.End)
	if err != nil {
		Fail(c, err, http.StatusInternalServerError)
		return
	}

	resp := forms.ListTenantClusterLifeCycleResp{Total: int64(len(cf))}
	for _, item := range cf {
		resp.Items = append(resp.Items, forms.TenantClusterLifecycle{
			Id:        item.ID,
			ProjectID: item.ProjectID,
			CreatedAt: item.CreatedAt,
			DeletedAt: item.DeletedAt,
		})
	}
	Success(c, resp, http.StatusOK)
}
