// SiYuan - Refactor your thinking
// Copyright (c) 2020-present, b3log.org
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

package api

import (
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/88250/gulu"
	"github.com/88250/lute"
	"github.com/88250/lute/html"
	"github.com/gin-gonic/gin"
	"github.com/siyuan-note/filelock"
	"github.com/siyuan-note/logging"
	"github.com/siyuan-note/siyuan/kernel/conf"
	"github.com/siyuan-note/siyuan/kernel/model"
	"github.com/siyuan-note/siyuan/kernel/util"
)

func clearTempFiles(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	model.ClearTempFiles()
}

func vacuumDataIndex(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	model.VacuumDataIndex()
}

func rebuildDataIndex(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	model.FullReindex()
}

func addMicrosoftDefenderExclusion(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	if !gulu.OS.IsWindows() {
		return
	}

	err := model.AddMicrosoftDefenderExclusion()
	if nil != err {
		ret.Code = -1
		ret.Msg = err.Error()
	}
}

func ignoreAddMicrosoftDefenderExclusion(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	if !gulu.OS.IsWindows() {
		return
	}

	model.Conf.System.MicrosoftDefenderExcluded = true
	model.Conf.Save()
}

func getWorkspaceInfo(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	ret.Data = map[string]any{
		"workspaceDir": util.WorkspaceDir,
		"siyuanVer":    util.Ver,
	}
}

func getNetwork(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	maskedConf, err := model.GetMaskedConf()
	if err != nil {
		ret.Code = -1
		ret.Msg = "get conf failed: " + err.Error()
		return
	}

	ret.Data = map[string]interface{}{
		"proxy": maskedConf.System.NetworkProxy,
	}
}

func getChangelog(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	data := map[string]interface{}{"show": false, "html": ""}
	ret.Data = data

	changelogsDir := filepath.Join(util.WorkingDir, "changelogs")
	if !gulu.File.IsDir(changelogsDir) {
		return
	}

	if !model.Conf.ShowChangelog {
		return
	}

	changelogPath := filepath.Join(changelogsDir, "v"+util.Ver, "v"+util.Ver+"_"+model.Conf.Lang+".md")
	if !gulu.File.IsExist(changelogPath) {
		changelogPath = filepath.Join(changelogsDir, "v"+util.Ver, "v"+util.Ver+".md")
		if !gulu.File.IsExist(changelogPath) {
			logging.LogErrorf("changelog not found: %s", changelogPath)
			return
		}
	}

	contentData, err := os.ReadFile(changelogPath)
	if err != nil {
		logging.LogErrorf("read changelog failed: %s", err)
		return
	}

	model.Conf.ShowChangelog = false
	luteEngine := lute.New()
	htmlContent := luteEngine.MarkdownStr("", string(contentData))
	htmlContent = util.LinkTarget(htmlContent, "")

	data["show"] = true
	data["html"] = htmlContent
	ret.Data = data
}

func getEmojiConf(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	builtConfPath := filepath.Join(util.AppearancePath, "emojis", "conf.json")
	data, err := os.ReadFile(builtConfPath)
	if err != nil {
		logging.LogErrorf("read emojis conf.json failed: %s", err)
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	var conf []map[string]interface{}
	if err = gulu.JSON.UnmarshalJSON(data, &conf); err != nil {
		logging.LogErrorf("unmarshal emojis conf.json failed: %s", err)
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	customConfDir := filepath.Join(util.DataDir, "emojis")
	custom := map[string]interface{}{
		"id":          "custom",
		"title":       "Custom",
		"title_zh_cn": "自定义",
		"title_ja_jp": "カスタム",
	}
	items := []map[string]interface{}{}
	custom["items"] = items
	if gulu.File.IsDir(customConfDir) {
		model.ClearCustomEmojis()
		customEmojis, err := os.ReadDir(customConfDir)
		if err != nil {
			logging.LogErrorf("read custom emojis failed: %s", err)
		} else {
			for _, customEmoji := range customEmojis {
				name := customEmoji.Name()
				if strings.HasPrefix(name, ".") {
					continue
				}

				if !util.IsValidUploadFileName(html.UnescapeString(name)) {
					emojiFullName := filepath.Join(customConfDir, name)
					name = util.FilterUploadEmojiFileName(name)
					fullPathFilteredName := filepath.Join(customConfDir, name)
					// XSS through emoji name https://github.com/siyuan-note/siyuan/issues/15034
					logging.LogWarnf("renaming invalid custom emoji file [%s] to [%s]", name, fullPathFilteredName)
					if removeErr := filelock.Rename(emojiFullName, fullPathFilteredName); nil != removeErr {
						logging.LogErrorf("renaming invalid custom emoji file to [%s] failed: %s", fullPathFilteredName, removeErr)
					}
				}

				if customEmoji.IsDir() {
					// 子级
					subCustomEmojis, err := os.ReadDir(filepath.Join(customConfDir, name))
					if err != nil {
						logging.LogErrorf("read custom emojis failed: %s", err)
						continue
					}

					for _, subCustomEmoji := range subCustomEmojis {
						if subCustomEmoji.IsDir() {
							continue
						}

						subName := subCustomEmoji.Name()
						if strings.HasPrefix(subName, ".") {
							continue
						}

						if !util.IsValidUploadFileName(html.UnescapeString(subName)) {
							emojiFullName := filepath.Join(customConfDir, name, subName)
							fullPathFilteredName := filepath.Join(customConfDir, name, util.FilterUploadEmojiFileName(subName))
							// XSS through emoji name https://github.com/siyuan-note/siyuan/issues/15034
							logging.LogWarnf("renaming invalid custom emoji file [%s] to [%s]", subName, fullPathFilteredName)
							if removeErr := filelock.Rename(emojiFullName, fullPathFilteredName); nil != removeErr {
								logging.LogErrorf("renaming invalid custom emoji file to [%s] failed: %s", fullPathFilteredName, removeErr)
							}
						}

						addCustomEmoji(name+"/"+subName, &items)
					}
					continue
				}

				addCustomEmoji(name, &items)
			}
		}
	}
	custom["items"] = items
	conf = append([]map[string]interface{}{custom}, conf...)

	ret.Data = conf
	return
}

func addCustomEmoji(name string, items *[]map[string]interface{}) {
	ext := filepath.Ext(name)
	nameWithoutExt := strings.TrimSuffix(name, ext)
	emoji := map[string]interface{}{
		"unicode":           name,
		"description":       nameWithoutExt,
		"description_zh_cn": nameWithoutExt,
		"description_ja_jp": nameWithoutExt,
		"keywords":          nameWithoutExt,
	}
	*items = append(*items, emoji)

	imgSrc := "/emojis/" + name
	model.AddCustomEmoji(nameWithoutExt, imgSrc)
}

func checkUpdate(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	arg, ok := util.JsonArg(c, ret)
	if !ok {
		return
	}

	showMsg := arg["showMsg"].(bool)
	model.CheckUpdate(showMsg)
}

func exportLog(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	zipPath := model.ExportSystemLog()
	ret.Data = map[string]interface{}{
		"zip": zipPath,
	}
}

func exportConf(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	logging.LogInfof("exporting conf...")

	name := "siyuan-conf-" + time.Now().Format("20060102150405") + ".json"
	tmpDir := filepath.Join(util.TempDir, "export")
	if err := os.MkdirAll(tmpDir, 0755); err != nil {
		logging.LogErrorf("export conf failed: %s", err)
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	data, err := gulu.JSON.MarshalJSON(model.Conf)
	if err != nil {
		logging.LogErrorf("export conf failed: %s", err)
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}
	clonedConf := &model.AppConf{}
	if err = gulu.JSON.UnmarshalJSON(data, clonedConf); err != nil {
		logging.LogErrorf("export conf failed: %s", err)
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	if nil != clonedConf.Appearance {
		clonedConf.Appearance.DarkThemes = nil
		clonedConf.Appearance.LightThemes = nil
		clonedConf.Appearance.Icons = nil
	}
	if nil != clonedConf.Editor {
		clonedConf.Editor.Emoji = []string{}
	}
	if nil != clonedConf.Export {
		clonedConf.Export.PandocBin = ""
	}
	clonedConf.UserData = ""
	clonedConf.Account = nil
	clonedConf.AccessAuthCode = ""
	if nil != clonedConf.System {
		clonedConf.System.ID = ""
		clonedConf.System.Name = ""
		clonedConf.System.OSPlatform = ""
		clonedConf.System.Container = ""
		clonedConf.System.IsMicrosoftStore = false
		clonedConf.System.IsInsider = false
		clonedConf.System.MicrosoftDefenderExcluded = false
	}
	clonedConf.Sync = nil
	clonedConf.Stat = nil
	clonedConf.Api = nil
	clonedConf.Repo = nil
	clonedConf.Publish = nil
	clonedConf.CloudRegion = 0
	clonedConf.DataIndexState = 0

	data, err = gulu.JSON.MarshalIndentJSON(clonedConf, "", "  ")
	if err != nil {
		logging.LogErrorf("export conf failed: %s", err)
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	tmp := filepath.Join(tmpDir, name)
	if err = os.WriteFile(tmp, data, 0644); err != nil {
		logging.LogErrorf("export conf failed: %s", err)
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	zipFile, err := gulu.Zip.Create(tmp + ".zip")
	if err != nil {
		logging.LogErrorf("export conf failed: %s", err)
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	if err = zipFile.AddEntry(name, tmp); err != nil {
		logging.LogErrorf("export conf failed: %s", err)
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	if err = zipFile.Close(); err != nil {
		logging.LogErrorf("export conf failed: %s", err)
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	logging.LogInfof("exported conf")

	zipPath := "/export/" + name + ".zip"
	ret.Data = map[string]interface{}{
		"name": name,
		"zip":  zipPath,
	}
}

func importConf(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(200, ret)

	logging.LogInfof("importing conf...")

	form, err := c.MultipartForm()
	if err != nil {
		logging.LogErrorf("read upload file failed: %s", err)
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	files := form.File["file"]
	if 1 != len(files) {
		ret.Code = -1
		ret.Msg = "invalid upload file"
		return
	}

	f := files[0]
	fh, err := f.Open()
	if err != nil {
		logging.LogErrorf("read upload file failed: %s", err)
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	data, err := io.ReadAll(fh)
	fh.Close()
	if err != nil {
		logging.LogErrorf("read upload file failed: %s", err)
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	importDir := filepath.Join(util.TempDir, "import")
	if err = os.MkdirAll(importDir, 0755); err != nil {
		logging.LogErrorf("import conf failed: %s", err)
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	tmp := filepath.Join(importDir, f.Filename)
	if err = os.WriteFile(tmp, data, 0644); err != nil {
		logging.LogErrorf("import conf failed: %s", err)
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	tmpDir := filepath.Join(importDir, "conf")
	os.RemoveAll(tmpDir)
	if strings.HasSuffix(strings.ToLower(tmp), ".zip") {
		if err = gulu.Zip.Unzip(tmp, tmpDir); err != nil {
			logging.LogErrorf("import conf failed: %s", err)
			ret.Code = -1
			ret.Msg = err.Error()
			return
		}
	} else if strings.HasSuffix(strings.ToLower(tmp), ".json") {
		if err = gulu.File.CopyFile(tmp, filepath.Join(tmpDir, f.Filename)); err != nil {
			logging.LogErrorf("import conf failed: %s", err)
			ret.Code = -1
			ret.Msg = err.Error()
		}
	} else {
		logging.LogErrorf("invalid conf package")
		ret.Code = -1
		ret.Msg = "invalid conf package"
		return
	}

	entries, err := os.ReadDir(tmpDir)
	if err != nil {
		logging.LogErrorf("import conf failed: %s", err)
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	if 1 != len(entries) {
		logging.LogErrorf("invalid conf package")
		ret.Code = -1
		ret.Msg = "invalid conf package"
		return
	}

	tmp = filepath.Join(tmpDir, entries[0].Name())
	data, err = os.ReadFile(tmp)
	if err != nil {
		logging.LogErrorf("import conf failed: %s", err)
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	importedConf := model.NewAppConf()
	if err = gulu.JSON.UnmarshalJSON(data, importedConf); err != nil {
		logging.LogErrorf("import conf failed: %s", err)
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	model.Conf.FileTree = importedConf.FileTree
	model.Conf.Tag = importedConf.Tag
	model.Conf.Editor = importedConf.Editor
	model.Conf.Export = importedConf.Export
	model.Conf.Graph = importedConf.Graph
	model.Conf.UILayout = importedConf.UILayout
	model.Conf.System = importedConf.System
	model.Conf.Keymap = importedConf.Keymap
	model.Conf.Search = importedConf.Search
	model.Conf.Flashcard = importedConf.Flashcard
	model.Conf.AI = importedConf.AI
	model.Conf.Bazaar = importedConf.Bazaar
	model.Conf.Save()

	logging.LogInfof("imported conf")
}

func getConf(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	maskedConf, err := model.GetMaskedConf()
	if err != nil {
		ret.Code = -1
		ret.Msg = "get conf failed: " + err.Error()
		return
	}

	if !maskedConf.Sync.Enabled || (0 == maskedConf.Sync.Provider && !model.IsSubscriber()) {
		maskedConf.Sync.Stat = model.Conf.Language(53)
	}

	// REF: https://github.com/siyuan-note/siyuan/issues/11364
	role := model.GetGinContextRole(c)
	isPublish := model.IsReadOnlyRole(role)
	if isPublish {
		maskedConf.ReadOnly = true
	}
	if !model.IsValidRole(role, []model.Role{
		model.RoleAdministrator,
	}) {
		model.HideConfSecret(maskedConf)
	}

	ret.Data = map[string]interface{}{
		"conf":      maskedConf,
		"start":     !util.IsUILoaded,
		"isPublish": isPublish,
	}
}

func setUILayout(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	if util.ReadOnly {
		return
	}

	arg, ok := util.JsonArg(c, ret)
	if !ok {
		return
	}

	param, err := gulu.JSON.MarshalJSON(arg["layout"])
	if err != nil {
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	uiLayout := &conf.UILayout{}
	if err = gulu.JSON.UnmarshalJSON(param, uiLayout); err != nil {
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	model.Conf.SetUILayout(uiLayout)
	model.Conf.Save()
}

func setAPIToken(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	arg, ok := util.JsonArg(c, ret)
	if !ok {
		return
	}

	token := arg["token"].(string)
	model.Conf.Api.Token = token
	model.Conf.Save()
}

func setAccessAuthCode(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	if util.ContainerDocker == util.Container {
		ret.Code = -1
		ret.Msg = "access auth code cannot be set in Docker container"
		return
	}

	arg, ok := util.JsonArg(c, ret)
	if !ok {
		return
	}

	aac := arg["accessAuthCode"].(string)
	if model.MaskedAccessAuthCode == aac {
		aac = model.Conf.AccessAuthCode
	}

	aac = strings.TrimSpace(aac)
	aac = util.RemoveInvalid(aac)

	model.Conf.AccessAuthCode = aac
	model.Conf.Save()

	session := util.GetSession(c)
	workspaceSession := util.GetWorkspaceSession(session)
	workspaceSession.AccessAuthCode = aac
	session.Save(c)
	go func() {
		time.Sleep(200 * time.Millisecond)
		util.ReloadUI()
	}()
	return
}

func setFollowSystemLockScreen(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	arg, ok := util.JsonArg(c, ret)
	if !ok {
		return
	}

	lockScreenMode := int(arg["lockScreenMode"].(float64))

	model.Conf.System.LockScreenMode = lockScreenMode
	model.Conf.Save()
	return
}

func getSysFonts(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)
	ret.Data = util.LoadSysFonts()
}

func version(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	ret.Data = util.Ver
}

func currentTime(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	ret.Data = util.CurrentTimeMillis()
}

func bootProgress(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	progress, details := util.GetBootProgressDetails()
	ret.Data = map[string]interface{}{"progress": progress, "details": details}
}

func setAppearanceMode(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	arg, ok := util.JsonArg(c, ret)
	if !ok {
		return
	}

	mode := int(arg["mode"].(float64))
	model.Conf.Appearance.Mode = mode
	if 0 == mode {
		model.Conf.Appearance.ThemeJS = gulu.File.IsExist(filepath.Join(util.ThemesPath, model.Conf.Appearance.ThemeLight, "theme.js"))
	} else {
		model.Conf.Appearance.ThemeJS = gulu.File.IsExist(filepath.Join(util.ThemesPath, model.Conf.Appearance.ThemeDark, "theme.js"))
	}
	model.Conf.Save()

	ret.Data = map[string]interface{}{
		"appearance": model.Conf.Appearance,
	}
}

func setNetworkServe(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	arg, ok := util.JsonArg(c, ret)
	if !ok {
		return
	}

	networkServe := arg["networkServe"].(bool)
	model.Conf.System.NetworkServe = networkServe
	model.Conf.Save()

	util.PushMsg(model.Conf.Language(42), 1000*15)
	time.Sleep(time.Second * 3)
}

func setNetworkServeTLS(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	arg, ok := util.JsonArg(c, ret)
	if !ok {
		return
	}

	networkServeTLS := arg["networkServeTLS"].(bool)
	model.Conf.System.NetworkServeTLS = networkServeTLS
	model.Conf.Save()

	util.PushMsg(model.Conf.Language(42), 1000*15)
	time.Sleep(time.Second * 3)
}

func exportTLSCACert(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	caCertPath := filepath.Join(util.ConfDir, util.TLSCACertFilename)
	if !gulu.File.IsExist(caCertPath) {
		ret.Code = -1
		ret.Msg = "CA certificate not found"
		return
	}

	tmpDir := filepath.Join(util.TempDir, "export")
	if err := os.MkdirAll(tmpDir, 0755); err != nil {
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	exportPath := filepath.Join(tmpDir, util.TLSCACertFilename)
	if err := gulu.File.CopyFile(caCertPath, exportPath); err != nil {
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	ret.Data = map[string]interface{}{
		"path": "/export/" + util.TLSCACertFilename,
	}
}

func exportTLSCABundle(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	caCertPath := filepath.Join(util.ConfDir, util.TLSCACertFilename)
	caKeyPath := filepath.Join(util.ConfDir, util.TLSCAKeyFilename)

	if !gulu.File.IsExist(caCertPath) || !gulu.File.IsExist(caKeyPath) {
		ret.Code = -1
		ret.Msg = "CA certificate not found, please enable TLS first"
		return
	}

	tmpDir := filepath.Join(util.TempDir, "export", "ca-bundle")
	os.RemoveAll(tmpDir)
	if err := os.MkdirAll(tmpDir, 0755); err != nil {
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}
	defer os.RemoveAll(tmpDir)

	if err := gulu.File.CopyFile(caCertPath, filepath.Join(tmpDir, util.TLSCACertFilename)); err != nil {
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}
	if err := gulu.File.CopyFile(caKeyPath, filepath.Join(tmpDir, util.TLSCAKeyFilename)); err != nil {
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	zipPath := filepath.Join(util.TempDir, "export", "ca-bundle.zip")
	zipFile, err := gulu.Zip.Create(zipPath)
	if err != nil {
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	if err := zipFile.AddDirectory("", tmpDir); err != nil {
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	if err := zipFile.Close(); err != nil {
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	ret.Data = map[string]interface{}{
		"path": "/export/ca-bundle.zip",
	}
}

func importTLSCABundle(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	file, err := c.FormFile("file")
	if err != nil {
		ret.Code = -1
		ret.Msg = "file is required: " + err.Error()
		return
	}

	tmpDir := filepath.Join(util.TempDir, "import")
	if err := os.MkdirAll(tmpDir, 0755); err != nil {
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	tmpZipPath := filepath.Join(tmpDir, "ca-bundle.zip")
	if err := c.SaveUploadedFile(file, tmpZipPath); err != nil {
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}
	defer os.Remove(tmpZipPath)

	extractDir := filepath.Join(tmpDir, "ca-bundle")
	os.RemoveAll(extractDir)
	if err := gulu.Zip.Unzip(tmpZipPath, extractDir); err != nil {
		ret.Code = -1
		ret.Msg = "failed to extract zip file: " + err.Error()
		return
	}
	defer os.RemoveAll(extractDir)

	caCertPath := filepath.Join(extractDir, util.TLSCACertFilename)
	caCertPEM, err := os.ReadFile(caCertPath)
	if err != nil {
		ret.Code = -1
		ret.Msg = "ca.crt not found in zip file"
		return
	}

	caKeyPath := filepath.Join(extractDir, util.TLSCAKeyFilename)
	caKeyPEM, err := os.ReadFile(caKeyPath)
	if err != nil {
		ret.Code = -1
		ret.Msg = "ca.key not found in zip file"
		return
	}

	if err := util.ImportCABundle(string(caCertPEM), string(caKeyPEM)); err != nil {
		ret.Code = -1
		ret.Msg = err.Error()
		return
	}

	ret.Data = map[string]interface{}{
		"msg": "CA bundle imported successfully. Please restart to apply changes.",
	}
}

func setAutoLaunch(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	arg, ok := util.JsonArg(c, ret)
	if !ok {
		return
	}

	autoLaunch := int(arg["autoLaunch"].(float64))
	model.Conf.System.AutoLaunch2 = autoLaunch
	model.Conf.Save()
}

func setDownloadInstallPkg(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	arg, ok := util.JsonArg(c, ret)
	if !ok {
		return
	}

	downloadInstallPkg := arg["downloadInstallPkg"].(bool)
	model.Conf.System.DownloadInstallPkg = downloadInstallPkg
	model.Conf.Save()
}

func setNetworkProxy(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	arg, ok := util.JsonArg(c, ret)
	if !ok {
		return
	}

	scheme := arg["scheme"].(string)
	host := arg["host"].(string)
	port := arg["port"].(string)
	model.Conf.System.NetworkProxy = &conf.NetworkProxy{
		Scheme: scheme,
		Host:   host,
		Port:   port,
	}
	model.Conf.Save()

	proxyURL := model.Conf.System.NetworkProxy.String()
	util.SetNetworkProxy(proxyURL)
	util.PushMsg(model.Conf.Language(102), 3000)
}

func addUIProcess(c *gin.Context) {
	pid := c.Query("pid")
	util.UIProcessIDs.Store(pid, true)
}

func exit(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(http.StatusOK, ret)

	arg, ok := util.JsonArg(c, ret)
	if !ok {
		return
	}

	forceArg := arg["force"]
	var force bool
	if nil != forceArg {
		force = forceArg.(bool)
	}

	execInstallPkgArg := arg["execInstallPkg"] // 0：默认检查新版本，1：不执行新版本安装，2：执行新版本安装
	execInstallPkg := 0
	if nil != execInstallPkgArg {
		execInstallPkg = int(execInstallPkgArg.(float64))
	}

	setCurrentWorkspaceArg := arg["setCurrentWorkspace"]
	setCurrentWorkspace := true
	if nil != setCurrentWorkspaceArg {
		setCurrentWorkspace = setCurrentWorkspaceArg.(bool)
	}

	exitCode := model.Close(force, setCurrentWorkspace, execInstallPkg)
	ret.Code = exitCode
	switch exitCode {
	case 0:
	case 1: // 同步执行失败
		ret.Msg = model.Conf.Language(96) + "<div class=\"fn__space\"></div><button class=\"b3-button b3-button--white\">" + model.Conf.Language(97) + "</button>"
		ret.Data = map[string]interface{}{"closeTimeout": 0}
	case 2: // 提示新安装包
		ret.Msg = model.Conf.Language(61)
		ret.Data = map[string]interface{}{"closeTimeout": 0}
	}
}
