package cmd

import (
	"fmt"
	"os"

	"github.com/urfave/cli/v2"

	clis "github.com/mayswind/ezbookkeeping/pkg/cli"
	"github.com/mayswind/ezbookkeeping/pkg/core"
	"github.com/mayswind/ezbookkeeping/pkg/errs"
	"github.com/mayswind/ezbookkeeping/pkg/log"
	"github.com/mayswind/ezbookkeeping/pkg/models"
	"github.com/mayswind/ezbookkeeping/pkg/utils"
)

// UserData represents the data command
var UserData = &cli.Command{
	Name:  "userdata",
	Usage: "ezBookkeeping user data maintenance",
	Subcommands: []*cli.Command{
		{
			Name:   "user-add",
			Usage:  "Add new user",
			Action: bindAction(addNewUser),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:     "username",
					Aliases:  []string{"n"},
					Required: true,
					Usage:    "New user name",
				},
				&cli.StringFlag{
					Name:     "email",
					Aliases:  []string{"m"},
					Required: true,
					Usage:    "New user email",
				},
				&cli.StringFlag{
					Name:     "nickname",
					Aliases:  []string{"i"},
					Required: true,
					Usage:    "New user nickname",
				},
				&cli.StringFlag{
					Name:     "password",
					Aliases:  []string{"p"},
					Required: true,
					Usage:    "New user password",
				},
				&cli.StringFlag{
					Name:     "default-currency",
					Aliases:  []string{"c"},
					Required: true,
					Usage:    "New user default currency",
				},
			},
		},
		{
			Name:   "user-get",
			Usage:  "Get specified user info",
			Action: bindAction(getUserInfo),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:     "username",
					Aliases:  []string{"n"},
					Required: true,
					Usage:    "Specific user name",
				},
			},
		},
		{
			Name:   "user-modify-password",
			Usage:  "Modify user password",
			Action: bindAction(modifyUserPassword),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:     "username",
					Aliases:  []string{"n"},
					Required: true,
					Usage:    "Specific user name",
				},
				&cli.StringFlag{
					Name:     "password",
					Aliases:  []string{"p"},
					Required: true,
					Usage:    "User new password",
				},
			},
		},
		{
			Name:   "user-enable",
			Usage:  "Enable specified user",
			Action: bindAction(enableUser),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:     "username",
					Aliases:  []string{"n"},
					Required: true,
					Usage:    "Specific user name",
				},
			},
		},
		{
			Name:   "user-disable",
			Usage:  "Disable specified user",
			Action: bindAction(disableUser),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:     "username",
					Aliases:  []string{"n"},
					Required: true,
					Usage:    "Specific user name",
				},
			},
		},
		{
			Name:   "user-resend-verify-email",
			Usage:  "Resend user verify email",
			Action: bindAction(resendUserVerifyEmail),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:     "username",
					Aliases:  []string{"n"},
					Required: true,
					Usage:    "Specific user name",
				},
			},
		},
		{
			Name:   "user-set-email-verified",
			Usage:  "Set user email address verified",
			Action: bindAction(setUserEmailVerified),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:     "username",
					Aliases:  []string{"n"},
					Required: true,
					Usage:    "Specific user name",
				},
			},
		},
		{
			Name:   "user-set-email-unverified",
			Usage:  "Set user email address unverified",
			Action: bindAction(setUserEmailUnverified),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:     "username",
					Aliases:  []string{"n"},
					Required: true,
					Usage:    "Specific user name",
				},
			},
		},
		{
			Name:   "user-delete",
			Usage:  "Delete specified user",
			Action: bindAction(deleteUser),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:     "username",
					Aliases:  []string{"n"},
					Required: true,
					Usage:    "Specific user name",
				},
			},
		},
		{
			Name:   "user-2fa-disable",
			Usage:  "Disable user 2fa setting",
			Action: bindAction(disableUser2FA),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:     "username",
					Aliases:  []string{"n"},
					Required: true,
					Usage:    "Specific user name",
				},
			},
		},
		{
			Name:   "user-session-list",
			Usage:  "List all user sessions",
			Action: bindAction(listUserTokens),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:     "username",
					Aliases:  []string{"n"},
					Required: true,
					Usage:    "Specific user name",
				},
			},
		},
		{
			Name:   "user-session-clear",
			Usage:  "Clear user all sessions",
			Action: bindAction(clearUserTokens),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:     "username",
					Aliases:  []string{"n"},
					Required: true,
					Usage:    "Specific user name",
				},
			},
		},
		{
			Name:   "send-password-reset-mail",
			Usage:  "Send password reset mail",
			Action: bindAction(sendPasswordResetMail),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:     "username",
					Aliases:  []string{"n"},
					Required: true,
					Usage:    "Specific user name",
				},
			},
		},
		{
			Name:   "transaction-check",
			Usage:  "Check whether user all transactions and accounts are correct",
			Action: bindAction(checkUserTransactionAndAccount),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:     "username",
					Aliases:  []string{"n"},
					Required: true,
					Usage:    "Specific user name",
				},
			},
		},
		{
			Name:   "transaction-tag-index-fix-transaction-time",
			Usage:  "Fix the transaction tag index data which does not have transaction time",
			Action: bindAction(fixTransactionTagIndexNotHaveTransactionTime),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:     "username",
					Aliases:  []string{"n"},
					Required: true,
					Usage:    "Specific user name",
				},
			},
		},
		{
			Name:   "transaction-import",
			Usage:  "Import transactions to specified user",
			Action: bindAction(importUserTransaction),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:     "username",
					Aliases:  []string{"n"},
					Required: true,
					Usage:    "Specific user name",
				},
				&cli.StringFlag{
					Name:     "file",
					Aliases:  []string{"f"},
					Required: true,
					Usage:    "Specific import file path (e.g. transaction.csv)",
				},
				&cli.StringFlag{
					Name:     "type",
					Aliases:  []string{"t"},
					Required: true,
					Usage:    "Import file type (supports \"ezbookkeeping_csv\", \"ezbookkeeping_tsv\")",
				},
			},
		},
		{
			Name:   "transaction-export",
			Usage:  "Export user all transactions to file",
			Action: bindAction(exportUserTransaction),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:     "username",
					Aliases:  []string{"n"},
					Required: true,
					Usage:    "Specific user name",
				},
				&cli.StringFlag{
					Name:     "file",
					Aliases:  []string{"f"},
					Required: true,
					Usage:    "Specific exported file path (e.g. transaction.csv)",
				},
				&cli.StringFlag{
					Name:     "type",
					Aliases:  []string{"t"},
					Required: false,
					Usage:    "Export file type, support csv or tsv, default is csv",
				},
			},
		},
	},
}

func addNewUser(c *core.CliContext) error {
	_, err := initializeSystem(c)

	if err != nil {
		return err
	}

	username := c.String("username")
	email := c.String("email")
	nickname := c.String("nickname")
	password := c.String("password")
	defaultCurrency := c.String("default-currency")

	user, err := clis.UserData.AddNewUser(c, username, email, nickname, password, defaultCurrency)

	if err != nil {
		log.CliErrorf(c, "[user_data.addNewUser] error occurs when adding new user")
		return err
	}

	printUserInfo(user)

	return nil
}

func getUserInfo(c *core.CliContext) error {
	_, err := initializeSystem(c)

	if err != nil {
		return err
	}

	username := c.String("username")
	user, err := clis.UserData.GetUserByUsername(c, username)

	if err != nil {
		log.CliErrorf(c, "[user_data.getUserInfo] error occurs when getting user data")
		return err
	}

	printUserInfo(user)

	return nil
}

func modifyUserPassword(c *core.CliContext) error {
	_, err := initializeSystem(c)

	if err != nil {
		return err
	}

	username := c.String("username")
	password := c.String("password")
	err = clis.UserData.ModifyUserPassword(c, username, password)

	if err != nil {
		log.CliErrorf(c, "[user_data.modifyUserPassword] error occurs when modifying user password")
		return err
	}

	log.CliInfof(c, "[user_data.modifyUserPassword] password of user \"%s\" has been changed", username)

	return nil
}

func sendPasswordResetMail(c *core.CliContext) error {
	_, err := initializeSystem(c)

	if err != nil {
		return err
	}

	username := c.String("username")
	err = clis.UserData.SendPasswordResetMail(c, username)

	if err != nil {
		log.CliErrorf(c, "[user_data.sendPasswordResetMail] error occurs when sending password reset email")
		return err
	}

	log.CliInfof(c, "[user_data.sendPasswordResetMail] a password reset email for user \"%s\" has been sent", username)

	return nil
}

func enableUser(c *core.CliContext) error {
	_, err := initializeSystem(c)

	if err != nil {
		return err
	}

	username := c.String("username")
	err = clis.UserData.EnableUser(c, username)

	if err != nil {
		log.CliErrorf(c, "[user_data.enableUser] error occurs when setting user enabled")
		return err
	}

	log.CliInfof(c, "[user_data.enableUser] user \"%s\" has been set enabled", username)

	return nil
}

func disableUser(c *core.CliContext) error {
	_, err := initializeSystem(c)

	if err != nil {
		return err
	}

	username := c.String("username")
	err = clis.UserData.DisableUser(c, username)

	if err != nil {
		log.CliErrorf(c, "[user_data.disableUser] error occurs when setting user disabled")
		return err
	}

	log.CliInfof(c, "[user_data.disableUser] user \"%s\" has been set disabled", username)

	return nil
}

func resendUserVerifyEmail(c *core.CliContext) error {
	_, err := initializeSystem(c)

	if err != nil {
		return err
	}

	username := c.String("username")
	err = clis.UserData.ResendVerifyEmail(c, username)

	if err != nil {
		log.CliErrorf(c, "[user_data.resendUserVerifyEmail] error occurs when resending user verify email")
		return err
	}

	log.CliInfof(c, "[user_data.resendUserVerifyEmail] verify email for user \"%s\" has been resent", username)

	return nil
}

func setUserEmailVerified(c *core.CliContext) error {
	_, err := initializeSystem(c)

	if err != nil {
		return err
	}

	username := c.String("username")
	err = clis.UserData.SetUserEmailVerified(c, username)

	if err != nil {
		log.CliErrorf(c, "[user_data.setUserEmailVerified] error occurs when setting user email address verified")
		return err
	}

	log.CliInfof(c, "[user_data.setUserEmailVerified] user \"%s\" email address has been set verified", username)

	return nil
}

func setUserEmailUnverified(c *core.CliContext) error {
	_, err := initializeSystem(c)

	if err != nil {
		return err
	}

	username := c.String("username")
	err = clis.UserData.SetUserEmailUnverified(c, username)

	if err != nil {
		log.CliErrorf(c, "[user_data.setUserEmailUnverified] error occurs when setting user email address unverified")
		return err
	}

	log.CliInfof(c, "[user_data.setUserEmailUnverified] user \"%s\" email address has been set unverified", username)

	return nil
}

func deleteUser(c *core.CliContext) error {
	_, err := initializeSystem(c)

	if err != nil {
		return err
	}

	username := c.String("username")
	err = clis.UserData.DeleteUser(c, username)

	if err != nil {
		log.CliErrorf(c, "[user_data.deleteUser] error occurs when deleting user")
		return err
	}

	log.CliInfof(c, "[user_data.deleteUser] user \"%s\" has been deleted", username)

	return nil
}

func disableUser2FA(c *core.CliContext) error {
	_, err := initializeSystem(c)

	if err != nil {
		return err
	}

	username := c.String("username")
	err = clis.UserData.DisableUserTwoFactorAuthorization(c, username)

	if err != nil {
		log.CliErrorf(c, "[user_data.disableUser2FA] error occurs when disabling user two-factor authorization")
		return err
	}

	log.CliInfof(c, "[user_data.disableUser2FA] two-factor authorization of user \"%s\" has been disabled", username)

	return nil
}

func listUserTokens(c *core.CliContext) error {
	_, err := initializeSystem(c)

	if err != nil {
		return err
	}

	username := c.String("username")
	tokens, err := clis.UserData.ListUserTokens(c, username)

	if err != nil {
		log.CliErrorf(c, "[user_data.listUserTokens] error occurs when getting user tokens")
		return err
	}

	for i := 0; i < len(tokens); i++ {
		printTokenInfo(tokens[i])

		if i < len(tokens)-1 {
			fmt.Printf("---\n")
		}
	}

	return nil
}

func clearUserTokens(c *core.CliContext) error {
	_, err := initializeSystem(c)

	if err != nil {
		return err
	}

	username := c.String("username")
	err = clis.UserData.ClearUserTokens(c, username)

	if err != nil {
		log.CliErrorf(c, "[user_data.clearUserTokens] error occurs when clearing user tokens")
		return err
	}

	log.CliInfof(c, "[user_data.clearUserTokens] all tokens of user \"%s\" has been cleared", username)

	return nil
}

func checkUserTransactionAndAccount(c *core.CliContext) error {
	_, err := initializeSystem(c)

	if err != nil {
		return err
	}

	username := c.String("username")

	log.CliInfof(c, "[user_data.checkUserTransactionAndAccount] starting checking user \"%s\" data", username)

	_, err = clis.UserData.CheckTransactionAndAccount(c, username)

	if err != nil {
		log.CliErrorf(c, "[user_data.checkUserTransactionAndAccount] error occurs when checking user data")
		return err
	}

	log.CliInfof(c, "[user_data.checkUserTransactionAndAccount] user transactions and accounts data has been checked successfully, there is no problem with user data")

	return nil
}

func fixTransactionTagIndexNotHaveTransactionTime(c *core.CliContext) error {
	_, err := initializeSystem(c)

	if err != nil {
		return err
	}

	username := c.String("username")

	log.CliInfof(c, "[user_data.fixTransactionTagIndexNotHaveTransactionTime] starting fixing user \"%s\" transaction tag index data", username)

	_, err = clis.UserData.FixTransactionTagIndexWithTransactionTime(c, username)

	if err != nil {
		log.CliErrorf(c, "[user_data.fixTransactionTagIndexNotHaveTransactionTime] error occurs when fixing user data")
		return err
	}

	log.CliInfof(c, "[user_data.fixTransactionTagIndexNotHaveTransactionTime] user transaction tag index data has been fixed successfully")

	return nil
}

func exportUserTransaction(c *core.CliContext) error {
	_, err := initializeSystem(c)

	if err != nil {
		return err
	}

	username := c.String("username")
	filePath := c.String("file")
	fileType := c.String("type")

	if fileType != "" && fileType != "csv" && fileType != "tsv" {
		log.CliErrorf(c, "[user_data.exportUserTransaction] export file type is not supported")
		return errs.ErrNotSupported
	}

	if filePath == "" {
		log.CliErrorf(c, "[user_data.exportUserTransaction] export file path is unspecified")
		return os.ErrNotExist
	}

	fileExists, err := utils.IsExists(filePath)

	if fileExists {
		log.CliErrorf(c, "[user_data.exportUserTransaction] specified file path already exists")
		return os.ErrExist
	}

	log.CliInfof(c, "[user_data.exportUserTransaction] starting exporting user \"%s\" data", username)

	content, err := clis.UserData.ExportTransaction(c, username, fileType)

	if err != nil {
		log.CliErrorf(c, "[user_data.exportUserTransaction] error occurs when exporting user data")
		return err
	}

	err = utils.WriteFile(filePath, content)

	if err != nil {
		log.CliErrorf(c, "[user_data.exportUserTransaction] failed to write to %s", filePath)
		return err
	}

	log.CliInfof(c, "[user_data.exportUserTransaction] user transactions have been exported to %s", filePath)

	return nil
}

func importUserTransaction(c *core.CliContext) error {
	_, err := initializeSystem(c)

	if err != nil {
		return err
	}

	username := c.String("username")
	filePath := c.String("file")
	filetype := c.String("type")

	if filePath == "" {
		log.CliErrorf(c, "[user_data.importUserTransaction] import file path is not specified")
		return os.ErrNotExist
	}

	fileExists, err := utils.IsExists(filePath)

	if !fileExists {
		log.CliErrorf(c, "[user_data.importUserTransaction] import file does not exist")
		return os.ErrExist
	}

	if filetype != "ezbookkeeping_csv" && filetype != "ezbookkeeping_tsv" {
		log.CliErrorf(c, "[user_data.importUserTransaction] unknown file type \"%s\"", filetype)
		return errs.ErrImportFileTypeNotSupported
	}

	data, err := os.ReadFile(filePath)

	if err != nil {
		log.CliErrorf(c, "[user_data.importUserTransaction] failed to load import file")
		return err
	}

	log.CliInfof(c, "[user_data.importUserTransaction] start importing transactions to user \"%s\"", username)

	err = clis.UserData.ImportTransaction(c, username, filetype, data)

	if err != nil {
		log.CliErrorf(c, "[user_data.importUserTransaction] error occurs when importing user data")
		return err
	}

	log.CliInfof(c, "[user_data.importUserTransaction] transactions have been imported to user \"%s\"", username)

	return nil
}

func printUserInfo(user *models.User) {
	fmt.Printf("[Uid] %d\n", user.Uid)
	fmt.Printf("[Username] %s\n", user.Username)
	fmt.Printf("[Email] %s\n", user.Email)
	fmt.Printf("[Nickname] %s\n", user.Nickname)
	fmt.Printf("[Password] %s\n", user.Password)
	fmt.Printf("[Salt] %s\n", user.Salt)
	fmt.Printf("[DefaultAccountId] %d\n", user.DefaultAccountId)
	fmt.Printf("[TransactionEditScope] %s (%d)\n", user.TransactionEditScope, user.TransactionEditScope)
	fmt.Printf("[Language] %s\n", user.Language)
	fmt.Printf("[DefaultCurrency] %s\n", user.DefaultCurrency)
	fmt.Printf("[FirstDayOfWeek] %s (%d)\n", user.FirstDayOfWeek, user.FirstDayOfWeek)
	fmt.Printf("[LongDateFormat] %s (%d)\n", user.LongDateFormat, user.LongDateFormat)
	fmt.Printf("[ShortDateFormat] %s (%d)\n", user.ShortDateFormat, user.ShortDateFormat)
	fmt.Printf("[LongTimeFormat] %s (%d)\n", user.LongTimeFormat, user.LongTimeFormat)
	fmt.Printf("[ShortTimeFormat] %s (%d)\n", user.ShortTimeFormat, user.ShortTimeFormat)
	fmt.Printf("[DecimalSeparator] %s (%d)\n", user.DecimalSeparator, user.DecimalSeparator)
	fmt.Printf("[DigitGroupingSymbol] %s (%d)\n", user.DigitGroupingSymbol, user.DigitGroupingSymbol)
	fmt.Printf("[DigitGrouping] %s (%d)\n", user.DigitGrouping, user.DigitGrouping)
	fmt.Printf("[CurrencyDisplayType] %s (%d)\n", user.CurrencyDisplayType, user.CurrencyDisplayType)
	fmt.Printf("[ExpenseAmountColor] %s (%d)\n", user.ExpenseAmountColor, user.ExpenseAmountColor)
	fmt.Printf("[IncomeAmountColor] %s (%d)\n", user.IncomeAmountColor, user.IncomeAmountColor)
	fmt.Printf("[Deleted] %t\n", user.Deleted)
	fmt.Printf("[EmailVerified] %t\n", user.EmailVerified)
	fmt.Printf("[CreatedAt] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(user.CreatedUnixTime), user.CreatedUnixTime)

	if user.UpdatedUnixTime > 0 {
		fmt.Printf("[UpdatedAt] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(user.UpdatedUnixTime), user.UpdatedUnixTime)
	}

	if user.DeletedUnixTime > 0 {
		fmt.Printf("[DeletedAt] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(user.DeletedUnixTime), user.DeletedUnixTime)
	}

	if user.LastLoginUnixTime > 0 {
		fmt.Printf("[LastLoginAt] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(user.LastLoginUnixTime), user.LastLoginUnixTime)
	}
}

func printTokenInfo(token *models.TokenRecord) {
	fmt.Printf("[CreatedAt] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(token.CreatedUnixTime), token.CreatedUnixTime)
	fmt.Printf("[ExpiredAt] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(token.ExpiredUnixTime), token.ExpiredUnixTime)
	fmt.Printf("[LastSeen] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(token.LastSeenUnixTime), token.LastSeenUnixTime)
	fmt.Printf("[UserAgent] %s\n", token.UserAgent)
}
