package pingchat

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/go-resty/resty/v2"
	"github.com/google/go-querystring/query"
	"github.com/tidbcloud/ecosystem-service/config"
	"github.com/tidbcloud/pkgv2/tracing"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	tiinf_auth "github.com/tidbcloud/tiinf/sdk/auth"
	"go.opentelemetry.io/otel"
	"go.uber.org/zap"
)

var Tracer = otel.Tracer("connect_controller")

type PingChatController struct {
	config *config.ConfigWrapper
}

type errorResponse struct {
	Code    int           `json:"code,omitempty"`
	Message string        `json:"message,omitempty"`
	Details []interface{} `json:"details,omitempty"`
}

func NewPingChatController(config *config.ConfigWrapper) *PingChatController {
	return &PingChatController{
		config: config,
	}
}

//	@title			PingChat Swagger API
//	@version		1.0
//	@termsOfService	http://swagger.io/terms/

//	@host		api.tidbcloud.com
//	@schemes	https

// Chat godoc
//
//	@Description	Chat with PingChat
//	@id				Chat
//	@Tags			PingChatService
//	@Accept			json
//	@Produce		json
//	@Param			chatInfo	body		ChatInfo		true	"chat information"
//	@Success		200			{object}	ChatResponse	"ok"
//	@Failure		400			{object}	object			"error"
//	@Failure		500			{object}	object			"error"
//	@response		default		{object}	object			"other error"
//	@Router			/ecosystem/api/internal/pingchat/chat [post]
func (c *PingChatController) Chat(ctx *gin.Context) {
	ctxReq := ctx.Request.Context()
	var err error
	_, span := Tracer.Start(ctxReq, "pingchat.Chat")
	defer func() {
		tracing.EndSpan(span, err)
	}()
	logger := tilog.WithContextL(ctxReq).With(zap.String("method", "PingChatController.Chat"))

	var chatInfo ChatInfo
	err = ctx.ShouldBindJSON(&chatInfo)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, errorResponse{http.StatusBadRequest, "Invalid request body", nil})
		logger.Error("Invalid request body", zap.Error(err), zap.Any("body", chatInfo))
		return
	}

	userIdentity, err := getUserIdentityFromContext(ctx)
	if err != nil {
		ctx.JSON(http.StatusUnauthorized, errorResponse{http.StatusUnauthorized, "Invalid user", nil})
		logger.Error("Can't get user from context", zap.Error(err))
		return
	}
	logger = logger.With(zap.String("publicKey", userIdentity))

	v, _ := query.Values(ChatParams{
		Domain: chatInfo.Domain,
	})
	var resp ChatResponse
	client := resty.New()
	response, err := client.
		R().
		SetQueryString(v.Encode()).
		SetHeader("content-type", "application/json").
		SetHeader("x-pingchat-access-key", c.config.PingChatConfig.AccessKey).
		SetHeader("x-pingchat-access-secret", c.config.PingChatConfig.AccessSecret).
		// Use user's public key as the x-pingchat-user identity
		SetHeader("x-pingchat-user", userIdentity).
		SetBody(chatInfo.Messages).
		SetResult(&resp).
		Post("https://chat-api.pingcap.net/chat")
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, errorResponse{http.StatusInternalServerError, err.Error(), nil})
		logger.Error("Failed to chat with PingChat", zap.Error(err))
		return
	}

	if !response.IsSuccess() {
		ctx.JSON(http.StatusInternalServerError, errorResponse{
			http.StatusInternalServerError,
			fmt.Sprintf("Chat with PingChat return error, status code: %d, body: %s", response.StatusCode(), string(response.Body())),
			nil,
		})
		logger.Error("Chat with PingChat return error", zap.Int("status_code", response.StatusCode()), zap.String("body", string(response.Body())))
		return
	}

	logger.Info("Chat with PingChat success")
	ctx.JSON(http.StatusOK, &resp)
}

func getUserIdentityFromContext(ctx *gin.Context) (string, error) {
	identityInfo, err := tiinf_auth.GetIdentityInfoFromGinContext(ctx)
	if err != nil {
		return "", err
	}
	switch identityInfo.IdentityType {
	case tiinf_auth.AuthnIdentityTypeUserToken:
		return identityInfo.UserTokenRsp.User.Email, nil
	case tiinf_auth.AuthnIdentityTypeApiKeyAK:
		return identityInfo.ApiKeyRsp.ApiKey.AccessKey, nil
	case tiinf_auth.AuthnIdentityTypeAccessToken:
		return identityInfo.AccessTokenRsp.AccessTokenIdentity.UserEmail, nil
	}

	return identityInfo.ApiKeyRsp.ApiKey.AccessKey, nil
}
