package handler

import (
	"errors"
	"net/http"
	"strconv"

	"github.com/labstack/echo/v4"

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

// LoginGoogleAuth ...
// LoginGoogleAuth will not store id_token
// @Summary Login via Google OAuth2
// @ID login-google-auth
// @Tags user
// @Param id_token query string true "Google OAuth id_token"
// @Success 200 {object} map[string]interface{}
// @Failure default {object} HTTPError
// @Router /users/login/google-auth [get]
func (h *HTTPHandler) LoginGoogleAuth(c echo.Context) error {
	ctx := c.(utils.Context)

	idToken := c.QueryParam("id_token")
	if idToken == "" {
		err := errors.New("id_token is empty")
		return serror.New(http.StatusBadRequest, err)
	}

	// See https://developers.google.com/identity/sign-in/web/backend-auth?hl=en#verify-the-integrity-of-the-id-token.

	// validate id_token
	res, err := h.service.Google.ValidateIDToken(ctx.Request().Context(), idToken)
	if err != nil {
		return serror.New(http.StatusBadRequest, err)
	}

	// 2. Create/Update user if needed
	user, err := h.service.User.GetByEmail(ctx, res.Email)
	if err != nil {
		ctx.Log().Errorf(err, "GetUserByEmail failed")
		return err
	}

	if user != nil {
		if user.Name != res.Name ||
			user.Avatar != res.Avatar {
			user, err = h.service.User.Update(ctx, &model.User{
				ID:     user.ID,
				Email:  res.Email,
				Name:   res.Name,
				Avatar: res.Avatar,
			})
		}
	} else {
		user, err = h.service.User.Create(ctx, &model.User{
			Email:  res.Email,
			Name:   res.Name,
			Avatar: res.Avatar,
		})
	}
	if err != nil {
		ctx.Log().Errorf(err, "create or update user failed")
		return err
	}

	// return jwt token
	jwtToken := utils.JWTClaims{
		User: utils.SessionUser{
			ID: user.ID,
		},
	}.Encode(h.config.JWTKey)

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

// CurrentUser returns current user info to client.
// @Summary Get current user
// @ID current-user
// @Security Bearer
// @Tags user
// @Success 200 {object} model.User
// @Failure default {object} HTTPError
// @Router /users/current [get]
func (h *HTTPHandler) CurrentUser(c echo.Context) error {
	ctx := c.(utils.Context)

	// get user in Session
	uid := ctx.User().ID
	log := ctx.Log().With("userID", uid)

	var u *model.User
	if uid == 0 {
		u = model.GetInternalUser()
	} else {
		// get user in DB
		_u, err := h.service.User.Get(ctx, uid)
		if err != nil {
			log.Errorf(err, "get user from db failed")
			return err
		} else if _u == nil {
			log.Infof("user not found in db, return 401")
			return serror.ErrCurrentUserNotFound
		}
		u = _u
	}

	return ctx.JSON(http.StatusOK, u)
}

// ListUsers ...
// @Summary List users
// @ID list-users
// @Tags user
// @Param ids query []uint64 false "Filter by user ids" collectionFormat(csv) maxItems(100) uniqueItems(true)
// @Param emails query []string false "Filter by user emails" collectionFormat(csv) maxItems(100) uniqueItems(true)
// @Success 200 {object} model.UserListResponse
// @Failure default {object} HTTPError
// @Router /users [get]
func (h *HTTPHandler) ListUsers(c echo.Context) error {
	ctx := c.(utils.Context)

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

	userEmails, err := utils.ParseCSV(ctx.QueryParam("emails"))
	if err != nil {
		return serror.New(http.StatusBadRequest, err)
	}

	log := ctx.Log()
	log.Infof("ListUsers, idsLen= %v, emailsLen= %v", len(userIDs), len(userEmails))

	if len(userIDs) == 0 && len(userEmails) == 0 {
		return serror.NewMsg(http.StatusBadRequest, "expect one of ids and emails")
	}

	// get users in DB
	dbUsers, err := h.service.User.ListBasicInfo(ctx, userIDs, userEmails)
	if err != nil {
		log.Errorf(err, "get users from db failed")
		return err
	}

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

// AutoCompleteUser ...
// @Summary Auto complete user by email.
// @ID autocomplete-user
// @Tags user
// @Param query query string true "Filter by query"
// @Param count query uint32 false "Number of items returned" default(10) minimum(1) maximum(100)
// @Success 200 {object} model.UserListResponse
// @Failure default {object} HTTPError
// @Router /users/autocomplete [get]
func (h *HTTPHandler) AutoCompleteUser(c echo.Context) error {
	ctx := c.(utils.Context)

	query := c.QueryParam("query")
	if query == "" || len(query) > 255 {
		return ctx.JSON(http.StatusOK, respM.UserListResponse{
			Total: 0,
			Data:  []*model.User{},
		})
	}

	count, err := strconv.Atoi(c.QueryParam("count"))
	if count <= 0 || err != nil {
		count = 10
	} else if count > 500 {
		count = 500
	}

	log := ctx.Log().With("query", query, "count", count)
	dbUsers, err := h.service.User.AutoComplete(ctx, query, count)
	if err != nil {
		log.Errorf(err, "auto complete failed")
		return err
	}

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