package central

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/transport/http/tigin"

	"github.com/tidbcloud/aipower/commons/ginparser"
	"github.com/tidbcloud/aipower/commons/grpc"
	"github.com/tidbcloud/aipower/internal/bean/dto"
	"github.com/tidbcloud/aipower/internal/service/openaisvc"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	"github.com/gin-gonic/gin"
)

// Chat with Bot godoc
// @Summary chat with bot
// @Description chat with bot
// @Tags message
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param request_body body dto.ChatBotReqSchema true "request_body"
// @Accept json
// @Produce json
// @Success 200 {object} dto.InteractResponse
// @Router /orgs/{org_id}/projects/{project_id}/bot [post]
func InteractBot(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID}
	params := ginparser.NewCommonParamsByGin(c)
	validateErr := params.ValidateUrl(validateKeys)
	if validateErr != nil {
		tilog.Error(ctx, fmt.Sprintf("ERROR: InteractBot param validate failed! err: %v", validateErr))
		tigin.AbortWithError(c, errs.NewBadRequest("invalid request", validateErr))
		return
	}
	req := &dto.InteractRequest{}
	err := params.BindParam(req)
	if err != nil {
		tilog.Error(ctx, fmt.Sprintf("ERROR: CreateApiKey param bind failed! err: %v", err))
		tigin.AbortWithError(c, errs.NewBadRequest("invalid request", validateErr))
		return
	}
	// Set projectid
	req.ProjectID = params.ProjectID
	req.OrgID = params.OrgID
	tilog.Info(ctx, "interact bot request", zap.Any("req", req))

	if invalidErr := openaisvc.CheckInteractReqeustValid(req); invalidErr != nil {
		tilog.Error(ctx, "CheckInteractReqeustValid failed", zap.Any("req", req), zap.Any("err", invalidErr))
		tigin.AbortWithError(c, errs.NewBadRequest("invalid request", invalidErr))
		return
	}
	rsp, err := openaisvc.InteractBot(ctx, req, req.Caller)

	tilog.Info(ctx, "interact bot result", zap.Any("result", rsp), zap.Any("err", err))

	if err != nil {
		tilog.Info(ctx, "interact bot error", zap.Any("req", req), zap.Any("err", err))
		tigin.AbortWithError(c, err)
		return
	}

	c.JSON(http.StatusOK, rsp)
}

func Fail(c *gin.Context, code int, msg string) {
	c.JSON(code, gin.H{"code": code, "message": msg})
}

var validSortByCols = map[string]struct{}{
	"id":         {},
	"session_id": {},
	"question":   {},
	"response":   {},
	"created_by": {},
	"created_at": {},
	"updated_at": {},
}

var validOrderVals = map[string]struct{}{
	"asc":  {},
	"desc": {},
}

func ListInteractBotRecord(c *gin.Context) {
	req := &dto.ListInteractRecParam{}
	if err := c.ShouldBindQuery(req); err != nil {
		Fail(c, http.StatusBadRequest, "bad request")
		return
	}
	if req.SortBy != "" {
		lowerSortBy := strings.ToLower(req.SortBy)
		if _, ok := validSortByCols[lowerSortBy]; !ok {
			Fail(c, http.StatusBadRequest, "sort_by invalid")
			return
		}
		req.SortBy = lowerSortBy
	}
	if req.Order != "" {
		lowerOrder := strings.ToLower(req.Order)
		if _, ok := validOrderVals[lowerOrder]; !ok {
			Fail(c, http.StatusBadRequest, "order invalid")
			return
		}
		req.Order = lowerOrder
	}
	result, err := openaisvc.ListInteractBotRecord(c.Request.Context(), req)
	if err != nil {
		tilog.Error(c.Request.Context(), "list interact bot record error", zap.Any("req", req), zap.Any("err", err))
		Fail(c, http.StatusInternalServerError, "internal error")
		return
	}

	c.JSON(http.StatusOK, result)
}

func GetAPIKeyInfo(c *gin.Context) {
	params := &dto.GetAPIKeyParams{}
	if err := c.ShouldBindQuery(params); err != nil {
		Fail(c, http.StatusBadRequest, "bad request")
		return
	}

	ctx := c.Request.Context()
	req := &account.GetApiKeyByAccessKeyReq{AccessKey: params.AccessKey}
	resp, err := grpc.GetApiKeyByAccessKey(ctx, req)
	tilog.Info(ctx, "request api key", zap.Any("req", req), zap.Any("resp", resp), zap.Error(err))
	if err != nil {
		tilog.Error(ctx, "failed to get api key by access key", zap.Any("resp", resp), zap.Error(err))
		Fail(c, http.StatusInternalServerError, err.Error())
		return
	}

	var orgIDs []uint64
	for _, i := range resp.ResourceInfos {
		if i.ScopeType == "ORG" {
			orgIDs = append(orgIDs, i.ResourceId)
		}
	}

	tilog.Info(ctx, "request api key", zap.Any("req", req), zap.Any("resp", resp), zap.Error(err), zap.Any("orgIDs", orgIDs))
	c.JSON(http.StatusOK, gin.H{"org_ids": orgIDs})
}

func GetClusterInfo(c *gin.Context) {
	params := &dto.GetClusterDBURLParams{}
	if err := c.ShouldBindQuery(params); err != nil {
		Fail(c, http.StatusBadRequest, "bad request")
		return
	}

	ctx := c.Request.Context()
	cluster, err := grpc.GetClusterByID(ctx, params.ClusterID)
	if err != nil {
		tilog.Error(ctx, "failed to get cluster by id", zap.Any("cluster", cluster), zap.Error(err))
		Fail(c, http.StatusInternalServerError, err.Error())
		return
	}

	if cluster == nil {
		Fail(c, http.StatusNotFound, "cluster not found")
		return
	}

	c.JSON(http.StatusOK, gin.H{"host": "", "port": "", "cluster": cluster})
}

func CheckAPIKeyPermission(c *gin.Context) {
	params := &dto.CheckAPIKeyPermissionParams{}
	if err := c.ShouldBindJSON(params); err != nil {
		Fail(c, http.StatusBadRequest, "bad request")
		return
	}

	ctx := c.Request.Context()
	pass, err := grpc.CheckAPIKeyPermission(ctx, params.APIKey, params.ResourceURI)
	if err != nil {
		tilog.Error(ctx, "failed to check permission", zap.Any("params", params), zap.Error(err))
		Fail(c, http.StatusInternalServerError, err.Error())
		return
	}

	c.JSON(http.StatusOK, gin.H{"pass": pass})
}

func CheckUserPermission(c *gin.Context) {
	params := &dto.CheckUserPermissionParams{}
	if err := c.ShouldBindJSON(params); err != nil {
		Fail(c, http.StatusBadRequest, "bad request")
		return
	}
	ctx := c.Request.Context()

	if params.UserToken != "" {
		resp, err := grpc.GetUserByToken(c.Request.Context(), &account.GetUserByTokenRequest{Token: params.UserToken})
		if err != nil {
			tigin.AbortWithError(c, err)
			return
		}

		params.UserID = resp.User.Id
	}

	pass, err := grpc.CheckUserPermission(ctx, params.UserID, params.ResourceURI)
	if err != nil {
		tilog.Error(ctx, "failed to check permission", zap.Any("params", params), zap.Error(err))
		Fail(c, http.StatusInternalServerError, err.Error())
		return
	}

	c.JSON(http.StatusOK, gin.H{"pass": pass})
}
