package cluster

import (
	"context"
	"fmt"
	"io"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"
	regionalv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"
	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"google.golang.org/protobuf/encoding/protojson"

	"github.com/tidbcloud/devops/api"
	k8sresourceapi "github.com/tidbcloud/devops/api/k8sresource"
	k8sresourceent "github.com/tidbcloud/devops/entity/k8sresource"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
	contutil "github.com/tidbcloud/devops/pkg/infra/utils/container"
	k8sresource "github.com/tidbcloud/devops/pkg/service/k8sresource"
)

// GetControlplaneK8SResource
//
//	@Id				GetControlplaneK8SResource
//	@Description	get controlplane k8s resource
//	@Tags			ClusterService
//	@Accept			json
//	@Produce		json
//	@Param			cluster_id	path		string								true	"cluster id"
//	@Param			req			query		k8sresourceapi.GetK8SResourceReq	true	"k8s resource request"
//	@Success		200			{object}	k8sresourceapi.GetK8SResourceResp
//	@Failure		500			{object}	api.BaseResp
//	@Router			/api/v1/devops/clusters/{cluster_id}/controlplane [get]
func (c *ClusterController) GetControlplaneK8SResource(ctx *gin.Context) {
	clusterId := ctx.Param("cluster_id")
	req := &k8sresourceapi.GetK8SResourceReq{}
	err := ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
	}

	data, err := c.k8sResourceSvc.GetControlplaneK8SResource(ctx, &k8sresource.GetK8SResourceReq{
		ClusterId:    clusterId,
		Namespace:    req.Namespace,
		ResourceKind: req.ResourceKind,
		ResourceName: req.ResourceName,
	})
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInternal)
		return
	}
	ctrl.Success(ctx, &k8sresourceapi.GetK8SResourceResp{Metadata: data})
}

// ListK8SResources
//
//	@Id				ListK8SResources
//	@Description	list k8s resources
//	@Tags			ClusterService
//	@Accept			json
//	@Produce		json
//	@Param			req	query		k8sresourceapi.ListK8SResourcesReq	true	"k8s resources request"
//	@Success		200	{object}	k8sresourceapi.ListK8SResourcesResp
//	@Failure		500	{object}	api.BaseResp
//	@Router			/api/v1/devops/clusters/k8s-resources [get]
func (c *ClusterController) ListK8SResources(ctx *gin.Context) {
	req := k8sresourceapi.ListK8SResourcesReq{}
	err := ctx.ShouldBindQuery(&req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	resp, err := c.listK8SResources(ctx, req)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	ctrl.Success(ctx, resp)
}

func (c *ClusterController) listK8SResources(ctx context.Context, req k8sresourceapi.ListK8SResourcesReq) (*k8sresourceapi.ListK8SResourcesResp, error) {
	params := &k8sresource.ListK8SResourcesParams{
		ClusterId: req.ClusterId,
		Namespace: req.Namespace,
		Region:    req.Region,
		BeginTime: req.BeginTime,
		EndTime:   req.EndTime,
		Page:      int(req.Page),
		PerPage:   int(req.PerPage),
	}

	resources, total, err := c.k8sResourceSvc.ListK8SResources(ctx, params)
	if err != nil {
		return &k8sresourceapi.ListK8SResourcesResp{}, err
	}

	respResources := make([]k8sresourceent.K8SResource, 0, len(resources))
	contutil.ForEach(resources, func(idx int, item *k8sresourceent.K8SResource) {
		respResources = append(respResources, *item)
	})

	return &k8sresourceapi.ListK8SResourcesResp{
		Total:     total,
		Resources: respResources,
	}, nil
}

// ListDedicatedK8sResources
//
//	@Id				ListDedicatedK8sResources
//	@Description	list dedicated k8s resources via regional-server
//	@Tags			ClusterService
//	@Accept			json
//	@Produce		json
//	@Param			region			query		string											false	"Dedicated logical region name (e.g. us-west-2); required with cloud_provider"
//	@Param			cloud_provider	query		string											false	"cloud provider (e.g. aws); required with region"
//	@Param			cluster_id		query		string											false	"Dedicated cluster id; alternative to region/cloud_provider"
//	@Param			body			body		k8sresourceapi.ListDedicatedK8sResourcesBody	true	"same as regional-server ListK8sResourcesRequest"
//	@Success		200				{object}	k8sresourceapi.ListDedicatedK8sResourcesResp
//	@Failure		400				{object}	api.BaseResp
//	@Failure		500				{object}	api.BaseResp
//	@Router			/api/v1/devops/cluster/dedicatedK8sResources [post]
func (c *ClusterController) ListDedicatedK8sResources(ctx *gin.Context) {
	query := &k8sresourceapi.ListDedicatedK8sResourcesQuery{}
	if err := ctx.ShouldBindQuery(query); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	region, cloudProvider, err := c.resolveRegionCloudProvider(ctx, query)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	body, err := readListK8SResourcesRequest(ctx)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	if query.ClusterId != "" && body.GetPlaneType() == regionalv1.PlaneType_DATA_PLANE {
		if body.DataPlane == nil {
			body.DataPlane = &regionalv1.DataPlane{}
		}
		if body.DataPlane.ClusterId == "" {
			body.DataPlane.ClusterId = query.ClusterId
		}
	}

	resp, err := c.k8sResourceSvc.ListDedicatedK8sResources(ctx, &k8sresource.ListDedicatedK8sResourcesParams{
		Region:        region,
		CloudProvider: cloudProvider,
		Req:           body,
	})
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, &k8sresourceapi.ListDedicatedK8sResourcesResp{
		ContentType: resp.GetContentType().String(),
		Content:     resp.GetContent(),
		Truncated:   resp.GetTruncated(),
		Hints:       resp.GetHints(),
		ItemCount:   resp.GetItemCount(),
	})
}

func (c *ClusterController) resolveRegionCloudProvider(ctx context.Context, query *k8sresourceapi.ListDedicatedK8sResourcesQuery) (region, cloudProvider string, err error) {
	if query.Region != "" && query.CloudProvider != "" {
		return strings.ToLower(query.Region), strings.ToLower(query.CloudProvider), nil
	}
	if query.ClusterId == "" {
		return "", "", fmt.Errorf("must provide region and cloud_provider, or cluster_id")
	}

	clusterID, err := strconv.ParseUint(query.ClusterId, 10, 64)
	if err != nil {
		return "", "", fmt.Errorf("invalid cluster_id: %w", err)
	}
	cluster, err := c.coreSvcCli.Mgmt.GetCluster(ctx, clusterID)
	if err != nil {
		return "", "", fmt.Errorf("failed to get cluster %d: %w", clusterID, err)
	}
	if cluster.GetDeployType() != mgmtv1.DeployType_DEPLOY_TYPE_DEDICATED {
		return "", "", fmt.Errorf("cluster %d is not a dedicated cluster", clusterID)
	}
	return strings.ToLower(cluster.GetRegion()), strings.ToLower(cluster.GetCloudProvider()), nil
}

func readListK8SResourcesRequest(ctx *gin.Context) (*regionalv1.ListK8SResourcesRequest, error) {
	raw, err := io.ReadAll(ctx.Request.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read request body: %w", err)
	}
	if len(raw) == 0 {
		return nil, fmt.Errorf("request body is required")
	}

	var req regionalv1.ListK8SResourcesRequest
	if err := protojson.Unmarshal(raw, &req); err != nil {
		return nil, fmt.Errorf("invalid request body: %w", err)
	}
	return &req, nil
}

// ListClusterResources
//
//	@Id				ListClusterResources
//	@Description	list cluster resources
//	@Tags			ClusterService
//	@Accept			json
//	@Produce		json
//	@Param			cluster_id	path		string	true	"cluster id"
//	@Success		200			{object}	k8sresourceapi.ListClusterResourcesResp
//	@Failure		500			{object}	api.BaseResp
//	@Router			/api/v1/devops/clusters/{cluster_id}/cluster-resources [get]
func (c *ClusterController) ListClusterResources(ctx *gin.Context) {
	clusterID := ctx.Param("cluster_id")
	resources, err := c.mgmtHTTP.ClusterServer(clusterID).ListClusterResources(ctx, nil)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}
	metas, err := c.mgmtHTTP.ClusterServer(clusterID).ListClusterResourceMetas(ctx, nil)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &k8sresourceapi.ListClusterResourcesResp{
		Resources: make([]k8sresourceapi.ClusterResource, 0, len(metas.GetMetas())),
	}

	for _, meta := range metas.GetMetas() {
		if meta != nil && resources.Resources != nil && resources.Resources[meta.Key] != nil {
			resp.Resources = append(resp.Resources, k8sresourceapi.ClusterResource{
				Key:         meta.Key,
				DisplayName: meta.DisplayName,
				Description: meta.Description,
				Type:        strings.ToLower(strings.TrimPrefix(meta.Type.String(), "RESOURCE_TYPE_")),
				Content:     resources.Resources[meta.Key].Content,
			})
		}
	}
	ctrl.Success(ctx, resp)
}
