package api

import (
	"context"
	"net/http"
	"strings"

	"github.com/tidbcloud/account/internel/dto"

	"github.com/tidbcloud/account/internel/svc/comsvc"

	"github.com/tidbcloud/account/central/utils"

	"google.golang.org/protobuf/types/known/timestamppb"

	historysvc "github.com/tidbcloud/account/internel/svc/history"

	"github.com/tidbcloud/account/internel/dao/entity"
	userDao "github.com/tidbcloud/account/internel/dao/user"

	usersvc "github.com/tidbcloud/account/internel/svc/user"

	"github.com/gin-gonic/gin"
	"github.com/tidbcloud/account/admin/forms"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

// @Summary ListUsers
// @tags User
// @Accept  json
// @Produce json
// @Param search query forms.ListUsersReq  true "search"
// @Success 200 {object} forms.ListUsersResp
// @Router /users [get]
func ListUsers(c *gin.Context) {
	ctx := c.Request.Context()

	req := forms.ListUsersReq{}
	if err := c.ShouldBindQuery(&req); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	tilog.Info(ctx, "GetOrgUsers req", zap.Any("req", req))

	//userSvc, _ := usersvc.GetUserSvc()
	filters := map[string]interface{}{}
	//"email in (?)": filter.Email,

	if req.UserId != 0 {
		filters["id"] = req.UserId
	}
	if req.FirstName != "" {
		filters["first_name"] = req.FirstName
	}
	if req.LastName != "" {
		filters["last_name"] = req.LastName
	}
	if req.Status != "" {
		filters["status"] = req.Status
	}
	if req.EmailEq != "" {
		filters["email"] = req.EmailEq
	}

	advancedFilters := map[string]interface{}{}
	if req.EmailLike != "" {
		advancedFilters["email like (?) "] = "%" + req.EmailLike + "%"

	}
	params := dto.PageParams{
		Filters:         filters,
		AdvancedFilters: advancedFilters,
		Order:           req.Order,
		SortBy:          req.SortBy,
		Page:            req.Pager.Page,
		PageSize:        req.Pager.PageSize,
	}
	//if req.Pager.SortBy != "" {
	//	params.SortBy = req.Pager.SortBy
	//}

	//paginator, err := userSvc.ListUsersByPage(ctx, params)
	paginator, err := comsvc.ListEntitiesByPage(ctx, params, &entity.User{})
	tilog.Info(ctx, "list users by page", zap.Any("paginator.Total", paginator.Total))
	if err != nil {
		tilog.Error(ctx, "failed to list users", zap.Error(err))
		Fail(c, err, http.StatusBadRequest)
	}
	users := paginator.Data.([]entity.User)
	var userIDs []entity.ID
	for _, u := range users {
		userIDs = append(userIDs, u.ID)
	}
	loginHistorySvc, _ := historysvc.GetHistorySvc()
	historyMap, _ := loginHistorySvc.GetUserLastHistory(ctx, userIDs, entity.HistoryEventLogin)

	var us []*forms.User

	for _, u := range users {

		obj := &forms.User{Email: u.Email, ID: u.ID.Uint64(), FirstName: u.FirstName, LastName: u.LastName}
		history, ok := historyMap[u.ID.Uint64()]
		if ok {

			obj.LoginTime = timestamppb.New(*(history.UpdatedAt))

		}
		us = append(us, obj)

	}

	resp := &forms.ListUsersResp{
		Total: paginator.Total,
		Items: us,
	}
	Success(c, resp, http.StatusOK)
}

// @Summary DeleteUserFromAllOrg
// @tags ScopeUser
// @Accept  json
// @Produce json
// @Param org_id path string  true "user_id"
// @Success 200 {object} forms.EmptyResponse
// @Router /users/{user_id} [delete]
func DeleteUserFromAllOrg(c *gin.Context) {
	ctx := c.Request.Context()
	reqUri := forms.GetUserIDReq{}
	if err := c.ShouldBindUri(&reqUri); err != nil {
		tilog.Info(ctx, "ShouldBindUri err", zap.Error(err))
		Fail(c, err, http.StatusBadRequest)
		return
	}
	tilog.Info(ctx, "DeleteUserFromAllOrg req", zap.Any("req", reqUri))
	//user, err := GetUserFromContext(c)
	//if err != nil {
	//	tilog.Error(ctx, "failed to get user from context", zap.Error(err))
	//	Fail(c, err, http.StatusInternalServerError)
	//	return
	//}

	userSvc, _ := usersvc.GetUserSvc()
	err := userSvc.DeleteUserFromAllOrg(ctx, reqUri.UserID)
	if err != nil {
		tilog.Error(ctx, "failed to DeleteUserFromAllOrg", zap.Error(err))
		Fail(c, err, http.StatusInternalServerError)
	}
	tilog.Info(ctx, "DeleteUserFromAllOrg success")
	Success(c, nil, http.StatusOK)
}

// @Summary RemoveUserFromOrg
// @tags Tenant
// @Accept  json
// @Produce json
// @Param org_id path string  true "org_id"
// @Param org_id path string  true "user_id"
// @Success 200 {object} forms.EmptyResponse
// @Router /orgs/{org_id}/user/{id} [delete]
func RemoveUserFromOrg(c *gin.Context) {
	ctx := c.Request.Context()
	reqUri := forms.GetOrgIDAndUserIDReq{}
	if err := c.ShouldBindUri(&reqUri); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	tilog.Info(ctx, "RemoveUserFromOrg req", zap.Any("req", reqUri))

	userSvc, _ := usersvc.GetUserSvc()
	err := userSvc.RemoveUserFromOrg(ctx, reqUri.UserID, reqUri.UserID, reqUri.OrgID)
	if err != nil {
		tilog.Warn(ctx, "failed to RemoveUserFromOrg", zap.Error(err))
		Fail(c, err, http.StatusBadRequest)
	}

	Success(c, nil, http.StatusOK)
}

// @Summary AddUserToOrg
// @tags Tenant
// @Accept  json
// @Produce json
// @Param org_id path string  true "org_id"
// @Param org_id path string  true "user_id"
// @param req body forms.OrgRoleReq true "org_role"
// @Success 200 {object} forms.EmptyResponse
// @Router /orgs/{org_id}/users/{user_id} [post]
func AddUserToOrg(c *gin.Context) {
	ctx := c.Request.Context()
	reqUri := forms.GetOrgIDAndUserIDReq{}
	if err := c.ShouldBindUri(&reqUri); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}
	reqBody := forms.OrgRoleReq{}
	if err := c.ShouldBindJSON(&reqBody); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	tilog.Info(ctx, "AddUserToOrg req", zap.Any("reqBody", reqBody))

	userSvc, _ := usersvc.GetUserSvc()
	err := userSvc.AddUserToOrg(ctx, usersvc.AddUserToOrgReq{UserId: reqUri.UserID, OrgId: reqUri.OrgID, OrgRole: reqBody.OrgRole, OperatorId: reqUri.UserID})
	if err != nil {
		tilog.Error(ctx, "failed to AddUserToOrg", zap.Error(err))
		Fail(c, err, http.StatusBadRequest)
	}

	Success(c, nil, http.StatusOK)
}

// @Summary GetOrgUsers
// @tags Tenant
// @Accept  json
// @Produce json
// @Param org_id path string  true "org_id"
// @Param search query forms.GetScopeUsersReq  true "search"
// @Success 200 {object} forms.ListUsersResp
// @Router /orgs/{org_id}/users [get]
func GetOrgUsers(c *gin.Context) {
	ctx := c.Request.Context()
	reqUri := forms.GetOrgIDReq{}
	if err := c.ShouldBindUri(&reqUri); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}
	req := forms.GetScopeUsersReq{}
	if err := c.ShouldBindQuery(&req); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	tilog.Info(ctx, "GetOrgUsers req", zap.Any("req", req))
	param := ListUsersParam{
		Order:     req.Order,
		SortBy:    req.SortBy,
		Search:    req.Search,
		Page:      req.Page,
		PerPage:   req.PageSize,
		ScopeId:   reqUri.OrgID,
		ScopeType: entity.RoleBindingScopeOrg}
	users, err := listRoleBindingUsers(ctx, param)
	if err != nil {
		tilog.Error(ctx, "failed to listRolebindingUsersByScope", zap.Error(err))
		Fail(c, err, http.StatusBadRequest)
	}

	resp := &forms.GetScopeUsersResp{
		Total: uint64(len(users)),
		Items: utils.Pagination(req.Page, req.PageSize, users).([]*forms.ScopeUser),
	}
	Success(c, resp, http.StatusOK)
}

// @Summary GetProjectUsers
// @tags Tenant
// @Accept  json
// @Produce json
// @Param org_id path string  true "org_id"
// @Param org_id path string  true "project_id"
// @Param page query forms.GetScopeUsersReq  true "page"
// @Success 200 {object} forms.ListUsersResp
// @Router /orgs/{org_id}/projects/{project_id}/users [get]
func GetProjectUsers(c *gin.Context) {
	ctx := c.Request.Context()
	reqUri := forms.GetProjectIDReq{}
	if err := c.ShouldBindUri(&reqUri); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}
	req := forms.GetScopeUsersReq{}
	if err := c.ShouldBindQuery(&req); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	tilog.Info(ctx, "GetOrgUsers req", zap.Any("req", req))
	param := ListUsersParam{
		Order:     req.Order,
		SortBy:    req.SortBy,
		Search:    req.Search,
		Page:      req.Page,
		PerPage:   req.PageSize,
		ScopeId:   reqUri.ProjectID,
		ScopeType: entity.RoleBindingScopeProject}
	users, err := listRoleBindingUsers(ctx, param)
	if err != nil {
		tilog.Error(ctx, "failed to listRolebindingUsersByScope", zap.Error(err))
		Fail(c, err, http.StatusBadRequest)
	}
	resp := &forms.GetScopeUsersResp{
		Total: uint64(len(users)),
		Items: utils.Pagination(req.Page, req.PageSize, users).([]*forms.ScopeUser),
	}
	Success(c, resp, http.StatusOK)
}

func listRoleBindingUsers(ctx context.Context, req ListUsersParam) ([]*forms.ScopeUser, error) {

	f := userDao.RoleBindingUserFilter{
		RoleBindingStatus: []string{string(entity.Active), entity.RoleBindingStatusInactive},
		UserStatus:        []string{string(entity.UserStatusActive), string(entity.UserStatusPending)},
		ScopeType:         req.ScopeType,
		ScopeId:           req.ScopeId,
		Order:             req.Order,
		SortBy:            req.SortBy,
		EmailLike:         req.Search,
	}

	userSvc, _ := usersvc.GetUserSvc()
	userList, err := userSvc.ListRoleBindingUsers(ctx, f)

	if err != nil {
		return nil, err
	}
	//
	//err := slice.SortByField(userList, "age", "desc")
	//slice.SortBy()
	// add login history time
	loginTimeMap := getUserLoginTime(ctx, userList)
	//  roleBindingUser convert to central.user and  ignore system user
	users := usersOutput(userList, loginTimeMap)
	return users, nil

}

func usersOutput(users []userDao.RoleBindingUser, historyMap map[uint64]entity.EventHistoryLast) []*forms.ScopeUser {

	var opUsers []*forms.ScopeUser
	for _, user := range users {
		// Ignore system user
		if user.FirstName == "system" && user.LastName == "user" {
			continue
		}
		// inactive in the rolBinding is pending
		if user.Status == entity.RoleBindingStatusInactive {
			user.Status = entity.RoleBindingStatusPending
		}
		opUser := &forms.ScopeUser{
			ID:         user.UserId,
			Email:      user.Email,
			Status:     strings.ToUpper(user.Status),
			FirstName:  user.FirstName,
			LastName:   user.LastName,
			InviteTime: timestamppb.New(user.InviteTime),
			Role:       user.Role,
			//Role:       user.Role,
		}
		//h := historyMap[user.UserId]
		history, ok := historyMap[user.UserId]
		if ok && user.Status == string(entity.Active) {

			opUser.LoginTime = timestamppb.New(*(history.UpdatedAt))

		}
		opUsers = append(opUsers, opUser)
	}
	return opUsers
}

func getUserLoginTime(ctx context.Context, users []userDao.RoleBindingUser) map[uint64]entity.EventHistoryLast {

	var userIDs []entity.ID
	for _, u := range users {
		userIDs = append(userIDs, entity.ID(u.UserId))
	}
	loginHistorySvc, _ := historysvc.GetHistorySvc()
	historyMap, _ := loginHistorySvc.GetUserLastHistory(ctx, userIDs, entity.HistoryEventLogin)

	return historyMap

}

type ListUsersParam struct {
	ScopeType entity.ScopeType
	ScopeId   uint64
	Page      uint32
	PerPage   uint32
	Order     string
	SortBy    string
	Search    string
}

func TestCrudUser(c *gin.Context) {
	ctx := c.Request.Context()

	req := forms.ListUsersReq{}
	if err := c.ShouldBindQuery(&req); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	tilog.Info(ctx, "GetOrgUsers req", zap.Any("req", req))

	//userSvc, _ := usersvc.GetUserSvc()
	filters := map[string]interface{}{}
	//"email in (?)": filter.Email,

	if req.UserId != 0 {
		filters["id"] = req.UserId
	}

	advancedFilters := map[string]interface{}{}
	if req.EmailLike != "" {
		advancedFilters["email like (?) "] = "%" + req.EmailLike + "%"

	}
	params := dto.ComFilter{
		Filters:         filters,
		AdvancedFilters: advancedFilters,
	}
	//updates := map[string]interface{}{"first_name": "1111111"}
	//err := comsvc.UpdateEntity(ctx, params, updates, &entity.User{})

	//err := comsvc.DeleteEntity(ctx, params, &entity.EventHistory{})
	user, err := comsvc.GetEntity(ctx, params, &entity.User{})
	if err != nil {
		tilog.Error(ctx, "failed to ListUsersByPage", zap.Error(err))
		Fail(c, err, http.StatusBadRequest)
	}
	tilog.Info(ctx, "GetOrgUsers success")
	Success(c, user, http.StatusOK)
}
