package cmd

import (
	"fmt"

	"git.maurice.fr/thomas/mailout/pkg/database"
	"git.maurice.fr/thomas/mailout/pkg/models"
	"github.com/pterm/pterm"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"golang.org/x/crypto/bcrypt"
)

var (
	flagUserActive bool
)

var UserCmd = &cobra.Command{
	Use:   "user",
	Short: "manages users",
}

var UserAddCmd = &cobra.Command{
	Use:   "add [user_address] [password]",
	Short: "adds a user",
	Args:  cobra.ExactArgs(2),
	Run: func(cmd *cobra.Command, args []string) {
		db, err := database.NewDB(cfg)
		if err != nil {
			logrus.WithError(err).Fatal("could not connect to the database")
		}

		username, domain, err := splitUser(args[0])
		if err != nil {
			logrus.WithError(err).Fatal("could not parse user")
		}

		qRes := make([]models.User, 0)
		var user models.User
		userExists := false
		logger := logrus.WithField("user", args[0])

		err = db.Where(&models.User{Domain: domain, Username: username}).Find(&qRes).Error
		if err != nil {
			logger.WithError(err).Fatal("could not check user's existence")
		}

		if len(qRes) == 0 {
			userExists = false
		} else {
			logger.Warning("user already exists, it's password will be updated")
			user = qRes[0]
			userExists = true
		}

		passwordHash, err := bcrypt.GenerateFromPassword([]byte(args[1]), bcrypt.DefaultCost)
		if err != nil {
			logger.WithError(err).Fatal("could not compute user password hash")
		}

		if userExists {
			err = db.Model(&models.User{}).Where(&user).Update("password", fmt.Sprintf("{BLF-CRYPT}%s", string(passwordHash))).Error
			if err != nil {
				logger.WithError(err).Fatal("could not update user password")
			}
			logger.Infof("updated user %s", user.ID)
			return
		}

		user = models.User{
			Username: username,
			Domain:   domain,
			Active:   flagUserActive,
			Password: fmt.Sprintf("{BLF-CRYPT}%s", string(passwordHash)),
		}
		err = db.Save(&user).Error

		if err != nil {
			logger.WithError(err).Fatal("could not create user")
		}

		logger.Infof("created user %s", user.ID)
	},
}

var UserListCmd = &cobra.Command{
	Use:   "list",
	Short: "list users",
	Run: func(cmd *cobra.Command, args []string) {
		db, err := database.NewDB(cfg)
		if err != nil {
			logrus.WithError(err).Fatal("could not connect to the database")
		}

		qRes := make([]models.User, 0)

		err = db.Find(&qRes).Error
		if err != nil {
			logrus.WithError(err).Fatal("could not list users")
		}

		tData := pterm.TableData{
			{"id", "username", "domain", "active", "uid", "gid", "created_at", "updated_at"},
		}
		for _, u := range qRes {
			tData = append(tData, []string{u.ID.String(), u.Username, u.Domain, fmt.Sprintf("%v", u.Active), fmt.Sprintf("%d", u.UID), fmt.Sprintf("%d", u.GID), u.CreatedAt.String(), u.UpdatedAt.String()})
		}

		pterm.DefaultTable.WithHasHeader().WithData(tData).Render()
	},
}

var UserDeleteCmd = &cobra.Command{
	Use:   "delete",
	Short: "delete users",
	Args:  cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		db, err := database.NewDB(cfg)
		if err != nil {
			logrus.WithError(err).Fatal("could not connect to the database")
		}

		userQuery, err := buildUserQuery(args[0])
		if err != nil {
			logrus.WithError(err).Fatal("unable to determine user")
		}

		err = db.Where(&userQuery).Delete(&models.User{}).Error
		if err != nil {
			logrus.WithError(err).Fatal("could not delete user")
		}

		logrus.Infof("deleted user %s if it existed", args[0])
	},
}

var UserActivateCmd = &cobra.Command{
	Use:   "activate",
	Short: "activates a user",
	Args:  cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		db, err := database.NewDB(cfg)
		if err != nil {
			logrus.WithError(err).Fatal("could not connect to the database")
		}

		userQuery, err := buildUserQuery(args[0])
		if err != nil {
			logrus.WithError(err).Fatal("unable to determine user")
		}

		err = db.Model(&models.User{}).Where(&userQuery).Update("active", true).Error
		if err != nil {
			logrus.WithError(err).Fatal("could not activate user")
		}

		logrus.Infof("activated user %s", args[0])
	},
}

var UserDeactivateCmd = &cobra.Command{
	Use:   "deactivate",
	Short: "deactivates a user",
	Args:  cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		db, err := database.NewDB(cfg)
		if err != nil {
			logrus.WithError(err).Fatal("could not connect to the database")
		}

		userQuery, err := buildUserQuery(args[0])
		if err != nil {
			logrus.WithError(err).Fatal("unable to determine user")
		}

		err = db.Model(&models.User{}).Where(&userQuery).Update("active", false).Error
		if err != nil {
			logrus.WithError(err).Fatal("could not deactivate user")
		}

		logrus.Infof("deactivated user %s", args[0])
	},
}

func InitUserCmd() {
	UserCmd.AddCommand(UserAddCmd)
	UserCmd.AddCommand(UserListCmd)
	UserCmd.AddCommand(UserDeleteCmd)
	UserCmd.AddCommand(UserActivateCmd)
	UserCmd.AddCommand(UserDeactivateCmd)

	UserAddCmd.PersistentFlags().BoolVarP(&flagUserActive, "active", "a", true, "whether or not the created user is active")
}