// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .

package instances

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
	"github.com/TeaOSLab/EdgeAPI/internal/db/models"
	"github.com/TeaOSLab/EdgeAPI/internal/installers/helpers"
	"github.com/TeaOSLab/EdgeAPI/internal/setup"
	executils "github.com/TeaOSLab/EdgeAPI/internal/utils/exec"
	"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
	"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
	"github.com/iwind/TeaGo"
	"github.com/iwind/TeaGo/Tea"
	"github.com/iwind/TeaGo/dbs"
	"github.com/iwind/TeaGo/lists"
	"github.com/iwind/TeaGo/rands"
	"github.com/iwind/TeaGo/types"
	"gopkg.in/yaml.v3"
	"log"
	"net"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"time"
)

type Instance struct {
	options Options
}

func NewInstance(options Options) *Instance {
	return &Instance{
		options: options,
	}
}

func (this *Instance) SetupAll() error {
	type TaskFn func() error
	for _, taskFn := range []TaskFn{
		this.SetupDB,
		this.SetupAdminNode,
		this.SetupAPINode,
		this.SetupNode,
		this.SetupUserNode,
		this.Clean,
		this.Startup,
	} {
		err := taskFn()
		if err != nil {
			return err
		}
	}
	return nil
}

func (this *Instance) SetupDB() error {
	if this.options.Verbose {
		log.Println("setup db " + this.options.DB.Host + ":" + types.String(this.options.DB.Port) + "/" + this.options.DB.Name + " ...")
	}

	// 检查数据库名
	{
		db, dbErr := this.dbInstanceWithoutDBName()
		if dbErr != nil {
			return dbErr
		}

		defer func() {
			_ = db.Close()
		}()

		// 等待连接成功
		for i := 0; i < 30; i++ {
			err := db.Raw().PingContext(context.Background())
			if err == nil {
				break
			}
			time.Sleep(1 * time.Second)
		}

		_, err := db.Exec("USE `" + this.options.DB.Name + "`")
		if err != nil {
			if models.CheckSQLErrCode(err, 1049) {
				_, err = db.Exec("CREATE DATABASE `" + this.options.DB.Name + "` DEFAULT CHARSET utf8mb4")
				if err != nil {
					return fmt.Errorf("create new database failed: %w", err)
				}
			} else {
				return fmt.Errorf("check database failed: %w", err)
			}
		}
	}

	// 检查版本
	{
		db, instanceErr := this.dbInstance(false)
		if instanceErr != nil {
			return instanceErr
		}

		defer func() {
			_ = db.Close()
		}()

		dbConfig, configErr := db.Config()
		if configErr != nil {
			return configErr
		}

		var shouldExecute bool
		version, err := db.FindCol(0, "SELECT version FROM edgeVersions")
		if err != nil {
			shouldExecute = true
		} else {
			if !strings.HasPrefix(types.String(version), teaconst.Version+".") {
				shouldExecute = true
			}
		}
		if shouldExecute {
			var executor = setup.NewSQLExecutor(dbConfig)
			err = executor.Run(false)
			if err != nil {
				return fmt.Errorf("execute sql failed: %w", err)
			}

			// wait to commit
			time.Sleep(1 * time.Second)
		}
	}

	// 启用数据库
	db, instanceErr := this.dbInstance(true)
	if instanceErr != nil {
		return instanceErr
	}

	defer func() {
		_ = db.Close()
	}()

	// 创建Admin Token
	var tx *dbs.Tx
	{
		one, err := db.FindOne("SELECT * FROM edgeAPITokens WHERE role='admin'")
		if err != nil {
			return err
		}
		if len(one) == 0 {
			var nodeId = rands.HexString(32)
			var secret = rands.String(32)
			err = models.SharedApiTokenDAO.CreateAPIToken(tx, nodeId, secret, "admin")
			if err != nil {
				return fmt.Errorf("create admin node token failed: %w", err)
			}
		}
	}

	// 创建Admin
	{
		one, err := db.FindOne("SELECT * FROM edgeAdmins")
		if err != nil {
			return err
		}
		if len(one) == 0 {
			var password = rands.String(16)
			// 保存密码到文件
			var dir = this.options.WorkDir + "/usr/local/goedge"
			_, err = os.Stat(dir)
			if err != nil {
				err = os.MkdirAll(dir, 0777)
				if err != nil {
					return fmt.Errorf("create directory '"+dir+"' failed: %w", err)
				}
			}
			err = os.WriteFile(dir+"/password.txt", []byte("Admin Password: "+password+"\n"), 0666)
			if err != nil {
				return fmt.Errorf("write 'password.txt' failed: %w", err)
			}

			_, err = models.SharedAdminDAO.CreateAdmin(tx, "admin", true, password, "Admin", true, []byte("[]"))
			if err != nil {
				return fmt.Errorf("create admin failed: %w", err)
			}
		}
	}

	// 创建API Node
	{
		one, findErr := db.FindOne("SELECT * FROM edgeAPINodes")
		if findErr != nil {
			return findErr
		}
		if len(one) == 0 {
			// http
			var httpConfig = &serverconfigs.HTTPProtocolConfig{}
			httpConfig.IsOn = true
			httpConfig.Listen = []*serverconfigs.NetworkAddressConfig{
				{
					Protocol:  "http",
					PortRange: types.String(this.options.APINode.HTTPPort),
				},
			}
			httpJSON, encodeErr := json.Marshal(httpConfig)
			if encodeErr != nil {
				return fmt.Errorf("encode api http config failed: %w", encodeErr)
			}

			// rest
			var restHTTPConfig = &serverconfigs.HTTPProtocolConfig{}
			restHTTPConfig.IsOn = true
			restHTTPConfig.Listen = []*serverconfigs.NetworkAddressConfig{
				{
					Protocol:  "http",
					PortRange: types.String(this.options.APINode.RestHTTPPort),
				},
			}
			restHTTPJSON, encodeErr := json.Marshal(restHTTPConfig)
			if encodeErr != nil {
				return fmt.Errorf("encode api rest http config failed: %w", encodeErr)
			}

			// access addrs
			var accessAddrs = []*serverconfigs.NetworkAddressConfig{
				{
					Protocol:  "http",
					Host:      "127.0.0.1",
					PortRange: types.String(this.options.APINode.HTTPPort),
				},
			}
			accessAddrsJSON, encodeErr := json.Marshal(accessAddrs)
			if encodeErr != nil {
				return fmt.Errorf("encode access addresses failed: %w", encodeErr)
			}

			_, err := models.SharedAPINodeDAO.CreateAPINode(tx, "API Node", "Primary API Node", httpJSON, nil, true, restHTTPJSON, nil, accessAddrsJSON, true)
			if err != nil {
				return err
			}
		}
		{
			// check token
			nodes, nodesErr := models.SharedAPINodeDAO.FindAllEnabledAPINodes(tx)
			if nodesErr != nil {
				return nodesErr
			}
			for _, node := range nodes {
				token, err := models.SharedApiTokenDAO.FindEnabledTokenWithNode(tx, node.UniqueId)
				if err != nil {
					return err
				}
				if token == nil {
					err = models.SharedApiTokenDAO.CreateAPIToken(tx, node.UniqueId, node.Secret, nodeconfigs.NodeRoleAPI)
					if err != nil {
						return err
					}
				}
			}
		}
	}

	// 创建Node
	var clusterId int64
	{
		{
			// check cluster
			clusterIdCol, err := db.FindCol(0, "SELECT id FROM edgeNodeClusters WHERE state=1")
			if err != nil {
				return err
			}
			clusterId = types.Int64(clusterIdCol)
			if clusterId == 0 {
				return errors.New("invalid cluster id '" + types.String(clusterId) + "'")
			}
		}

		one, findErr := db.FindOne("SELECT * FROM edgeNodes")
		if findErr != nil {
			return findErr
		}
		if len(one) == 0 {
			_, err := models.SharedNodeDAO.CreateNode(tx, 0, "Local Node", clusterId, 0, 0)
			if err != nil {
				return fmt.Errorf("create node failed: %w", err)
			}
		}
	}

	// 检查User
	var userId int64
	{
		one, findErr := db.FindOne("SELECT id FROM edgeUsers WHERE state=1")
		if findErr != nil {
			return findErr
		}
		if len(one) == 0 {
			return errors.New("user must not be created yet")
		}
		userId = one.GetInt64("id")
	}

	// 创建用户节点
	{
		one, findErr := db.FindOne("SELECT * FROM edgeUserNodes")
		if findErr != nil {
			return findErr
		}
		if len(one) == 0 {
			// http
			var httpConfig = &serverconfigs.HTTPProtocolConfig{}
			httpConfig.IsOn = true
			httpConfig.Listen = []*serverconfigs.NetworkAddressConfig{
				{
					Protocol:  "http",
					PortRange: types.String(this.options.UserNode.HTTPPort),
				},
			}
			httpJSON, encodeErr := json.Marshal(httpConfig)
			if encodeErr != nil {
				return fmt.Errorf("encode api http config failed: %w", encodeErr)
			}

			// access addrs
			var accessAddrs = []*serverconfigs.NetworkAddressConfig{
				{
					Protocol:  "http",
					Host:      "127.0.0.1",
					PortRange: types.String(this.options.UserNode.HTTPPort),
				},
			}
			accessAddrsJSON, encodeErr := json.Marshal(accessAddrs)
			if encodeErr != nil {
				return fmt.Errorf("encode access addresses failed: %w", encodeErr)
			}

			_, err := models.SharedUserNodeDAO.CreateUserNode(tx, "User Platform", "Created by system", httpJSON, nil, accessAddrsJSON, true)
			if err != nil {
				return err
			}
		}
	}

	// 创建网站
	{
		one, findErr := db.FindOne("SELECT id FROM edgeServers WHERE state=1")
		if findErr != nil {
			return findErr
		}
		if len(one) == 0 {

			var webId int64

			{
				{
					var err error
					webId, err = models.SharedHTTPWebDAO.CreateWeb(tx, 0, userId, nil)
					if err != nil {
						return fmt.Errorf("create web failed: %w", err)
					}
				}

				// 访问日志
				{
					err := models.SharedHTTPWebDAO.UpdateWebAccessLogConfig(tx, webId, []byte(`{
			"isPrior": false,
			"isOn": true,
			"fields": [1, 2, 6, 7],
			"status1": true,
			"status2": true,
			"status3": true,
			"status4": true,
			"status5": true,

			"storageOnly": false,
			"storagePolicies": [],

            "firewallOnly": false
		}`))
					if err != nil {
						return err
					}
				}

				// websocket
				{
					websocketId, err := models.SharedHTTPWebsocketDAO.CreateWebsocket(tx, []byte(`{
					"count": 30,
					"unit": "second"
				}`), true, nil, true, "")
					if err != nil {
						return err
					}

					err = models.SharedHTTPWebDAO.UpdateWebsocket(tx, webId, []byte(`{
				"isPrior": false,
				"isOn": true,
				"websocketId": `+types.String(websocketId)+`
			}`))
					if err != nil {
						return err
					}
				}

				// cache
				{
					var cacheConfig = &serverconfigs.HTTPCacheConfig{
						IsPrior:         false,
						IsOn:            true,
						AddStatusHeader: true,
						PurgeIsOn:       false,
						PurgeKey:        "",
						CacheRefs:       []*serverconfigs.HTTPCacheRef{},
					}
					cacheConfigJSON, err := json.Marshal(cacheConfig)
					if err != nil {
						return err
					}
					err = models.SharedHTTPWebDAO.UpdateWebCache(tx, webId, cacheConfigJSON)
					if err != nil {
						return err
					}
				}

				// waf
				{
					var firewallRef = &firewallconfigs.HTTPFirewallRef{
						IsPrior:          false,
						IsOn:             true,
						FirewallPolicyId: 0,
					}
					firewallRefJSON, err := json.Marshal(firewallRef)
					if err != nil {
						return err
					}
					err = models.SharedHTTPWebDAO.UpdateWebFirewall(tx, webId, firewallRefJSON)
					if err != nil {
						return err
					}
				}

				// stat
				{
					var statConfig = &serverconfigs.HTTPStatRef{
						IsPrior: false,
						IsOn:    true,
					}
					statJSON, err := json.Marshal(statConfig)
					if err != nil {
						return err
					}
					err = models.SharedHTTPWebDAO.UpdateWebStat(tx, webId, statJSON)
					if err != nil {
						return err
					}
				}
			}

			var httpConfig = &serverconfigs.HTTPProtocolConfig{}
			httpConfig.IsOn = true
			httpConfig.Listen = []*serverconfigs.NetworkAddressConfig{
				{
					Protocol:  "http",
					PortRange: types.String(this.options.Node.HTTPPort),
				},
			}
			httpConfigJSON, err := json.Marshal(httpConfig)
			if err != nil {
				return err
			}

			// reverse proxy
			reverseProxyId, err := models.SharedReverseProxyDAO.CreateReverseProxy(tx, 0, userId, nil, nil, nil)
			if err != nil {
				return err
			}
			var reverseProxyRef = &serverconfigs.ReverseProxyRef{
				IsOn:           true,
				ReverseProxyId: reverseProxyId,
			}
			reverseProxyRefJSON, err := json.Marshal(reverseProxyRef)
			if err != nil {
				return err
			}

			_, err = models.SharedServerDAO.CreateServer(tx, 0, userId, serverconfigs.ServerTypeHTTPProxy, "First Site", "Created by system", []byte(`[{"name": "example.org", "type":"full"}]`), false, nil, httpConfigJSON, nil, nil, nil, nil, webId, reverseProxyRefJSON, clusterId, nil, nil, nil, 0)
			if err != nil {
				return fmt.Errorf("create server failed: %w", err)
			}
		}
	}

	// 数据库清理
	{
		var config = systemconfigs.NewDatabaseConfig()
		configJSON, err := json.Marshal(config)
		if err != nil {
			return fmt.Errorf("encode database config failed: %w", err)
		}

		err = models.SharedSysSettingDAO.UpdateSetting(tx, systemconfigs.SettingCodeDatabaseConfigSetting, configJSON)
		if err != nil {
			return err
		}
	}

	// 删除任务
	{
		err := models.SharedNodeTaskDAO.DeleteAllNodeTasks(tx)
		if err != nil {
			return err
		}
	}

	// 设置未初始化
	{
		err := models.SharedSysSettingDAO.UpdateSetting(tx, systemconfigs.SettingCodeStandaloneInstanceInitialized, []byte("0"))
		if err != nil {
			return err
		}
	}

	return nil
}

func (this *Instance) SetupAdminNode() error {
	if this.options.Verbose {
		log.Println("setup admin node ...")
	}

	{
		err := this.unzip("admin", teaconst.Version)
		if err != nil {
			return err
		}
	}

	// create api_node.yaml
	db, findErr := this.dbInstance(true)
	if findErr != nil {
		return findErr
	}

	var apiYAMLData []byte
	{
		node, err := db.FindOne("SELECT nodeId,secret FROM edgeAPITokens WHERE state=1 AND role='admin'")
		if err != nil {
			return err
		}
		if node == nil {
			return errors.New("can not find admin api token in database")
		}

		var apiConfig = &APIConfig{
			RPCEndpoints:     []string{"http://127.0.0.1:" + types.String(this.options.APINode.HTTPPort)},
			RPCDisableUpdate: true,
			NodeId:           node.GetString("nodeId"),
			Secret:           node.GetString("secret"),
		}
		data, err := apiConfig.AsYAML()
		if err != nil {
			return err
		}
		apiYAMLData = data
		err = os.WriteFile(this.targetPrefix()+"/edge-admin/configs/api_admin.yaml", data, 0666)
		if err != nil {
			return err
		}
	}

	// backup
	// ignore errors
	{
		homeDir, err := os.UserHomeDir()
		if err == nil {
			var dir = homeDir + "/.edge-admin/"
			_, err = os.Stat(dir)
			if err != nil {
				_ = os.MkdirAll(dir, 0777)
			}
			_ = os.WriteFile(dir+"/api_admin.yaml", apiYAMLData, 0666)
		}
	}

	// create database config
	{
		dbConfig, err := db.Config()
		if err != nil {
			return fmt.Errorf("retrieve database config failed: %w", err)
		}
		var fullConfig = dbs.Config{}
		fullConfig.Default.DB = "prod"
		fullConfig.DBs = map[string]*dbs.DBConfig{
			"prod": dbConfig,
		}

		data, err := yaml.Marshal(fullConfig)
		if err != nil {
			return err
		}
		err = os.WriteFile(this.targetPrefix()+"/edge-admin/configs/api_db.yaml", data, 0666)
		if err != nil {
			return err
		}
	}

	// server.yaml
	{
		var serverConfig = &TeaGo.ServerConfig{}
		serverConfig.Env = "prod"
		serverConfig.Http.On = true
		serverConfig.Http.Listen = []string{":" + types.String(this.options.AdminNode.Port)}
		data, err := yaml.Marshal(serverConfig)
		if err != nil {
			return err
		}
		err = os.WriteFile(this.targetPrefix()+"/edge-admin/configs/server.yaml", data, 0666)
		if err != nil {
			return err
		}
	}

	return nil
}

func (this *Instance) SetupAPINode() error {
	if this.options.Verbose {
		log.Println("setup api node ...")
	}

	{
		err := this.unzip("api", teaconst.NodeVersion)
		if err != nil {
			return err
		}
	}

	// create api_node.yaml
	db, findErr := this.dbInstance(true)
	if findErr != nil {
		return findErr
	}

	{
		node, err := db.FindOne("SELECT uniqueId,secret FROM edgeAPINodes WHERE state=1")
		if err != nil {
			return err
		}
		if node == nil {
			return errors.New("can not find node in database")
		}

		var apiConfig = &APIConfig{
			NodeId: node.GetString("uniqueId"),
			Secret: node.GetString("secret"),
		}
		data, err := apiConfig.AsYAML()
		if err != nil {
			return err
		}
		err = os.WriteFile(this.targetPrefix()+"/edge-api/configs/api.yaml", data, 0666)
		if err != nil {
			return err
		}
	}

	// create database config
	{
		dbConfig, err := db.Config()
		if err != nil {
			return fmt.Errorf("retrieve database config failed: %w", err)
		}
		var fullConfig = dbs.Config{}
		fullConfig.Default.DB = "prod"
		fullConfig.DBs = map[string]*dbs.DBConfig{
			"prod": dbConfig,
		}

		data, err := yaml.Marshal(fullConfig)
		if err != nil {
			return err
		}
		err = os.WriteFile(this.targetPrefix()+"/edge-api/configs/db.yaml", data, 0666)
		if err != nil {
			return err
		}
	}

	return nil
}

func (this *Instance) SetupNode() error {
	if this.options.Verbose {
		log.Println("setup node ...")
	}

	{
		err := this.unzip("node", teaconst.NodeVersion)
		if err != nil {
			return err
		}
	}

	// create api_node.yaml
	{
		db, err := this.dbInstance(true)
		if err != nil {
			return err
		}
		node, err := db.FindOne("SELECT uniqueId,secret FROM edgeNodes WHERE state=1")
		if err != nil {
			return err
		}
		if node == nil {
			return errors.New("can not find node in database")
		}

		var apiConfig = &APIConfig{
			RPCEndpoints:     []string{"http://127.0.0.1:" + types.String(this.options.APINode.HTTPPort)},
			RPCDisableUpdate: true,
			NodeId:           node.GetString("uniqueId"),
			Secret:           node.GetString("secret"),
		}
		data, err := apiConfig.AsYAML()
		if err != nil {
			return err
		}
		err = os.WriteFile(this.targetPrefix()+"/edge-node/configs/api_node.yaml", data, 0666)
		if err != nil {
			return err
		}
	}

	return nil
}

func (this *Instance) SetupUserNode() error {
	if !this.isPlus() {
		return nil
	}

	if this.options.Verbose {
		log.Println("setup user node ...")
	}

	{
		err := this.unzip("user", teaconst.NodeVersion)
		if err != nil {
			return err
		}
	}

	// create api_node.yaml
	{
		db, err := this.dbInstance(true)
		if err != nil {
			return err
		}
		node, err := db.FindOne("SELECT uniqueId,secret FROM edgeUserNodes WHERE state=1")
		if err != nil {
			return err
		}
		if node == nil {
			return errors.New("can not find node in database")
		}

		var apiConfig = &APIConfig{
			RPCEndpoints:     []string{"http://127.0.0.1:" + types.String(this.options.APINode.HTTPPort)},
			RPCDisableUpdate: true,
			NodeId:           node.GetString("uniqueId"),
			Secret:           node.GetString("secret"),
		}
		data, err := apiConfig.AsYAML()
		if err != nil {
			return err
		}
		err = os.WriteFile(this.targetPrefix()+"/edge-user/configs/api_user.yaml", data, 0666)
		if err != nil {
			return err
		}
	}

	return nil
}

func (this *Instance) Clean() error {
	if this.options.Verbose {
		log.Println("cleaning ...")
	}

	{
		var dir = this.targetPrefix() + "/edge-admin/edge-api"
		_, err := os.Stat(dir)
		if err == nil {
			err = os.RemoveAll(dir)
			if err != nil {
				return fmt.Errorf("remove %s failed: %w", dir, err)
			}
		}
	}

	if !this.options.IsTesting {
		matches, _ := filepath.Glob(this.options.SrcDir + "/*.zip")
		for _, filePath := range matches {
			err := os.Remove(filePath)
			if err != nil {
				return fmt.Errorf("remove %s failed: %w", filePath, err)
			}
		}
	}

	return nil
}

func (this *Instance) Startup() error {
	if this.options.Verbose {
		log.Println("startup ...")
	}

	type NodeInfo struct {
		Role  string
		Ports []int
	}

	for _, node := range []NodeInfo{
		{"api", []int{this.options.APINode.HTTPPort, this.options.APINode.RestHTTPPort}},
		{"admin", []int{this.options.AdminNode.Port}},
		{"node", []int{this.options.Node.HTTPPort}},
		{"user", []int{this.options.UserNode.HTTPPort}},
	} {
		var exe = this.targetPrefix() + "/edge-" + node.Role + "/bin/edge-" + node.Role
		_, err := os.Stat(exe)
		if err != nil {
			continue
		}

		var cmd = executils.NewTimeoutCmd(30*time.Second, exe, "start")
		err = cmd.Run()
		if err != nil {
			return fmt.Errorf("start '"+node.Role+"' failed: %w", err)
		}

		for _, port := range node.Ports {
			var isOk bool
			for i := 0; i < 30; /** seconds **/ i++ {
				conn, connErr := net.DialTimeout("tcp", ":"+types.String(port), 5*time.Second)
				if connErr != nil {
					time.Sleep(1 * time.Second)
					continue
				}
				_ = conn.Close()
				isOk = true
			}
			if !isOk {
				return fmt.Errorf("waiting '%s' port '%d' timeout", node.Role, port)
			}
		}
	}

	return nil
}

func (this *Instance) dbInstance(enableDAO bool) (*dbs.DB, error) {
	Tea.Env = "prod"
	db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
		Driver: "mysql",
		Dsn:    this.options.DB.Username + ":" + this.options.DB.Password + "@tcp(" + this.options.DB.Host + ":" + types.String(this.options.DB.Port) + ")/" + this.options.DB.Name + "?charset=utf8mb4&timeout=30s",
		Prefix: "edge",
	})
	if err != nil {
		return nil, fmt.Errorf("create database instance failed: %w", err)
	}

	if enableDAO {
		this.setupDAO(db)
	}

	return db, nil
}

func (this *Instance) dbInstanceWithoutDBName() (*dbs.DB, error) {
	Tea.Env = "prod"
	db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
		Driver: "mysql",
		Dsn:    this.options.DB.Username + ":" + this.options.DB.Password + "@tcp(" + this.options.DB.Host + ":" + types.String(this.options.DB.Port) + ")/?charset=utf8mb4&timeout=30s",
		Prefix: "edge",
	})
	if err != nil {
		return nil, fmt.Errorf("create database instance failed: %w", err)
	}

	return db, nil
}

func (this *Instance) setupDAO(db *dbs.DB) {
	dbConfig, err := db.Config()
	if err == nil {
		dbs.GlobalConfig().DBs = map[string]*dbs.DBConfig{
			"dev":  dbConfig,
			"prod": dbConfig,
		}
	}

	{
		models.SharedNodeClusterDAO = models.NewNodeClusterDAO()
		models.SharedNodeClusterDAO.Instance = db
	}

	{
		models.SharedNodeDAO = models.NewNodeDAO()
		models.SharedNodeDAO.Instance = db
	}

	{
		models.SharedNodeTaskDAO = models.NewNodeTaskDAO()
		models.SharedNodeTaskDAO.Instance = db
	}

	{
		models.SharedSysLockerDAO = models.NewSysLockerDAO()
		models.SharedSysLockerDAO.Instance = db
	}

	{
		models.SharedAdminDAO = models.NewAdminDAO()
		models.SharedAdminDAO.Instance = db
	}

	{
		models.SharedAPINodeDAO = models.NewAPINodeDAO()
		models.SharedAPINodeDAO.Instance = db
	}

	{
		models.SharedApiTokenDAO = models.NewApiTokenDAO()
		models.SharedApiTokenDAO.Instance = db
	}

	{
		models.SharedUserDAO = models.NewUserDAO()
		models.SharedUserDAO.Instance = db
	}

	{
		models.SharedUserNodeDAO = models.NewUserNodeDAO()
		models.SharedUserNodeDAO.Instance = db
	}

	{
		models.SharedServerDAO = models.NewServerDAO()
		models.SharedServerDAO.Instance = db
	}

	{
		models.SharedHTTPWebDAO = models.NewHTTPWebDAO()
		models.SharedHTTPWebDAO.Instance = db
	}

	{
		models.SharedHTTPWebsocketDAO = models.NewHTTPWebsocketDAO()
		models.SharedHTTPWebsocketDAO.Instance = db
	}

	{
		models.SharedHTTPLocationDAO = models.NewHTTPLocationDAO()
		models.SharedHTTPLocationDAO.Instance = db
	}

	{
		models.SharedServerGroupDAO = models.NewServerGroupDAO()
		models.SharedServerGroupDAO.Instance = db
	}

	{
		models.SharedReverseProxyDAO = models.NewReverseProxyDAO()
		models.SharedReverseProxyDAO.Instance = db
	}

	{
		models.SharedSysSettingDAO = models.NewSysSettingDAO()
		models.SharedSysSettingDAO.Instance = db
	}
}

func (this *Instance) unzip(role string, version string) error {
	if !regexp.MustCompile(`^\w+$`).MatchString(role) {
		return errors.New("invalid role '" + role + "'")
	}

	var arch = runtime.GOARCH
	if runtime.GOOS != "linux" {
		arch = "amd64"
	}
	var plusTag string
	if this.isPlus() && lists.ContainsString([]string{nodeconfigs.NodeRoleAdmin, nodeconfigs.NodeRoleAPI, nodeconfigs.NodeRoleNode}, role) {
		plusTag = "-plus"
	}

	var zipFile = this.options.SrcDir + "/edge-" + role + "-linux-" + arch + plusTag + "-v" + version + ".zip"
	{
		stat, err := os.Stat(zipFile)
		if err != nil {
			return fmt.Errorf("stat '"+zipFile+"' failed: %w", err)
		}
		if stat.IsDir() {
			return errors.New("'" + zipFile + "' should be a file instead of directory")
		}
	}

	var targetPrefix = this.targetPrefix()

	{
		stat, err := os.Stat(targetPrefix)
		if err != nil || !stat.IsDir() {
			err = os.MkdirAll(targetPrefix, 0777)
			if err != nil {
				return fmt.Errorf("create directory '"+targetPrefix+"' failed: %w", err)
			}
		}
	}

	if this.options.Cacheable {
		var targetDir = targetPrefix + "/edge-" + role
		_, err := os.Stat(targetDir)
		if err == nil {
			return nil
		}
	}

	var unzip = helpers.NewUnzip(zipFile, targetPrefix)
	return unzip.Run()
}

func (this *Instance) targetPrefix() string {
	return this.options.WorkDir + "/usr/local/goedge"
}

func (this *Instance) isPlus() bool {
	return teaconst.Tag == "plus"
}
