package handler

import (
	"net/http"
	"strings"

	"github.com/labstack/echo/v4"

	"github.com/pingcap/test-infra/common/model"
	respM "github.com/pingcap/test-infra/common/model/tcms/response"
	"github.com/pingcap/test-infra/services/tcms/service/common"
	serror "github.com/pingcap/test-infra/services/tcms/service/common/errors"
	"github.com/pingcap/test-infra/services/tcms/utils"
)

// ValidateToken ...
// @Summary Validate token and return info
// @ID validate-token
// @Tags token
// @Param token query string true "Personal access token"
// @Param user query string false "Also return the user info" Enums(1)
// @Success 200 {object} respM.PersonalAccessTokenInfo
// @Failure default {object} HTTPError
// @Router /tokens/validate [get]
func (h *HTTPHandler) ValidateToken(c echo.Context) (err error) {
	ctx := c.(utils.Context)

	t := ctx.QueryParam("token")

	var tokenInfo *model.PersonalToken

	if strings.HasPrefix(t, common.TOKEN_PREFIX) {
		tokenInfo, err = h.service.Token.Validate(ctx, t)
		if err != nil {
			ctx.Log().Warnf("validate token failed, err= %v", err)
			return err
		}
		go func() {
			if err := h.service.Token.UpdateLastUsedAt(tokenInfo.ID); err != nil {
				ctx.Log().Errorf(err, "update personal token last_used_at failed")
			}
		}()
	} else {
		// Otherwise, the token is considered as a JWT token
		claim, err := utils.DecodeJWT(h.config.JWTKey, t)
		if err != nil {
			return serror.New(http.StatusBadRequest, err)
		}
		tokenInfo = &model.PersonalToken{
			UserID: claim.User.ID,
			Scopes: new(string),
		}
	}

	var user *model.User
	if ctx.QueryParam("user") == "1" {
		if tokenInfo.UserID == 0 {
			user = model.GetInternalUser()
		} else {
			user, err = h.service.User.Get(ctx, tokenInfo.UserID)
			if err != nil {
				ctx.Log().Errorf(err, "ValidateToken get user failed")
				return err
			} else if user == nil {
				return serror.ErrInvalidToken
			}
		}
	}

	return ctx.JSON(http.StatusOK, respM.PersonalAccessTokenInfo{
		PersonalToken: *tokenInfo,
		User:          user,
	})
}

// ListTokens ...
// @Summary Create a personal access token.
// @ID list-tokens
// @Tags token
// @Security Bearer
// @Success 200 {object} respM.PersonalAccessTokenInfoListResponse
// @Failure default {object} HTTPError
// @Router /tokens [get]
func (h *HTTPHandler) ListTokens(c echo.Context) error {
	ctx := c.(utils.Context)

	tokens, err := h.service.Token.List(ctx, ctx.User().ID)
	if err != nil {
		ctx.Log().Errorf(err, "ListUserPersonalTokens failed")
		return err
	}

	return ctx.JSON(http.StatusOK, respM.PersonalAccessTokenInfoListResponse{
		Total: len(tokens),
		Data:  tokens,
	})
}

// CreateToken creates a personal access token
// @Summary Create a personal access token.
// @ID create-token
// @Tags token
// @Security Bearer
// @Param name query string true "Token name" minlength(1) maxlength(255)
// @Success 200 {object} map[string]interface{}
// @Failure default {object} HTTPError
// @Router /tokens [post]
func (h *HTTPHandler) CreateToken(c echo.Context) error {
	ctx := c.(utils.Context)

	userID := ctx.User().ID
	u, err := h.service.User.Get(ctx, userID)
	if err != nil {
		return err
	} else if u == nil {
		return serror.ErrCurrentUserNotFound
	}

	tName := ctx.QueryParam("name")
	if tName == "" || len(tName) > 255 {
		return serror.NewMsg(http.StatusBadRequest, "invalid token name")
	}

	// Currently, scopes is unused
	scopes := []string{}
	s := ctx.QueryParam("scopes")
	if s != "" {
		scopes = strings.Split(s, ",")
	}

	ctx.Log().Infof("create personal token, scopes= %v", scopes)

	t, err := h.service.Token.Create(ctx, &model.PersonalToken{
		UserID: userID,
		Name:   tName,
		Scopes: nil,
	})
	if err != nil {
		ctx.Log().Warnf("cannot create personal token, err= %v", err)
		return err
	}

	return ctx.JSON(http.StatusOK, map[string]string{"token": t})
}

// RevokeToken revokes user's tokens.
// @Summary Revokes user's tokens, revoke all tokens if ids is not specified.
// @ID revoke-token
// @Tags token
// @Security Bearer
// @Param ids query []uint64 false "Token ids" collectionFormat(csv)
// @Success 200 {object} map[string]interface{}
// @Failure default {object} HTTPError
// @Router /tokens [delete]
func (h *HTTPHandler) RevokeToken(c echo.Context) error {
	ctx := c.(utils.Context)

	userID := ctx.User().ID
	tokenIDs, err := utils.ParseCSVID(ctx.QueryParam("ids"))
	if err != nil {
		return serror.New(http.StatusBadRequest, err)
	}

	ctx.Log().Infof("revoke personal token, tokenIDs= %v", tokenIDs)

	_, err = h.service.Token.Delete(ctx, userID, tokenIDs)
	if err != nil {
		ctx.Log().Errorf(err, "revoke personal token failed")
		return err
	}

	return ctx.JSON(http.StatusOK, echo.Map{"result": "ok"})
}
