// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main

import (
	"errors"
	"fmt"

	"github.com/mattermost/platform/api"
	"github.com/mattermost/platform/einterfaces"
	"github.com/mattermost/platform/model"
	"github.com/mattermost/platform/utils"
	"github.com/spf13/cobra"
)

var userCmd = &cobra.Command{
	Use:   "user",
	Short: "Management of users",
}

var userActivateCmd = &cobra.Command{
	Use:   "activate [emails, usernames, userIds]",
	Short: "Activate users",
	Long:  "Activate users that have been deactivated.",
	Example: `  user activate user@example.com
  user activate username`,
	RunE: userActivateCmdF,
}

var userDeactivateCmd = &cobra.Command{
	Use:   "deactivate [emails, usernames, userIds]",
	Short: "Deactivate users",
	Long:  "Deactivate users. Deactivated users are immediately logged out of all sessions and are unable to log back in.",
	Example: `  user deactivate user@example.com
  user deactivate username`,
	RunE: userDeactivateCmdF,
}

var userCreateCmd = &cobra.Command{
	Use:   "create",
	Short: "Create a user",
	Long:  "Create a user",
	Example: `  user create --email user@example.com --username userexample --password Password1 
  user create --firstname Joe --system_admin --email joe@example.com --username joe --password Password1`,
	RunE: userCreateCmdF,
}

var userInviteCmd = &cobra.Command{
	Use:   "invite [email] [teams]",
	Short: "Send user an email invite to a team.",
	Long: `Send user an email invite to a team.
You can invite a user to multiple teams by listing them.
You can specify teams by name or ID.`,
	Example: `  user invite user@example.com myteam
  user invite user@example.com myteam1 myteam2`,
	RunE: userInviteCmdF,
}

var resetUserPasswordCmd = &cobra.Command{
	Use:     "password [user] [password]",
	Short:   "Set a user's password",
	Long:    "Set a user's password",
	Example: "  user password user@example.com Password1",
	RunE:    resetUserPasswordCmdF,
}

var resetUserMfaCmd = &cobra.Command{
	Use:   "resetmfa [users]",
	Short: "Turn off MFA",
	Long: `Turn off multi-factor authentication for a user. 
If MFA enforcement is enabled, the user will be forced to re-enable MFA as soon as they login.`,
	Example: "  user resetmfa user@example.com",
	RunE:    resetUserMfaCmdF,
}

var deleteUserCmd = &cobra.Command{
	Use:     "delete [users]",
	Short:   "Delete users and all posts",
	Long:    "Permanently delete user and all related information including posts.",
	Example: "  user delete user@example.com",
	RunE:    deleteUserCmdF,
}

var deleteAllUsersCmd = &cobra.Command{
	Use:     "deleteall",
	Short:   "Delete all users and all posts",
	Long:    "Permanently delete all users and all related information including posts.",
	Example: "  user deleteall",
	RunE:    deleteUserCmdF,
}

var migrateAuthCmd = &cobra.Command{
	Use:   "migrate_auth [from_auth] [to_auth] [match_field]",
	Short: "Mass migrate user accounts authentication type",
	Long: `Migrates accounts from one authentication provider to another. For example, you can upgrade your authentication provider from email to ldap.

from_auth: 
	The authentication service to migrate users accounts from.
	Supported options: email, gitlab, saml. 

to_auth:
	The authentication service to migrate users to.
	Supported options: ldap. 

match_field:
	The field that is guaranteed to be the same in both authentication services. For example, if the users emails are consistent set to email.
	Supported options: email, username.

Will display any accounts that are not migrated successfully.`,
	Example: "  user migrate_auth email ladp email",
	RunE:    migrateAuthCmdF,
}

var verifyUserCmd = &cobra.Command{
	Use:     "verify [users]",
	Short:   "Verify email of users",
	Long:    "Verify the emails of some users.",
	Example: "  user verify user1",
	RunE:    verifyUserCmdF,
}

func init() {
	userCreateCmd.Flags().String("username", "", "Username")
	userCreateCmd.Flags().String("email", "", "Email")
	userCreateCmd.Flags().String("password", "", "Password")
	userCreateCmd.Flags().String("nickname", "", "Nickname")
	userCreateCmd.Flags().String("firstname", "", "First Name")
	userCreateCmd.Flags().String("lastname", "", "Last Name")
	userCreateCmd.Flags().String("locale", "", "Locale (ex: en, fr)")
	userCreateCmd.Flags().Bool("system_admin", false, "Make the user a system administrator")

	deleteUserCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the user and a DB backup has been performed.")

	deleteAllUsersCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the user and a DB backup has been performed.")

	userCmd.AddCommand(
		userActivateCmd,
		userDeactivateCmd,
		userCreateCmd,
		userInviteCmd,
		resetUserPasswordCmd,
		resetUserMfaCmd,
		deleteUserCmd,
		deleteAllUsersCmd,
		migrateAuthCmd,
		verifyUserCmd,
	)
}

func userActivateCmdF(cmd *cobra.Command, args []string) error {
	initDBCommandContextCobra(cmd)

	if len(args) < 1 {
		return errors.New("Enter user(s) to activate.")
	}

	changeUsersActiveStatus(args, true)
	return nil
}

func changeUsersActiveStatus(userArgs []string, active bool) {
	users := getUsersFromUserArgs(userArgs)
	for i, user := range users {
		changeUserActiveStatus(user, userArgs[i], active)
	}
}

func changeUserActiveStatus(user *model.User, userArg string, activate bool) {
	if user == nil {
		CommandPrintErrorln("Can't find user '" + userArg + "'")
		return
	}
	if user.IsLDAPUser() {
		CommandPrintErrorln(utils.T("api.user.update_active.no_deactivate_ldap.app_error"))
		return
	}
	if _, err := api.UpdateActive(user, activate); err != nil {
		CommandPrintErrorln("Unable to change activation status of user: " + userArg)
	}
}

func userDeactivateCmdF(cmd *cobra.Command, args []string) error {
	initDBCommandContextCobra(cmd)

	if len(args) < 1 {
		return errors.New("Enter user(s) to deactivate.")
	}

	changeUsersActiveStatus(args, false)
	return nil
}

func userCreateCmdF(cmd *cobra.Command, args []string) error {
	initDBCommandContextCobra(cmd)
	username, erru := cmd.Flags().GetString("username")
	if erru != nil || username == "" {
		return errors.New("Username is required")
	}
	email, erre := cmd.Flags().GetString("email")
	if erre != nil || email == "" {
		return errors.New("Email is required")
	}
	password, errp := cmd.Flags().GetString("password")
	if errp != nil || password == "" {
		return errors.New("Password is required")
	}
	nickname, _ := cmd.Flags().GetString("nickname")
	firstname, _ := cmd.Flags().GetString("firstname")
	lastname, _ := cmd.Flags().GetString("lastname")
	locale, _ := cmd.Flags().GetString("locale")
	system_admin, _ := cmd.Flags().GetBool("system_admin")

	user := &model.User{
		Username:  username,
		Email:     email,
		Password:  password,
		Nickname:  nickname,
		FirstName: firstname,
		LastName:  lastname,
		Locale:    locale,
	}

	ruser, err := api.CreateUser(user)
	if err != nil {
		return errors.New("Unable to create user. Error: " + err.Error())
	}

	if system_admin {
		api.UpdateUserRoles(ruser, "system_user system_admin")
	}

	CommandPrettyPrintln("Created User")

	return nil
}

func userInviteCmdF(cmd *cobra.Command, args []string) error {
	initDBCommandContextCobra(cmd)
	utils.InitHTML()

	if len(args) < 2 {
		return errors.New("Not enough arguments.")
	}

	email := args[0]
	if !model.IsValidEmail(email) {
		return errors.New("Invalid email")
	}

	teams := getTeamsFromTeamArgs(args[1:])
	for i, team := range teams {
		inviteUser(email, team, args[i+1])
	}

	return nil
}

func inviteUser(email string, team *model.Team, teamArg string) {
	invites := []string{email}
	if team == nil {
		CommandPrintErrorln("Can't find team '" + teamArg + "'")
		return
	}
	api.InviteMembers(team, "Administrator", invites)
	CommandPrettyPrintln("Invites may or may not have been sent.")
}

func resetUserPasswordCmdF(cmd *cobra.Command, args []string) error {
	initDBCommandContextCobra(cmd)
	if len(args) != 2 {
		return errors.New("Incorect number of arguments.")
	}

	user := getUserFromUserArg(args[0])
	if user == nil {
		return errors.New("Unable to find user '" + args[0] + "'")
	}
	password := args[1]

	if result := <-api.Srv.Store.User().UpdatePassword(user.Id, model.HashPassword(password)); result.Err != nil {
		return result.Err
	}

	return nil
}

func resetUserMfaCmdF(cmd *cobra.Command, args []string) error {
	initDBCommandContextCobra(cmd)
	if len(args) < 1 {
		return errors.New("Enter at least one user.")
	}

	users := getUsersFromUserArgs(args)

	for i, user := range users {
		if user == nil {
			return errors.New("Unable to find user '" + args[i] + "'")
		}

		if err := api.DeactivateMfa(user.Id); err != nil {
			return err
		}
	}

	return nil
}

func deleteUserCmdF(cmd *cobra.Command, args []string) error {
	initDBCommandContextCobra(cmd)
	if len(args) < 1 {
		return errors.New("Enter at least one user.")
	}

	confirmFlag, _ := cmd.Flags().GetBool("confirm")
	if !confirmFlag {
		var confirm string
		CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ")
		fmt.Scanln(&confirm)

		if confirm != "YES" {
			return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
		}
		CommandPrettyPrintln("Are you sure you want to delete the teams specified?  All data will be permanently deleted? (YES/NO): ")
		fmt.Scanln(&confirm)
		if confirm != "YES" {
			return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
		}
	}

	users := getUsersFromUserArgs(args)

	for i, user := range users {
		if user == nil {
			return errors.New("Unable to find user '" + args[i] + "'")
		}

		if err := api.PermanentDeleteUser(user); err != nil {
			return err
		}
	}

	return nil
}

func deleteAllUsersCommandF(cmd *cobra.Command, args []string) error {
	initDBCommandContextCobra(cmd)
	if len(args) > 0 {
		return errors.New("Don't enter any agruments.")
	}

	confirmFlag, _ := cmd.Flags().GetBool("confirm")
	if !confirmFlag {
		var confirm string
		CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ")
		fmt.Scanln(&confirm)

		if confirm != "YES" {
			return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
		}
		CommandPrettyPrintln("Are you sure you want to delete the teams specified?  All data will be permanently deleted? (YES/NO): ")
		fmt.Scanln(&confirm)
		if confirm != "YES" {
			return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
		}
	}

	if err := api.PermanentDeleteAllUsers(); err != nil {
		return err
	} else {
		CommandPrettyPrintln("Sucsessfull. All users deleted.")
	}

	return nil
}

func migrateAuthCmdF(cmd *cobra.Command, args []string) error {
	initDBCommandContextCobra(cmd)
	if len(args) != 3 {
		return errors.New("Enter the correct number of arguments.")
	}

	fromAuth := args[0]
	toAuth := args[1]
	matchField := args[2]

	if len(fromAuth) == 0 || (fromAuth != "email" && fromAuth != "gitlab" && fromAuth != "saml") {
		return errors.New("Invalid from_auth argument")
	}

	if len(toAuth) == 0 || toAuth != "ldap" {
		return errors.New("Invalid to_auth argument")
	}

	// Email auth in Mattermost system is represented by ""
	if fromAuth == "email" {
		fromAuth = ""
	}

	if len(matchField) == 0 || (matchField != "email" && matchField != "username") {
		return errors.New("Invalid match_field argument")
	}

	if migrate := einterfaces.GetAccountMigrationInterface(); migrate != nil {
		if err := migrate.MigrateToLdap(fromAuth, matchField); err != nil {
			return errors.New("Error while migrating users: " + err.Error())
		} else {
			CommandPrettyPrintln("Sucessfully migrated accounts.")
		}
	}

	return nil
}

func verifyUserCmdF(cmd *cobra.Command, args []string) error {
	initDBCommandContextCobra(cmd)
	if len(args) < 1 {
		return errors.New("Enter at least one user.")
	}

	users := getUsersFromUserArgs(args)

	for i, user := range users {
		if user == nil {
			CommandPrintErrorln("Unable to find user '" + args[i] + "'")
		}
		if cresult := <-api.Srv.Store.User().VerifyEmail(user.Id); cresult.Err != nil {
			CommandPrintErrorln("Unable to verify '" + args[i] + "' email. Error: " + cresult.Err.Error())
		}
	}

	return nil
}
