package o11y

import (
	"fmt"
	"net/http"
	"net/http/httputil"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/go-logr/logr"
	"go.uber.org/dig"

	"github.com/tidbcloud/devops/api"
	o11yapi "github.com/tidbcloud/devops/api/o11y"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
	"github.com/tidbcloud/devops/pkg/infra/clients/cliniccli"
	"github.com/tidbcloud/devops/pkg/infra/clients/o11ycli"
	"github.com/tidbcloud/devops/pkg/infra/config"
)

type ControllerParam struct {
	dig.In

	Conf         *config.RootConfig
	O11yClient   o11ycli.Client
	ClinicClient cliniccli.Client
	Logger       logr.Logger
}

type O11yController struct {
	addr   string
	o11y   o11ycli.Client
	clinic cliniccli.Client
	logger logr.Logger
}

func (c *O11yController) Name() string {
	return "O11yController"
}

func NewO11yController(p ControllerParam) ctrl.Controller {
	return &O11yController{
		addr:   p.Conf.DbaasAddr.O11yServiceAddr,
		o11y:   p.O11yClient,
		clinic: p.ClinicClient,
		logger: p.Logger.WithName("O11yController"),
	}
}

func (c *O11yController) RegisterAPI(suite *ctrl.Suite) error {
	r := suite.Router
	g := r.Group("/o11y")
	{
		g.GET("/clusters", c.ListClusters)
		g.POST("/clusters", c.CreateCluster)
		g.GET("/clusters/:id", c.GetCluster)
		g.DELETE("/clusters/:id", c.DeleteCluster)
		g.Any("/proxy/*any", c.O11yProxy)
	}
	return nil
}

func (c *O11yController) O11yProxy(ctx *gin.Context) {
	c.logger.Info("handle o11y proxy", "path", ctx.Request.URL.Path)

	proxyPath := ctx.Params.ByName("any")
	director := func(req *http.Request) {
		req.URL.Scheme = "https"
		req.URL.Host = c.addr
		req.URL.Path = proxyPath
	}
	proxy := &httputil.ReverseProxy{Director: director}
	modifyFunc := func(resp *http.Response) error {
		resp.Header.Del("Access-Control-Allow-Origin")
		return nil
	}
	proxy.ModifyResponse = modifyFunc
	proxy.ServeHTTP(ctx.Writer, ctx.Request)
}

// ListClusters
//
//	@Id				O11yControllerListClusters
//	@Description	list clusters
//	@Tags			O11yController
//	@Accept			json
//	@Produce		json
//	@Param			req	query		o11yapi.ListClustersRequest	false	"request param"
//	@Success		200	{object}	o11yapi.ListClustersResponse
//	@Router			/api/v1/devops/o11y/clusters [get]
func (c *O11yController) ListClusters(ctx *gin.Context) {
	req := &o11yapi.ListClustersRequest{}
	err := ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	resp, err := c.o11y.ListClusters(ctx, *req)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctx.JSON(http.StatusOK, o11yapi.ListClustersResponse(*resp))
}

// GetCluster
//
//	@Id				O11yControllerGetCluster
//	@Description	get clusters
//	@Tags			O11yController
//	@Accept			json
//	@Produce		json
//	@Param			id	path		string						true	"id"
//	@Param			req	query		o11yapi.GetClusterRequest	false	"request param"
//	@Success		200	{object}	o11yapi.GetClusterResponse
//	@Router			/api/v1/devops/o11y/clusters/{id} [get]
func (c *O11yController) GetCluster(ctx *gin.Context) {
	req := &o11yapi.GetClusterRequest{}
	err := ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	id := ctx.Param("id")

	resp, err := c.o11y.GetCluster(ctx, id, *req)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctx.JSON(http.StatusOK, o11yapi.GetClusterResponse(*resp))
}

// CreateCluster
//
//	@Id				O11yControllerCreateCluster
//	@Description	create a cluster
//	@Tags			O11yController
//	@Accept			json
//	@Produce		json
//	@Param			req	body		o11yapi.CreateClusterRequest	true	"request param"
//	@Success		200	{object}	o11yapi.CreateClusterResponse
//	@Router			/api/v1/devops/o11y/clusters [post]
func (c *O11yController) CreateCluster(ctx *gin.Context) {
	req := &o11yapi.CreateClusterRequest{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	now := time.Now()

	orgInClinic, err := c.clinic.GetOrganizationByCloudID(ctx, req.OrgID)
	if err != nil {
		ctrl.Fail(ctx, fmt.Errorf("failed to get org from clinic: %w", err))
		return
	}

	// create cluster in o11y
	resp, err := c.o11y.CreateCluster(ctx, *req)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	// add cluster to clinic
	clinicReq := cliniccli.AddClusterRequest{
		Name:        resp.Data.ID, // name is o11y clusterID
		Description: resp.Data.Note,
		CreatedAt:   uint64(now.Unix()),
		ClusterType: cliniccli.ClusterTypeCloud,
		DeployType:  cliniccli.DeployTypeByoc,
		ClusterID:   resp.Data.ApplicationID, // NOTE: o11y applicationID is clinic clusterID
		ProjectID:   resp.Data.ProjectID,
		Region:      resp.Data.Region,
		Provider:    resp.Data.Vendor,
	}
	_, err = c.clinic.AddCluster(ctx, orgInClinic.ID, clinicReq)
	if err != nil {
		ctrl.Fail(ctx, fmt.Errorf("created cluster but failed to add cluster in clinic, please contact admin: %w", err))
		return
	}

	ctx.JSON(http.StatusOK, o11yapi.CreateClusterResponse(*resp))
}

// DeleteCluster
//
//	@Id				O11yControllerDeleteCluster
//	@Description	delete a cluster
//	@Tags			O11yController
//	@Accept			json
//	@Produce		json
//	@Param			id	path		string							true	"id"
//	@Param			req	body		o11yapi.DeleteClusterRequest	true	"request param"
//	@Success		200	{object}	o11yapi.DeleteClusterResponse
//	@Router			/api/v1/devops/o11y/clusters/{id} [delete]
func (c *O11yController) DeleteCluster(ctx *gin.Context) {
	id := ctx.Param("id")
	req := &o11yapi.DeleteClusterRequest{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	getResp, err := c.o11y.GetCluster(ctx, id, o11yapi.GetClusterRequest{
		BaseRequest: req.BaseRequest,
	})
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	clustersInClinic, err := c.clinic.ListClusters(ctx, &cliniccli.ClusterFilter{
		DeployType: []string{cliniccli.DeployTypeByoc},
		ClusterIds: []string{getResp.Data.ApplicationID}, // NOTE: o11y applicationID is clinic clusterID
	})
	if err != nil {
		ctrl.Fail(ctx, fmt.Errorf("failed to query this cluster from clinic: %w", err))
		return
	}
	if len(clustersInClinic) != 0 {
		clusterInClinic := clustersInClinic[0]
		err := c.clinic.UpdateClusterStatus(ctx, clusterInClinic.OrgID, clusterInClinic.ClusterID, cliniccli.UpdateClusterStatusReq{
			Status: cliniccli.ClusterStatusCleared,
		})
		if err != nil {
			ctrl.Fail(ctx, fmt.Errorf("failed to update status of cluster in clinic: %w", err))
			return
		}
	} else {
		c.logger.Info("skip to delete cluster in clinic cause no corresponding cluster", "cluster_id", getResp.Data.ApplicationID)
	}

	resp, err := c.o11y.DeleteCluster(ctx, id, *req)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctx.JSON(http.StatusOK, o11yapi.DeleteClusterResponse(*resp))
}
