package user

import (
	context "context"
	"errors"
	"fmt"

	"github.com/tidbcloud/account/central/events/publish"

	"github.com/tidbcloud/account/internel/dao/role"
	"github.com/tidbcloud/account/internel/svc/user/session"
	"gorm.io/gorm"

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

	"github.com/tidbcloud/tiinf/errs"

	"github.com/tidbcloud/account/commons/database"

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

	"github.com/tidbcloud/account/central/events/userevent"

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

	"go.uber.org/zap"

	"github.com/tidbcloud/account/commons/singleton"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/tiinf/monitor/tilog"
)

type AddUserToOrgReq struct {
	OrgRole    string
	OrgId      uint64
	UserId     uint64
	OperatorId uint64
}

func (s *UserSvc) RemoveUserFromOrg(ctx context.Context, userId, dbUserOperatorId, orgId uint64) error {
	orgEnt, err := dao.GetDaoHub().Org.GetOrgByOrgID(ctx, database.DBConn.DB, orgId)
	if err != nil {
		tilog.Error(ctx, "RemoveUserFromOrg GetOrgByOrgID error", zap.Any("org_id", orgId), zap.Any("err", err))
		return errs.NewInternalUnknown("internal error", err)
	}
	if orgEnt == nil || orgEnt.Status != entity.TenantStatusActive {
		tilog.Error(ctx, "RemoveUserFromOrg GetOrgByOrgID error", zap.Any("org_id", orgId), zap.Any("err", err))
		return errs.NewForbidden("org is not active", nil)
	}
	userEnt, err := dao.GetDaoHub().User.GetUserByID(ctx, database.DBConn.DB, userId)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return errs.NewNotFound("user not found", nil)
		}
		tilog.Error(ctx, "RemoveUserFromOrg GetUserByID error", zap.Any("user_id", userId), zap.Any("err", err))
		return errs.NewInternalUnknown("internal error", err)
	}

	// 1、quer user status
	roleBindingUser, err := s.GetRoleBindingUser(ctx, entity.RoleBindingScopeOrg, orgId, userId)
	if err != nil {
		return err
	}

	// check user is last org owner
	if roleBindingUser.Role == entity.RoleOrgOwner {
		ownerBindings, err := dao.GetDaoHub().Role.ListRoleBindings(ctx, database.DBConn.DB, role.RoleBindingFilter{
			ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeOrg},
			ScopeIDs:   []entity.ID{entity.ID(orgId)},
			TargetType: []entity.TargetType{entity.RoleBindingTargetUser},
			Status:     []string{entity.RoleBindingStatusActive},
			Roles:      []string{entity.RoleOrgOwner},
		})
		if err != nil {
			tilog.Info(ctx, "RemoveUserFromOrg list org owner role bindings error", zap.Any("org_id", orgId), zap.Any("err", err))
			return err
		}
		// if exists only one org owner user and the user is to updated user, can not edit role
		if len(ownerBindings) == 1 && ownerBindings[0].TargetID.Uint64() == userId {
			tilog.Info(ctx, "RemoveUserFromOrg fail, org needs to have at least one org owner", zap.Any("org_id", orgId), zap.Any("err", err))
			return errs.NewBadRequest("The organization needs to have at least one owner.", nil)
		}
	}

	// 2、DeleteUserFromOrg
	deleteEmail, shouldDeleteUser, err := DeleteUserFromOrg(ctx, orgId, userId, dbUserOperatorId)
	if err != nil {
		if tierr, ok := errs.AsTiError(err); ok && (tierr.HTTPCode() == 400 || tierr.HTTPCode() == 404) {
			tilog.Info(ctx, "DeleteUserFromOrg err "+err.Error())
		} else {
			tilog.Error(ctx, "DeleteUserFromOrg err "+err.Error())
		}
		return err
	}

	// 3、 delete auth0 user
	if shouldDeleteUser && deleteEmail != "" {

		if err := DeleteAuth0User(ctx, deleteEmail); err != nil {
			tilog.Error(ctx, "DeleteAuth0User err"+err.Error())
		}
		tilog.Info(ctx, "DeleteAuth0User success", zap.Any("DeleteUserFromOrg", deleteEmail))
	}
	// 4、 delete dbuser
	if roleBindingUser.Status == string(entity.Active) {
		err = DeleteDBUserFormOrg(ctx, userId, orgId, userId, deleteEmail)
		tilog.Info(ctx, "create DeleteUser command result", zap.Any("success", err == nil), zap.Any("email", deleteEmail))
	}

	//5、 notice to jsm
	go noticeToJsmWhenRemoveUserFromOrg(context.Background(), userId, orgId)

	//6、 publish event
	go func() {
		_ = userevent.PublishRemoveUserFromOrgEvent(ctx, orgId, userId)
		_ = publish.PublishDataChangeEventWithEntData(context.Background(), publish.BizEventUserRemovedFromOrg, userEnt, nil, orgEnt, nil)
	}()

	//7. remove user's session
	err = session.GetUserSessionSvc().RevokeLoginSessionByEmail(ctx, deleteEmail)
	if err != nil {
		tilog.Warn(ctx, "RemoveUserFromOrg RevokeLoginSessionByEmail error", zap.Any("deleteEmail", deleteEmail), zap.Any("orgId", orgId), zap.Any("err", err))
	}

	tilog.Info(ctx, "DeleteUserFromOrg success", zap.Any("DeleteUser", deleteEmail))
	return nil
}

// delete auth0 user
func DeleteAuth0User(ctx context.Context, email string) error {
	users, err := singleton.GetAuth0UserManagement().ListByEmail(email)
	if err != nil {
		tilog.Info(ctx, "Auth0UserManagement.ListByEmail err,"+fmt.Sprintf("%s, %s", email, err.Error()))
		return nil
	}
	for _, u := range users {
		if err := singleton.GetAuth0UserManagement().Delete(*u.ID); err != nil {
			tilog.Error(ctx, "Auth0UserManagement.Delete(*u.ID) err"+err.Error())
		}
		tilog.Info(ctx, "Auth0UserManagement.Delete(*u.ID) ", zap.Any("result", err == nil), zap.Any("email", email), zap.Any("auth0.userId", u.ID))
	}
	return nil
}

func (s *UserSvc) GetRoleBindingUser(ctx context.Context, scopeType entity.ScopeType, scopeId, userId uint64) (userDao.RoleBindingUser, error) {
	f := userDao.RoleBindingUserFilter{
		RoleBindingStatus: []string{string(entity.Active), entity.RoleBindingStatusInactive},
		UserStatus:        []string{string(entity.UserStatusActive), string(entity.UserStatusPending)},
		ScopeId:           scopeId,
		ScopeType:         scopeType,
		UserId:            userId,
	}
	var respUser userDao.RoleBindingUser

	tilog.Info(ctx, "GetRoleUser", zap.Any("RoleBindingUserFilter", f))

	userList, err := s.ListRoleBindingUsers(ctx, f)

	if err != nil {
		tilog.Error(ctx, "ListRoleBindingUsers", zap.Error(err))
		return respUser, err
	}

	if len(userList) == 1 {
		respUser = userList[0]
		tilog.Info(ctx, "GetRoleBindingUser", zap.Any("respUser", respUser))

	}
	return respUser, nil
}
func DeleteDBUserFormOrg(ctx context.Context, userId, orgId, operatorId uint64, email string) error {

	tx := database.DBConn.DB.WithContext(ctx)
	err := dao.GetDaoHub().DBUser.CreateCommandUserAction(ctx, tx,
		operatorId, userId, email, orgId, 0, entity.CommandStatusWaiting, entity.CommandActionDelete, entity.RoleBindingTargetUser, entity.RoleBindingTargetUser)
	// delete dbuser
	tilog.Info(ctx, "DeleteDBUserOfOrg", zap.Any("userId", userId), zap.Any("err", err))
	return err
}

func (s *UserSvc) AddUserToOrg(ctx context.Context, req AddUserToOrgReq) error {
	// check req.orgRole
	if err := utils.IsValidUserOrgRole(req.OrgRole); err != nil {

		tilog.Info(ctx, "AddUserToOrg ", zap.Any("err", err))
		return err
	}
	// 1、check user  status in org
	roleBindingUser, err := s.GetRoleBindingUser(ctx, entity.RoleBindingScopeOrg, req.OrgId, req.UserId)
	if err != nil {
		return err
	}
	if roleBindingUser.Status == string(entity.Active) || roleBindingUser.Status == string(entity.Pending) {
		return errs.NewBadRequest("user already in the org", nil)
	}
	// 2、check user  status
	user, err := s.GetUserByID(ctx, req.UserId)
	if err != nil {
		return err
	}

	if user.Status != entity.UserStatusActive {
		return errs.NewBadRequest("user status is not active in the org", nil)
	}

	// 3、Add user in rolebinding
	tx := database.DBConn.DB.WithContext(ctx)
	r, err := dao.GetDaoHub().Role.AddOrgRoleForUser(ctx, tx, user.ID.Uint64(), req.OrgId, req.OrgRole)
	if err != nil {
		return err
	}
	// 4 、create  dbuser in cluster
	err = AddDBUserToOrg(ctx, req.UserId, req.OrgId, req.UserId, user.Email)
	tilog.Info(ctx, "create DeleteUser command result", zap.Any("success", err == nil), zap.Any("email", user.Email))

	//5、 notice to jsm
	go noticeToJsmWhenAddUserToOrg(context.Background(), req.UserId, req.OrgId, req.OrgRole)

	//6、 publish event
	go func() {
		_ = userevent.PublishAddUserToOrgEvent(ctx, req.OrgId, req.UserId, req.OrgRole)
	}()
	tilog.Info(ctx, "AddUserToOrg success", zap.Any("roleBinding", r))
	return nil
}
func AddDBUserToOrg(ctx context.Context, userId, orgId, operatorId uint64, email string) error {

	tx := database.DBConn.DB.WithContext(ctx)
	err := dao.GetDaoHub().DBUser.CreateCommandUserAction(ctx, tx,
		operatorId, userId, email, orgId, 0, entity.CommandStatusWaiting, entity.CommandActionCreate, entity.RoleBindingTargetUser, entity.RoleBindingTargetUser)
	// delete dbuser
	tilog.Info(ctx, "DeleteDBUserOfOrg", zap.Any("userId", userId), zap.Any("err", err))
	return err
}

func (s *UserSvc) DeleteUserFromAllOrg(ctx context.Context, userId uint64) error {
	//tx := database.DBConn.DB.WithContext(ctx)
	err := database.Transaction(func(tx *gorm.DB) error {
		orgs, err := dao.GetDaoHub().Role.GetUserOrgIds(ctx, tx, userId, []string{entity.RoleBindingStatusActive, entity.RoleBindingStatusInactive})
		if err != nil {
			tilog.Error(ctx, "ListOrgsByUserID error", zap.Any("err", err))
			return err
		}
		for _, orgId := range orgs {

			err := s.RemoveUserFromOrg(ctx, userId, userId, orgId)
			if err != nil {
				tilog.Error(ctx, "DeleteUserFromOrg error", zap.Any("err", err))
				return err
			}
			tilog.Info(ctx, "DeleteUserFromOrg success", zap.Any("orgId", orgId))

		}
		return nil
	})

	if err != nil {
		tilog.Error(ctx, "DeleteUserFromAllOrg", zap.Error(err))
	}
	tilog.Info(ctx, "DeleteUserFromAllOrg success", zap.Any("userId", userId))
	return nil

}
