package cmd

import (
	"bufio"
	"fmt"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"
	"unicode"

	"github.com/1Panel-dev/1Panel/core/cmd/server/conf"
	"github.com/1Panel-dev/1Panel/core/constant"
	"github.com/1Panel-dev/1Panel/core/global"
	"github.com/1Panel-dev/1Panel/core/i18n"
	"github.com/1Panel-dev/1Panel/core/utils/cmd"
	"github.com/1Panel-dev/1Panel/core/utils/common"
	"github.com/1Panel-dev/1Panel/core/utils/encrypt"
	upgradeUtil "github.com/1Panel-dev/1Panel/core/utils/upgrade"
	"github.com/spf13/cobra"
	"golang.org/x/term"
	"gopkg.in/yaml.v3"
	"gorm.io/gorm"
)

func init() {
	updateCmd.SetHelpFunc(func(c *cobra.Command, s []string) {
		i18n.UseI18nForCmd(language)
		loadUpdateHelper()
	})

	RootCmd.AddCommand(updateCmd)
	updateCmd.AddCommand(updateUserName)
	updateUserName.Flags().StringVar(&updateUserNameFlag, "username", "", "username")
	updateCmd.AddCommand(updatePassword)
	updatePassword.Flags().StringVar(&updatePasswordUserName, "username", "", "username")
	updateCmd.AddCommand(updatePort)

	updateCmd.AddCommand(updateVersion)
}

var updateUserNameFlag string
var updatePasswordUserName string

var updateCmd = &cobra.Command{
	Use: "update",
	RunE: func(cmd *cobra.Command, args []string) error {
		i18n.UseI18nForCmd(language)
		loadUpdateHelper()
		return nil
	},
}

var updateUserName = &cobra.Command{
	Use:   "username",
	Short: i18n.GetMsgByKeyForCmd("UpdateUser"),
	RunE: func(cmd *cobra.Command, args []string) error {
		i18n.UseI18nForCmd(language)
		if !isRoot() {
			fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl update username"}))
			return nil
		}
		if isEnterprise() && len(strings.TrimSpace(updateUserNameFlag)) == 0 {
			fmt.Println(i18n.GetMsgByKeyForCmd("UsernameNeed"))
			return nil
		}
		username()
		return nil
	},
}
var updatePassword = &cobra.Command{
	Use:   "password",
	Short: i18n.GetMsgByKeyForCmd("UpdatePassword"),
	RunE: func(cmd *cobra.Command, args []string) error {
		i18n.UseI18nForCmd(language)
		if !isRoot() {
			fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl update password"}))
			return nil
		}
		if isEnterprise() && len(strings.TrimSpace(updatePasswordUserName)) == 0 {
			fmt.Println(i18n.GetMsgByKeyForCmd("UsernameNeed"))
			return nil
		}
		password()
		return nil
	},
}

type serverConfig struct {
	Base struct {
		IsEnterprise bool `yaml:"is_enterprise"`
	} `yaml:"base"`
}

type Node struct {
	Name          string `gorm:"column:name"`
	Addr          string `gorm:"column:addr"`
	IsAutoUpgrade bool   `gorm:"column:is_auto_upgrade"`
	Status        string `gorm:"column:status"`
}

func isEnterprise() bool {
	var config serverConfig
	if err := yaml.Unmarshal(conf.AppYaml, &config); err != nil {
		return false
	}
	return config.Base.IsEnterprise
}

func updateEnterprisePassword(db *gorm.DB, username, password string) error {
	result := db.Exec("UPDATE users SET password = ? WHERE name = ?", password, username)
	if result.Error != nil {
		return result.Error
	}
	if result.RowsAffected == 0 {
		return fmt.Errorf("user %s not found", username)
	}
	return nil
}

func updateEnterpriseUserName(db *gorm.DB, username, newUsername string) error {
	result := db.Exec("UPDATE users SET name = ? WHERE name = ?", newUsername, username)
	if result.Error != nil {
		return result.Error
	}
	if result.RowsAffected == 0 {
		return fmt.Errorf("user %s not found", username)
	}
	return nil
}

var updatePort = &cobra.Command{
	Use:   "port",
	Short: i18n.GetMsgByKeyForCmd("UpdatePort"),
	RunE: func(cmd *cobra.Command, args []string) error {
		i18n.UseI18nForCmd(language)
		if !isRoot() {
			fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl update port"}))
			return nil
		}
		port()
		return nil
	},
}
var updateVersion = &cobra.Command{
	Use:  "version",
	Args: cobra.ExactArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		i18n.UseI18nForCmd(language)
		if !isRoot() {
			fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl update version"}))
			return nil
		}
		version := args[0]
		if len(version) == 0 || !strings.HasPrefix(version, "v2.") {
			fmt.Println("err version in param input")
			return nil
		}
		db, err := loadDBConn("core.db")
		if err != nil {
			fmt.Println(i18n.GetMsgWithMapForCmd("DBConnErr", map[string]interface{}{"err": err.Error()}))
			return err
		}
		if err := setSettingByKey(db, "SystemVersion", version); err != nil {
			fmt.Println(i18n.GetMsgWithMapForCmd("UpdateUserErr", map[string]interface{}{"err": err.Error()}))
			return err
		}
		defer dropUpgradeBackupCopies(db)

		xpackDB, err := loadDBConn("xpack.db")
		if err != nil {
			return nil
		}
		var nodes []Node
		if err := xpackDB.Where("is_auto_upgrade = ? AND name != ?", true, "local").Find(&nodes).Error; err != nil {
			fmt.Println(i18n.GetMsgWithMapForCmd("LoadAutoUpgradeNodesFailed", map[string]interface{}{"err": err.Error()}))
		}
		var nodeNames []string
		for _, item := range nodes {
			nodeNames = append(nodeNames, fmt.Sprintf("%s-%s", item.Name, item.Addr))
		}
		if len(nodeNames) > 0 {
			fmt.Printf("[%s] %s\n", time.Now().Format("2006-01-02 15:04:05"), i18n.GetMsgWithMapForCmd("AutoUpgradeNodes", map[string]interface{}{"nodes": strings.Join(nodeNames, ", ")}))
		}
		if err := xpackDB.Model(&Node{}).
			Where("is_auto_upgrade = ? AND name != ?", true, "local").
			Updates(map[string]interface{}{"status": constant.StatusWaitForUpgrade}).
			Error; err != nil {
			fmt.Println(i18n.GetMsgWithMapForCmd("UpdateAutoUpgradeNodesStatusFailed", map[string]interface{}{"err": err.Error()}))
		}
		return nil
	},
}

func dropUpgradeBackupCopies(db *gorm.DB) {
	baseDir, err := loadBaseDir()
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	if err := upgradeUtil.DropBackupCopies(baseDir, getSettingByKey(db, "UpgradeBackupCopies")); err != nil {
		fmt.Printf("read upgrade dir failed, err: %v\n", err)
	}
}

func username() {
	reader := bufio.NewReader(os.Stdin)
	fmt.Print(i18n.GetMsgByKeyForCmd("UpdateUser") + ": ")
	newUsername, _ := reader.ReadString('\n')
	newUsername = strings.Trim(newUsername, "\n")
	if len(newUsername) == 0 {
		fmt.Println(i18n.GetMsgByKeyForCmd("UpdateUserNull"))
		return
	}
	if strings.Contains(newUsername, " ") {
		fmt.Println(i18n.GetMsgByKeyForCmd("UpdateUserBlank"))
		return
	}
	result, err := regexp.MatchString("^[a-zA-Z0-9_\u4e00-\u9fa5]{3,30}$", newUsername)
	if !result || err != nil {
		fmt.Println(i18n.GetMsgByKeyForCmd("UpdateUserFormat"))
		return
	}

	db, err := loadDBConn("core.db")
	if err != nil {
		fmt.Println(i18n.GetMsgWithMapForCmd("DBConnErr", map[string]interface{}{"err": err.Error()}))
		return
	}
	if isEnterprise() {
		enterpriseDB, err := loadDBConn("enterprise.db")
		if err != nil {
			fmt.Println(i18n.GetMsgWithMapForCmd("DBConnErr", map[string]interface{}{"err": err.Error()}))
			return
		}
		if err := updateEnterpriseUserName(enterpriseDB, strings.TrimSpace(updateUserNameFlag), newUsername); err != nil {
			fmt.Println(i18n.GetMsgWithMapForCmd("UpdateUserErr", map[string]interface{}{"err": err.Error()}))
			return
		}
	} else if err := setSettingByKey(db, "UserName", newUsername); err != nil {
		fmt.Println(i18n.GetMsgWithMapForCmd("UpdateUserErr", map[string]interface{}{"err": err.Error()}))
		return
	}

	fmt.Println("\n" + i18n.GetMsgByKeyForCmd("UpdateSuccessful"))
	fmt.Println(i18n.GetMsgWithMapForCmd("UpdateUserResult", map[string]interface{}{"name": newUsername}))
}

func password() {
	fmt.Print(i18n.GetMsgByKeyForCmd("UpdatePassword") + ": ")
	bytePassword, err := term.ReadPassword(int(os.Stdin.Fd()))
	if err != nil {
		fmt.Println("\n" + i18n.GetMsgWithMapForCmd("UpdatePasswordRead", map[string]interface{}{"err": err.Error()}))
		return
	}
	newPassword := string(bytePassword)
	newPassword = strings.Trim(newPassword, "\n")

	if len(newPassword) == 0 {
		fmt.Println("\n", i18n.GetMsgByKeyForCmd("UpdatePasswordNull"))
		return
	}
	if strings.Contains(newPassword, " ") {
		fmt.Println("\n" + i18n.GetMsgByKeyForCmd("UpdateUPasswordBlank"))
		return
	}
	db, err := loadDBConn("core.db")
	if err != nil {
		fmt.Println("\n" + i18n.GetMsgWithMapForCmd("DBConnErr", map[string]interface{}{"err": err.Error()}))
		return
	}
	complexSetting := getSettingByKey(db, "ComplexityVerification")
	if complexSetting == constant.StatusEnable {
		if isValidPassword("newPassword") {
			fmt.Println("\n" + i18n.GetMsgByKeyForCmd("UpdatePasswordFormat"))
			return
		}
	}
	if len(newPassword) < 6 {
		fmt.Println(i18n.GetMsgByKeyForCmd("UpdatePasswordLen"))
		return
	}

	fmt.Print("\n" + i18n.GetMsgByKeyForCmd("UpdatePasswordRe"))
	byteConfirmPassword, err := term.ReadPassword(int(os.Stdin.Fd()))
	if err != nil {
		fmt.Println("\n" + i18n.GetMsgWithMapForCmd("UpdatePasswordRead", map[string]interface{}{"err": err.Error()}))
		return
	}
	confirmPassword := string(byteConfirmPassword)
	confirmPassword = strings.Trim(confirmPassword, "\n")

	if newPassword != confirmPassword {
		fmt.Println("\n", i18n.GetMsgByKeyForCmd("UpdatePasswordSame"))
		return
	}

	p := ""
	encryptSetting := getSettingByKey(db, "EncryptKey")
	if len(encryptSetting) == 16 {
		global.CONF.Base.EncryptKey = encryptSetting
		p, _ = encrypt.StringEncrypt(newPassword)
	} else {
		p = newPassword
	}
	if isEnterprise() {
		enterpriseDB, err := loadDBConn("enterprise.db")
		if err != nil {
			fmt.Println("\n" + i18n.GetMsgWithMapForCmd("DBConnErr", map[string]interface{}{"err": err.Error()}))
			return
		}
		if err := updateEnterprisePassword(enterpriseDB, strings.TrimSpace(updatePasswordUserName), p); err != nil {
			fmt.Println("\n", i18n.GetMsgWithMapForCmd("UpdatePasswordErr", map[string]interface{}{"err": err.Error()}))
			return
		}
	} else if err := setSettingByKey(db, "Password", p); err != nil {
		fmt.Println("\n", i18n.GetMsgWithMapForCmd("UpdatePasswordErr", map[string]interface{}{"err": err.Error()}))
		return
	}
	username := ""
	if isEnterprise() {
		username = strings.TrimSpace(updatePasswordUserName)
	} else {
		username = getSettingByKey(db, "UserName")
	}

	fmt.Println("\n" + i18n.GetMsgByKeyForCmd("UpdateSuccessful"))
	fmt.Println(i18n.GetMsgWithMapForCmd("UpdateUserResult", map[string]interface{}{"name": username}))
	fmt.Println(i18n.GetMsgWithMapForCmd("UpdatePasswordResult", map[string]interface{}{"name": string(newPassword)}))
}

func port() {
	reader := bufio.NewReader(os.Stdin)
	fmt.Print(i18n.GetMsgByKeyForCmd("UpdatePort") + ": ")

	newPortStr, _ := reader.ReadString('\n')
	newPortStr = strings.Trim(newPortStr, "\n")
	newPort, err := strconv.Atoi(strings.TrimSpace(newPortStr))
	if err != nil || newPort < 1 || newPort > 65535 {
		fmt.Println(i18n.GetMsgByKeyForCmd("UpdatePortFormat"))
		return
	}
	if common.ScanPort(newPort) {
		fmt.Println(i18n.GetMsgByKeyForCmd("UpdatePortUsed"))
		return
	}
	db, err := loadDBConn("core.db")
	if err != nil {
		fmt.Println(i18n.GetMsgWithMapForCmd("DBConnErr", map[string]interface{}{"err": err.Error()}))
		return
	}
	if err := setSettingByKey(db, "ServerPort", newPortStr); err != nil {
		fmt.Println(i18n.GetMsgWithMapForCmd("UpdatePortErr", map[string]interface{}{"err": err.Error()}))
		return
	}

	fmt.Println("\n" + i18n.GetMsgByKeyForCmd("UpdateSuccessful"))
	fmt.Println(i18n.GetMsgWithMapForCmd("UpdatePortResult", map[string]interface{}{"name": newPortStr}))

	std, err := cmd.NewCommandMgr().RunWithStdout("1pctl", "restart", "core")
	if err != nil {
		fmt.Println(std)
	}
}
func isValidPassword(password string) bool {
	numCount := 0
	alphaCount := 0
	specialCount := 0

	for _, char := range password {
		switch {
		case unicode.IsDigit(char):
			numCount++
		case unicode.IsLetter(char):
			alphaCount++
		case isSpecialChar(char):
			specialCount++
		}
	}

	if len(password) < 8 || len(password) > 30 {
		return false
	}
	if (numCount == 0 && alphaCount == 0) || (alphaCount == 0 && specialCount == 0) || (numCount == 0 && specialCount == 0) {
		return false
	}
	return true
}

func isSpecialChar(char rune) bool {
	specialChars := "!@#$%*_,.?"
	return unicode.IsPunct(char) && contains(specialChars, char)
}

func contains(specialChars string, char rune) bool {
	for _, c := range specialChars {
		if c == char {
			return true
		}
	}
	return false
}

func loadUpdateHelper() {
	fmt.Println(i18n.GetMsgByKeyForCmd("UpdateCommands"))
	fmt.Println("\nUsage:\n  1panel update [command]\n\nAvailable Commands:")
	fmt.Println("\n  password    " + i18n.GetMsgByKeyForCmd("UpdatePassword"))
	fmt.Println("  port        " + i18n.GetMsgByKeyForCmd("UpdatePort"))
	fmt.Println("  username    " + i18n.GetMsgByKeyForCmd("UpdateUser"))
	fmt.Println("\nFlags:\n  -h, --help   help for update")
	fmt.Println("\nUse \"1panel update [command] --help\" for more information about a command.")
}
