package mgmtportal

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"

	"github.com/gin-gonic/gin"
	"github.com/go-logr/logr"
	userapiv1 "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/api/core-server/user/v1"
	"github.com/tidbcloud/devops/api"
	mgmtportal_api "github.com/tidbcloud/devops/api/mgmtportal"
	mgmtportal_ent "github.com/tidbcloud/devops/entity/mgmtportal"
	opent "github.com/tidbcloud/devops/entity/operation"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
	"github.com/tidbcloud/devops/pkg/infra/config"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	auditsvc "github.com/tidbcloud/devops/pkg/service/audit"
	"github.com/tidbcloud/devops/pkg/service/mgmtportal"
	opsvc "github.com/tidbcloud/devops/pkg/service/operation"
	mgmtportalaction "github.com/tidbcloud/devops/pkg/service/operation/actions/mgmtportalaction"
	opsvcins "github.com/tidbcloud/devops/pkg/service/operation/instance"
	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"go.uber.org/dig"
)

type ControllerParam struct {
	dig.In

	Conf              *config.RootConfig
	Logger            logr.Logger
	MgmtPortalService mgmtportal.MgmtPortalServiceAPI
	OperationService  opsvc.OperationServiceAPI
	AuditService      auditsvc.AuditServiceAPI
}

const MGMTPORTAL_PREFIX = "/inner/mgmt/v1/"

type MgmtPortalController struct {
	mgmtPortalAddr    string
	logger            logr.Logger
	mgmtPortalService mgmtportal.MgmtPortalServiceAPI
	opSvc             opsvc.OperationServiceAPI
	auditSvc          auditsvc.AuditServiceAPI
}

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

func NewMgmtPortalController(p ControllerParam) ctrl.Controller {
	return &MgmtPortalController{
		mgmtPortalAddr:    p.Conf.DbaasAddr.MgmtPortalServiceAddr,
		logger:            p.Logger.WithName("MgmtPortalController"),
		mgmtPortalService: p.MgmtPortalService,
		opSvc:             p.OperationService,
		auditSvc:          p.AuditService,
	}
}

func (c *MgmtPortalController) RegisterAPI(suite *ctrl.Suite) error {
	r := suite.Router
	group := r.Group("/mgmt-portal")
	{
		group.GET("/spus", c.GenerateMgmtPortalProxy("spus", false))
		group.POST("/spus", c.GenerateMgmtPortalProxy("spus", true))
		group.GET("/skus", c.GenerateMgmtPortalProxy("skus", false))
		group.POST("/skus", c.GenerateMgmtPortalProxy("skus", true))
		group.GET("/computeMus", c.GenerateMgmtPortalProxy("computeMus", false))
		group.GET("/storageMus", c.GenerateMgmtPortalProxy("storageMus", false))

		group.PATCH("/spus/:spu_id", c.PatchSpu)
		group.PATCH("/skus/:sku_id", c.PatchSku)
	}
	// {
	// 	r.Any("/mgmt-portal-proxy/*any", c.MgmtPortalProxy)
	// }
	return nil
}

func (c *MgmtPortalController) ListSpus(ctx *gin.Context) {
	req := &mgmtv1.ListSpusRequest{}
	err := ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	resp, err := c.mgmtPortalService.ListSpus(ctx, req)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("internal list attributes error: %s", err), api.ErrCodeInternal)
		return
	}
	ctrl.Success(ctx, resp)
}

func (c *MgmtPortalController) CreateSpu(ctx *gin.Context) {
	req := &mgmtv1.CreateSpuRequest{}
	err := ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	resp, err := c.mgmtPortalService.CreateSpu(ctx, req)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("internal list attributes error: %s", err), api.ErrCodeInternal)
		return
	}
	ctrl.Success(ctx, resp)
}

func (c *MgmtPortalController) ListSkus(ctx *gin.Context) {
	req := &mgmtv1.ListSkusRequest{}
	err := ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	resp, err := c.mgmtPortalService.ListSkus(ctx, req)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("internal list attributes error: %s", err), api.ErrCodeInternal)
		return
	}
	ctrl.Success(ctx, resp)
}

func (c *MgmtPortalController) CreateSku(ctx *gin.Context) {
	req := &mgmtv1.CreateSkuRequest{}
	err := ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	resp, err := c.mgmtPortalService.CreateSku(ctx, req)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("internal list attributes error: %s", err), api.ErrCodeInternal)
		return
	}
	ctrl.Success(ctx, resp)
}

func (c *MgmtPortalController) ListComputMus(ctx *gin.Context) {
	req := &mgmtv1.ListComputeMusRequest{}
	err := ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	resp, err := c.mgmtPortalService.ListComputeMus(ctx, req)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("internal list attributes error: %s", err), api.ErrCodeInternal)
		return
	}
	ctrl.Success(ctx, resp)
}

func (c *MgmtPortalController) ListStorageMus(ctx *gin.Context) {
	req := &mgmtv1.ListStorageMusRequest{}
	err := ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	resp, err := c.mgmtPortalService.ListStorageMus(ctx, req)
	if err != nil {
		ctrl.FailWithCode(ctx, fmt.Errorf("internal list attributes error: %s", err), api.ErrCodeInternal)
		return
	}
	ctrl.Success(ctx, resp)
}

// PatchSpu
//
//	@Id				PatchSpu
//	@Description	path a spu
//	@Tags			MgmtPortalController
//	@Accept			json
//	@Produce		json
//	@Param			spu_id	path		string	true	"spu_id"
//	@Param			req		body		object	false	"request param"
//	@Success		200		{object}	mgmtportal_api.CommonResp
//	@Router			/api/v1/devops/mgmt-portal/spus/{spu_id} [patch]
func (c *MgmtPortalController) PatchSpu(ctx *gin.Context) {
	spuId, err := strconv.ParseUint(ctx.Param("spu_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)

	bodyBytes, _ := io.ReadAll(ctx.Request.Body)
	defer ctx.Request.Body.Close()
	ctx.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))

	targetURL := &url.URL{
		Scheme:   "http",
		Host:     c.mgmtPortalAddr,
		Path:     fmt.Sprintf("%sspus/%s", MGMTPORTAL_PREFIX, strconv.FormatUint(spuId, 10)),
		RawQuery: ctx.Request.URL.RawQuery,
	}

	req := mgmtportal_ent.StoredRequest{
		Method:  ctx.Request.Method,
		URL:     targetURL.String(),
		Headers: make(map[string][]string),
		Body:    string(bodyBytes),
		SpuId:   strconv.FormatUint(spuId, 10),
	}

	for k, v := range ctx.Request.Header {
		req.Headers[k] = v
	}

	bytes, _ := json.Marshal(req)

	createOPparam := opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetUndefined,
		Operation: "PatchSpu",
		Applicant: userProfile.UserEmail,
		Reason:    "",
		Params: map[opent.ParamName]string{
			opent.ParamPatchSpuBody: string(bytes),
			opent.ParamPatchSpuInfo: string(bodyBytes),
			opent.ParamPatchSpuID:   strconv.FormatUint(spuId, 10),
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        mgmtportalaction.ActionPatchSpu.Name,
				StartPolicy: opent.ActionStartPolicyManual,
			},
		},
	}

	insID, err := c.opSvc.CreateInstance(ctx, createOPparam)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &mgmtportal_api.CommonResp{InstanceID: insID}
	ctrl.Success(ctx, resp)
}

// PatchSku
//
//	@Id				PatchSku
//	@Description	path a sku
//	@Tags			MgmtPortalController
//	@Accept			json
//	@Produce		json
//	@Param			sku_id	path		string	true	"sku_id"
//	@Param			req		body		object	false	"request param"
//	@Success		200		{object}	mgmtportal_api.CommonResp
//	@Router			/api/v1/devops/mgmt-portal/skus/{sku_id} [patch]
func (c *MgmtPortalController) PatchSku(ctx *gin.Context) {
	skuId, err := strconv.ParseUint(ctx.Param("sku_id"), 10, 64)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)

	bodyBytes, _ := io.ReadAll(ctx.Request.Body)
	defer ctx.Request.Body.Close()
	ctx.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))

	targetURL := &url.URL{
		Scheme:   "http",
		Host:     c.mgmtPortalAddr,
		Path:     fmt.Sprintf("%sskus/%s", MGMTPORTAL_PREFIX, strconv.FormatUint(skuId, 10)),
		RawQuery: ctx.Request.URL.RawQuery,
	}

	req := mgmtportal_ent.StoredRequest{
		Method:  ctx.Request.Method,
		URL:     targetURL.String(),
		Headers: make(map[string][]string),
		Body:    string(bodyBytes),
		SkuId:   strconv.FormatUint(skuId, 10),
	}

	for k, v := range ctx.Request.Header {
		req.Headers[k] = v
	}

	bytes, _ := json.Marshal(req)

	createOPparam := opsvc.CreateInstanceParam{
		Type:      opent.OperationTargetUndefined,
		Operation: "PatchSku",
		Applicant: userProfile.UserEmail,
		Reason:    "",
		Params: map[opent.ParamName]string{
			opent.ParamPatchSkuBody: string(bytes),
			opent.ParamPatchSkuInfo: string(bodyBytes),
			opent.ParamPatchSkuID:   strconv.FormatUint(skuId, 10),
		},
		Actions: []opsvcins.CreateInstanceActionParam{
			{
				Name:        mgmtportalaction.ActionPatchSku.Name,
				StartPolicy: opent.ActionStartPolicyManual,
			},
		},
	}

	insID, err := c.opSvc.CreateInstance(ctx, createOPparam)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &mgmtportal_api.CommonResp{InstanceID: insID}
	ctrl.Success(ctx, resp)
}
func (c *MgmtPortalController) GenerateMgmtPortalProxy(path string, audit bool) func(ctx *gin.Context) {
	return func(ctx *gin.Context) {
		c.logger.Info("handle mgmt portal proxy", "path", ctx.Request.URL.Path)

		director := func(req *http.Request) {
			req.URL.Scheme = "http"
			req.URL.Host = c.mgmtPortalAddr
			req.URL.Path = MGMTPORTAL_PREFIX + path
		}
		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)
	}
}
