package migrations

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"os/user"
	"path"
	"strconv"
	"strings"
	"time"

	"github.com/1Panel-dev/1Panel/agent/app/dto"
	"github.com/1Panel-dev/1Panel/agent/app/dto/request"
	"github.com/1Panel-dev/1Panel/agent/app/model"
	providercatalog "github.com/1Panel-dev/1Panel/agent/app/provider"
	"github.com/1Panel-dev/1Panel/agent/app/service"
	"github.com/1Panel-dev/1Panel/agent/constant"
	"github.com/1Panel-dev/1Panel/agent/global"
	migrationutils "github.com/1Panel-dev/1Panel/agent/init/migration/migrations/utils"
	"github.com/1Panel-dev/1Panel/agent/utils/common"
	"github.com/1Panel-dev/1Panel/agent/utils/copier"
	"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
	"github.com/1Panel-dev/1Panel/agent/utils/firewall"
	"github.com/1Panel-dev/1Panel/agent/utils/ssh"
	"github.com/1Panel-dev/1Panel/agent/utils/xpack"

	"github.com/go-gormigrate/gormigrate/v2"
	"gorm.io/gorm"
)

var AddTable = &gormigrate.Migration{
	ID: "20250930-add-table",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(
			&model.AppDetail{},
			&model.AppInstallResource{},
			&model.AppInstall{},
			&model.AppTag{},
			&model.Tag{},
			&model.App{},
			&model.AppLauncher{},
			&model.OllamaModel{},
			&model.BackupAccount{},
			&model.BackupRecord{},
			&model.Clam{},
			&model.ComposeTemplate{},
			&model.Compose{},
			&model.Cronjob{},
			&model.Database{},
			&model.DatabaseMysql{},
			&model.DatabaseUser{},
			&model.DatabaseUserGrant{},
			&model.DatabaseMongodb{},
			&model.DatabasePostgresql{},
			&model.Favorite{},
			&model.FileShare{},
			&model.Firewall{},
			&model.Host{},
			&model.Ftp{},
			&model.ImageRepo{},
			&model.ScriptLibrary{},
			&model.JobRecords{},
			&model.MonitorBase{},
			&model.MonitorIO{},
			&model.MonitorNetwork{},
			&model.PHPExtensions{},
			&model.Runtime{},
			&model.Setting{},
			&model.Snapshot{},
			&model.Tag{},
			&model.Website{},
			&model.WebsiteAcmeAccount{},
			&model.WebsiteCA{},
			&model.WebsiteTemplate{},
			&model.WebsiteTemplateOutput{},
			&model.WebsiteDnsAccount{},
			&model.WebsiteDomain{},
			&model.WebsiteSSL{},
			&model.Group{},
			&model.AppIgnoreUpgrade{},
			&model.McpServer{},
			&model.RootCert{},
			&model.ClamRecord{},
		)
	},
}

var AddMonitorTable = &gormigrate.Migration{
	ID: "20240813-add-monitor-table",
	Migrate: func(tx *gorm.DB) error {
		return global.MonitorDB.AutoMigrate(
			&model.MonitorBase{},
			&model.MonitorIO{},
			&model.MonitorNetwork{},
		)
	},
}

var InitSetting = &gormigrate.Migration{
	ID: "20240722-init-setting",
	Migrate: func(tx *gorm.DB) error {
		global.CONF.Base.EncryptKey = common.RandStr(16)
		nodeInfo, err := xpack.MultiNodeProvider.LoadNodeInfo(true)
		if err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "BaseDir", Value: nodeInfo.BaseDir}).Error; err != nil {
			return err
		}
		itemKey, _ := encrypt.StringEncrypt(nodeInfo.ServerKey)
		if err := tx.Create(&model.Setting{Key: "ServerKey", Value: itemKey}).Error; err != nil {
			return err
		}
		itemCrt, _ := encrypt.StringEncrypt(nodeInfo.ServerCrt)
		if err := tx.Create(&model.Setting{Key: "ServerCrt", Value: itemCrt}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "NodeScope", Value: nodeInfo.Scope}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "NodePort", Value: fmt.Sprintf("%v", nodeInfo.NodePort)}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "SystemVersion", Value: nodeInfo.Version}).Error; err != nil {
			return err
		}

		if err := tx.Create(&model.Setting{Key: "EncryptKey", Value: global.CONF.Base.EncryptKey}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "DockerSockPath", Value: "unix:///var/run/docker.sock"}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "SystemStatus", Value: "Free"}).Error; err != nil {
			return err
		}
		lang := common.LoadParamsWithoutPanic("LANGUAGE")
		if err := tx.Create(&model.Setting{Key: "Language", Value: lang}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "SystemIP", Value: ""}).Error; err != nil {
			return err
		}

		if err := tx.Create(&model.Setting{Key: "LocalTime", Value: ""}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "TimeZone", Value: common.LoadTimeZoneByCmd()}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "NtpSite", Value: "pool.ntp.org"}).Error; err != nil {
			return err
		}

		if err := tx.Create(&model.Setting{Key: "LastCleanTime", Value: ""}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "LastCleanSize", Value: ""}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "LastCleanData", Value: ""}).Error; err != nil {
			return err
		}

		if err := tx.Create(&model.Setting{Key: "DefaultNetwork", Value: "all"}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "MonitorStatus", Value: constant.StatusEnable}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "MonitorStoreDays", Value: "7"}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "MonitorInterval", Value: "5"}).Error; err != nil {
			return err
		}

		if err := tx.Create(&model.Setting{Key: "ProxyType", Value: ""}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "ProxyUrl", Value: ""}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "ProxyPort", Value: ""}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "ProxyUser", Value: ""}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "ProxyPasswd", Value: ""}).Error; err != nil {
			return err
		}

		if err := tx.Create(&model.Setting{Key: "AppStoreVersion", Value: ""}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "AppStoreSyncStatus", Value: "SyncSuccess"}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "AppStoreLastModified", Value: "0"}).Error; err != nil {
			return err
		}

		if err := tx.Create(&model.Setting{Key: "FileRecycleBin", Value: constant.StatusEnable}).Error; err != nil {
			return err
		}

		if err := tx.Create(&model.Setting{Key: "LocalSSHConn", Value: ""}).Error; err != nil {
			return err
		}

		return nil
	},
}

var InitImageRepo = &gormigrate.Migration{
	ID: "20240722-init-imagerepo",
	Migrate: func(tx *gorm.DB) error {
		item := &model.ImageRepo{
			Name:        "Docker Hub",
			Protocol:    "https",
			DownloadUrl: "docker.io",
			Status:      constant.StatusSuccess,
		}
		if err := tx.Create(item).Error; err != nil {
			return err
		}
		return nil
	},
}

var InitDefaultCA = &gormigrate.Migration{
	ID: "20240722-init-default-ca",
	Migrate: func(tx *gorm.DB) error {
		caService := service.NewIWebsiteCAService()
		if _, err := caService.Create(request.WebsiteCACreate{
			CommonName:       "1Panel-CA",
			Country:          "CN",
			KeyType:          "EC256",
			Name:             "1Panel",
			Organization:     "FIT2CLOUD",
			OrganizationUint: "1Panel",
			Province:         "Beijing",
			City:             "Beijing",
		}); err != nil {
			return err
		}
		return nil
	},
}

var InitPHPExtensions = &gormigrate.Migration{
	ID: "20240722-add-php-extensions",
	Migrate: func(tx *gorm.DB) error {
		phpExtensions := []model.PHPExtensions{
			{
				Name:       "Default",
				Extensions: "bcmath,ftp,gd,gettext,intl,mysqli,pcntl,pdo_mysql,shmop,soap,sockets,sysvsem,xmlrpc,zip",
			},
			{
				Name:       "WordPress",
				Extensions: "exif,igbinary,imagick,intl,zip,apcu,memcached,opcache,redis,shmop,mysqli,pdo_mysql,gd",
			},
			{
				Name:       "Flarum",
				Extensions: "curl,gd,pdo_mysql,mysqli,bz2,exif,yaf,imap",
			},
			{
				Name:       "SeaCMS",
				Extensions: "mysqli,pdo_mysql,gd,curl",
			},
			{
				Name:       "Dev",
				Extensions: "bcmath,ftp,gd,gettext,intl,mysqli,pcntl,pdo_mysql,shmop,soap,sockets,sysvsem,xmlrpc,zip,exif,igbinary,imagick,apcu,memcached,opcache,redis,bc,image,dom,iconv,mbstring,mysqlnd,openssl,pdo,tokenizer,xml,curl,bz2,yaf,imap,xdebug,swoole,pdo_pgsql,fileinfo,pgsql,calendar,gmp",
			},
		}

		for _, ext := range phpExtensions {
			if err := tx.Create(&ext).Error; err != nil {
				return err
			}
		}
		return nil
	},
}

var AddTaskTable = &gormigrate.Migration{
	ID: "20241226-add-task",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(
			&model.Task{},
		)
	},
}

var InitBackup = &gormigrate.Migration{
	ID: "20241226-init-backup",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.Create(&model.BackupAccount{
			Name:       "localhost",
			Type:       "LOCAL",
			BackupPath: path.Join(global.Dir.DataDir, "backup"),
		}).Error; err != nil {
			return err
		}
		return nil
	},
}

var InitDefault = &gormigrate.Migration{
	ID: "20250301-init-default",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.Create(&model.Group{Name: "Default", Type: "website", IsDefault: true}).Error; err != nil {
			return err
		}
		return nil
	},
}

var UpdateWebsiteExpireDate = &gormigrate.Migration{
	ID: "20250304-update-website",
	Migrate: func(tx *gorm.DB) error {
		targetDate := time.Date(9999, 12, 31, 0, 0, 0, 0, time.UTC)

		if err := tx.Model(&model.Website{}).
			Where("expire_date = ?", time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)).
			Update("expire_date", targetDate).Error; err != nil {
			return err
		}
		return nil
	},
}

var UpdateRuntime = &gormigrate.Migration{
	ID: "20250624-update-runtime",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(
			&model.Runtime{},
		)
	},
}

var AddSnapshotRule = &gormigrate.Migration{
	ID: "20250703-add-snapshot-rule",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(
			&model.Cronjob{},
		)
	},
}
var UpdatePHPRuntime = &gormigrate.Migration{
	ID: "20250702-update-php-runtime",
	Migrate: func(tx *gorm.DB) error {
		service.HandleOldPHPRuntime()
		return nil
	},
}
var AddSnapshotIgnore = &gormigrate.Migration{
	ID: "20250716-add-snapshot-ignore",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(
			&model.Snapshot{},
		)
	},
}

var InitAppLauncher = &gormigrate.Migration{
	ID: "20250702-init-app-launcher",
	Migrate: func(tx *gorm.DB) error {
		launchers := []string{"openresty", "mysql", "halo", "redis", "maxkb", "wordpress"}
		for _, val := range launchers {
			var item model.AppLauncher
			_ = tx.Model(&model.AppLauncher{}).Where("key = ?", val).First(&item).Error
			if item.ID == 0 {
				item.Key = val
				_ = tx.Create(&item).Error
			}
		}
		return nil
	},
}

var AddTableAlert = &gormigrate.Migration{
	ID: "20250122-add-table-alert",
	Migrate: func(tx *gorm.DB) error {
		return global.AlertDB.AutoMigrate(&model.Alert{}, &model.AlertTask{}, model.AlertLog{}, model.AlertConfig{})
	},
}

var InitAlertConfig = &gormigrate.Migration{
	ID: "20250705-init-alert-config",
	Migrate: func(tx *gorm.DB) error {
		records := []model.AlertConfig{
			{
				Type:       "sms",
				Title:      "xpack.alert.smsConfig",
				Status:     "Enable",
				Config:     `{"alertDailyNum":50}`,
				CreateUser: "system",
				UpdateUser: "system",
			},
			{
				Type:       "common",
				Title:      "xpack.alert.commonConfig",
				Status:     "Enable",
				Config:     `{"isOffline":"Disable","alertSendTimeRange":{"noticeAlert":{"sendTimeRange":"08:00:00 - 23:59:59","type":["ssl","siteEndTime","panelPwdEndTime","panelUpdate"]},"resourceAlert":{"sendTimeRange":"00:00:00 - 23:59:59","type":["clams","cronJob","cpu","memory","load","disk"]}}}`,
				CreateUser: "system",
				UpdateUser: "system",
			},
		}
		for _, r := range records {
			if err := global.AlertDB.Model(&model.AlertConfig{}).Create(&r).Error; err != nil {
				return err
			}
		}
		return nil
	},
}

var AddMethodToAlertLog = &gormigrate.Migration{
	ID: "20250713-add-method-to-alert_log",
	Migrate: func(tx *gorm.DB) error {
		if err := global.AlertDB.AutoMigrate(&model.AlertLog{}); err != nil {
			return err
		}
		if err := global.AlertDB.Model(&model.AlertLog{}).Where("method IS NULL OR method = ''").Update("method", "sms").Error; err != nil {
			return err
		}
		return nil
	},
}

var AddMethodToAlertTask = &gormigrate.Migration{
	ID: "20250723-add-method-to-alert_task",
	Migrate: func(tx *gorm.DB) error {
		if err := global.AlertDB.AutoMigrate(&model.AlertTask{}); err != nil {
			return err
		}
		if err := global.AlertDB.Model(&model.AlertTask{}).Where("method IS NULL OR method = ''").Update("method", "sms").Error; err != nil {
			return err
		}
		return nil
	},
}

var UpdateMcpServer = &gormigrate.Migration{
	ID: "20250729-update-mcp-server",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.McpServer{}); err != nil {
			return err
		}
		if err := tx.Model(&model.McpServer{}).Where("1=1").Update("output_transport", "sse").Error; err != nil {
			return err
		}
		return nil
	},
}

var InitCronjobGroup = &gormigrate.Migration{
	ID: "20250805-init-cronjob-group",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.Cronjob{}); err != nil {
			return err
		}
		if err := tx.Model(&model.Cronjob{}).Where("1=1").Updates(map[string]interface{}{"group_id": 0}).Error; err != nil {
			return err
		}
		return nil
	},
}

var AddColumnToAlert = &gormigrate.Migration{
	ID: "20250729-add-column-to-alert",
	Migrate: func(tx *gorm.DB) error {
		if err := global.AlertDB.AutoMigrate(&model.Alert{}); err != nil {
			return err
		}
		if err := global.AlertDB.Model(&model.Alert{}).
			Where("advanced_params IS NULL").
			Update("advanced_params", "").Error; err != nil {
			return err
		}
		return nil
	},
}

var MigrateAlertMethodConfigIDs = &gormigrate.Migration{
	ID: "20251001-migrate-alert-method-config-ids",
	Migrate: func(tx *gorm.DB) error {
		if err := global.AlertDB.AutoMigrate(&model.Alert{}, &model.AlertLog{}, &model.AlertTask{}, &model.AlertConfig{}); err != nil {
			return err
		}
		if err := migrateAlertMethodConfigIDs(tx); err != nil {
			return err
		}
		return nil
	},
}

var MigrateAlertLogTaskMethodConfigIDs = &gormigrate.Migration{
	ID: "20260608-migrate-alert-log-task-method-config-ids",
	Migrate: func(tx *gorm.DB) error {
		if err := global.AlertDB.AutoMigrate(&model.AlertLog{}, &model.AlertTask{}, &model.AlertConfig{}); err != nil {
			return err
		}
		if err := migrateAlertMethodRecords(tx, &model.AlertLog{}); err != nil {
			return err
		}
		if err := migrateAlertMethodRecords(tx, &model.AlertTask{}); err != nil {
			return err
		}
		return nil
	},
}

var AddAlertAuditUser = &gormigrate.Migration{
	ID: "20260602-add-alert-audit-user",
	Migrate: func(tx *gorm.DB) error {
		return global.AlertDB.AutoMigrate(&model.Alert{}, &model.AlertConfig{})
	},
}

func migrateAlertMethodConfigIDs(tx *gorm.DB) error {
	if err := tx.Model(&model.AlertConfig{}).Where("type = ?", "mail").Update("type", constant.EmailConfig).Error; err != nil {
		return err
	}

	configIDs, err := loadAlertConfigIDs(tx)
	if err != nil {
		return err
	}

	var alerts []model.Alert
	if err := tx.Find(&alerts).Error; err != nil {
		return err
	}
	for _, alert := range alerts {
		method := migrateAlertMethodValue(alert.Method, alertLegacyMethodTypeMap(), configIDs)
		if method == alert.Method {
			continue
		}
		if err := tx.Model(&model.Alert{}).Where("id = ?", alert.ID).Update("method", method).Error; err != nil {
			return err
		}
	}
	return nil
}

func migrateAlertMethodRecords(tx *gorm.DB, modelValue interface{}) error {
	configIDs, err := loadAlertConfigIDs(tx)
	if err != nil {
		return err
	}

	switch modelValue.(type) {
	case *model.AlertLog:
		var logs []model.AlertLog
		if err := tx.Find(&logs).Error; err != nil {
			return err
		}
		for _, item := range logs {
			method := migrateAlertMethodValue(item.Method, alertLegacyMethodTypeMap(), configIDs)
			if method == item.Method {
				continue
			}
			if err := tx.Model(&model.AlertLog{}).Where("id = ?", item.ID).Update("method", method).Error; err != nil {
				return err
			}
		}
	case *model.AlertTask:
		var tasks []model.AlertTask
		if err := tx.Find(&tasks).Error; err != nil {
			return err
		}
		for _, item := range tasks {
			method := migrateAlertMethodValue(item.Method, alertLegacyMethodTypeMap(), configIDs)
			if method == item.Method {
				continue
			}
			if err := tx.Model(&model.AlertTask{}).Where("id = ?", item.ID).Update("method", method).Error; err != nil {
				return err
			}
		}
	}
	return nil
}

func loadAlertConfigIDs(tx *gorm.DB) (map[string]string, error) {
	configIDs := map[string]string{}
	for _, configType := range alertLegacyMethodTypeMap() {
		if _, ok := configIDs[configType]; ok {
			continue
		}
		var config model.AlertConfig
		if err := tx.Where("type = ?", configType).Order("id ASC").First(&config).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				continue
			}
			return nil, err
		}
		configIDs[configType] = strconv.Itoa(int(config.ID))
	}
	return configIDs, nil
}

func alertLegacyMethodTypeMap() map[string]string {
	return map[string]string{
		"mail":            constant.Email,
		constant.Email:    constant.Email,
		constant.SMS:      constant.SMS,
		constant.Bark:     constant.Bark,
		constant.WeChat:   constant.WeCom,
		constant.WeCom:    constant.WeCom,
		constant.DingTalk: constant.DingTalk,
		constant.FeiShu:   constant.FeiShu,
	}
}

func migrateAlertMethodValue(method string, typeMap map[string]string, configIDs map[string]string) string {
	items := strings.Split(method, ",")
	next := make([]string, 0, len(items))
	seen := map[string]struct{}{}
	for _, item := range items {
		item = strings.TrimSpace(item)
		if item == "" {
			continue
		}
		configType, ok := typeMap[item]
		if ok {
			if id, exists := configIDs[configType]; exists {
				item = id
			}
		}
		if _, exists := seen[item]; exists {
			continue
		}
		seen[item] = struct{}{}
		next = append(next, item)
	}
	return strings.Join(next, ",")
}

var UpdateWebsiteSSL = &gormigrate.Migration{
	ID: "20250819-update-website-ssl",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.WebsiteSSL{}); err != nil {
			return err
		}
		return nil
	},
}

var AddQuickJump = &gormigrate.Migration{
	ID: "20250901-add-quick-jump",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.QuickJump{}); err != nil {
			return err
		}
		if err := tx.Create(&model.QuickJump{Name: "Website", Title: "menu.website", Recommend: 10, IsShow: true, Router: "/websites"}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.QuickJump{Name: "Database", Title: "home.database", Recommend: 30, IsShow: true, Router: "/databases"}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.QuickJump{Name: "Cronjob", Title: "menu.cronjob", Recommend: 50, IsShow: true, Router: "/cronjobs"}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.QuickJump{Name: "AppInstalled", Title: "home.appInstalled", Recommend: 70, IsShow: true, Router: "/apps/installed"}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.QuickJump{Name: "File", Detail: "/", Title: "home.quickDir", Recommend: 90, IsShow: false, Router: "/hosts/files"}).Error; err != nil {
			return err
		}
		return nil
	},
}

var UpdateMcpServerAddType = &gormigrate.Migration{
	ID: "20250904-update-mcp-server",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.McpServer{}); err != nil {
			return err
		}
		if err := tx.Model(&model.McpServer{}).Where("1=1").Update("type", "npx").Error; err != nil {
			return err
		}
		return nil
	},
}

var UpdateMcpServerGatewayConfig = &gormigrate.Migration{
	ID: "20260615-update-mcp-server-gateway-config",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.McpServer{}); err != nil {
			return err
		}
		if err := tx.Model(&model.McpServer{}).Where("gateway_image IS NULL OR gateway_image = ''").Where("type = ?", "uvx").Update("gateway_image", "supercorp/supergateway:uvx").Error; err != nil {
			return err
		}
		if err := tx.Model(&model.McpServer{}).Where("gateway_image IS NULL OR gateway_image = ''").Where("type <> ? OR type IS NULL OR type = ''", "uvx").Update("gateway_image", "supercorp/supergateway:3.4.3").Error; err != nil {
			return err
		}
		if err := tx.Model(&model.McpServer{}).Where("protocol_version IS NULL OR protocol_version = ''").Update("protocol_version", "2025-06-18").Error; err != nil {
			return err
		}
		return nil
	},
}

var InitLocalSSHConn = &gormigrate.Migration{
	ID: "20250905-init-local-ssh",
	Migrate: func(tx *gorm.DB) error {
		itemPath := ""
		currentInfo, _ := user.Current()
		if len(currentInfo.HomeDir) == 0 {
			itemPath = "/root/.ssh/id_ed25519_1panel"
		} else {
			itemPath = path.Join(currentInfo.HomeDir, ".ssh/id_ed25519_1panel")
		}
		if _, err := os.Stat(itemPath); err != nil {
			_ = service.NewISSHService().CreateRootCert(dto.RootCertOperate{EncryptionMode: "ed25519", Name: "id_ed25519_1panel", Description: "1Panel Terminal"})
		}
		privateKey, _ := os.ReadFile(itemPath)
		connWithKey := ssh.ConnInfo{
			Addr:       "127.0.0.1",
			User:       "root",
			Port:       22,
			AuthMode:   "key",
			PrivateKey: privateKey,
		}
		if _, err := ssh.NewClient(connWithKey); err != nil {
			return nil
		}
		var conn model.LocalConnInfo
		_ = copier.Copy(&conn, &connWithKey)
		conn.PrivateKey = string(privateKey)
		conn.PassPhrase = ""
		localConn, _ := json.Marshal(&conn)
		connAfterEncrypt, _ := encrypt.StringEncrypt(string(localConn))
		if err := tx.Model(&model.Setting{}).Where("key = ?", "LocalSSHConn").Updates(map[string]interface{}{"value": connAfterEncrypt}).Error; err != nil {
			return err
		}
		return nil
	},
}

var InitLocalSSHShow = &gormigrate.Migration{
	ID: "20250908-init-local-ssh-show",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.Create(&model.Setting{Key: "LocalSSHConnShow", Value: constant.StatusEnable}).Error; err != nil {
			return err
		}
		return nil
	},
}

var InitRecordStatus = &gormigrate.Migration{
	ID: "20250910-init-record-status",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.BackupRecord{}); err != nil {
			return err
		}
		if err := tx.Model(&model.BackupRecord{}).Where("1 == 1").Updates(map[string]interface{}{"status": constant.StatusSuccess}).Error; err != nil {
			return err
		}
		return nil
	},
}

var AddShowNameForQuickJump = &gormigrate.Migration{
	ID: "20250918-add-show-name-for-quick-jump",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(&model.QuickJump{})
	},
}

var AddAgentQuickJump = &gormigrate.Migration{
	ID: "20260312-add-agent-quick-jump",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.QuickJump{}); err != nil {
			return err
		}

		var quicks []model.QuickJump
		if err := tx.Find(&quicks).Error; err != nil {
			return err
		}

		var (
			cronjob  *model.QuickJump
			database *model.QuickJump
			showList []*model.QuickJump
		)
		for i := range quicks {
			switch quicks[i].Name {
			case "Cronjob":
				cronjob = &quicks[i]
			case "Database":
				database = &quicks[i]
			}
			if quicks[i].IsShow {
				showList = append(showList, &quicks[i])
			}
		}

		showCount := len(showList)
		updatedIDs := make(map[uint]struct{})
		kickedCronjob := false
		if showCount >= 4 && cronjob != nil && cronjob.IsShow {
			cronjob.IsShow = false
			updatedIDs[cronjob.ID] = struct{}{}
			kickedCronjob = true
		}
		if !kickedCronjob && showCount >= 4 && database != nil && database.IsShow {
			database.IsShow = false
			updatedIDs[database.ID] = struct{}{}
		}

		for _, item := range quicks {
			if _, ok := updatedIDs[item.ID]; !ok {
				continue
			}
			if err := tx.Model(&model.QuickJump{}).Where("id = ?", item.ID).Update("is_show", item.IsShow).Error; err != nil {
				return err
			}
		}

		return tx.Create(&model.QuickJump{
			Name:      "Agent",
			Title:     "aiTools.agents.agent",
			Recommend: 1,
			IsShow:    true,
			Router:    "/ai/agents/agent",
		}).Error
	},
}

var AddTimeoutForClam = &gormigrate.Migration{
	ID: "20250922-add-timeout-for-clam",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.Clam{}); err != nil {
			return err
		}
		if err := tx.Model(&model.Clam{}).Where("1 == 1").Updates(map[string]interface{}{"timeout": 18000}).Error; err != nil {
			return err
		}
		return nil
	},
}

var UpdateCronjobSpec = &gormigrate.Migration{
	ID: "20250925-update-cronjob-spec",
	Migrate: func(tx *gorm.DB) error {
		var cronjobs []model.Cronjob
		if err := tx.Where("1 == 1").Find(&cronjobs).Error; err != nil {
			return err
		}
		for _, item := range cronjobs {
			if !strings.Contains(item.Spec, ",") {
				continue
			}
			if err := tx.Model(&model.Cronjob{}).Where("id = ?", item.ID).Updates(
				map[string]interface{}{"spec": strings.ReplaceAll(item.Spec, ",", "&&")}).Error; err != nil {
				return err
			}
		}
		return nil
	},
}

var UpdateWebsiteSSLAddColumn = &gormigrate.Migration{
	ID: "20250928-update-website-ssl",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.WebsiteSSL{}); err != nil {
			return err
		}
		return nil
	},
}

var AddTensorRTLLMModel = &gormigrate.Migration{
	ID: "20251018-add-tensorrt-llm-model",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(&model.TensorRTLLM{})
	},
}

var UpdateMonitorInterval = &gormigrate.Migration{
	ID: "20251026-update-monitor-interval",
	Migrate: func(tx *gorm.DB) error {
		var monitorInterval model.Setting
		if err := tx.Where("key = ?", "MonitorInterval").First(&monitorInterval).Error; err != nil {
			return err
		}
		interval, _ := strconv.Atoi(monitorInterval.Value)
		if interval == 0 {
			interval = 300
		}
		if err := tx.Model(&model.Setting{}).
			Where("key = ?", "MonitorInterval").
			Updates(map[string]interface{}{"value": fmt.Sprintf("%v", interval*60)}).
			Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "DefaultIO", Value: "all"}).Error; err != nil {
			return err
		}
		return nil
	},
}

var AddIptablesFilterRuleTable = &gormigrate.Migration{
	ID: "20251106-add-iptables-filter-rule-table",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.Firewall{}); err != nil {
			return err
		}
		var firewalls []model.Firewall
		_ = tx.Where("1 = 1").Find(&firewalls).Error

		firewallType := ""
		client, err := firewall.NewFirewallClient()
		if err == nil {
			firewallType = client.Name()
		}
		for _, item := range firewalls {
			if err := tx.Model(&model.Firewall{}).
				Where("id = ?", item.ID).
				Updates(map[string]interface{}{"dst_port": item.Port, "src_ip": item.Address, "firewall_type": firewallType}); err != nil {
				global.LOG.Errorf("update firewall failed, err: %v", err)
			}
		}
		return nil
	},
}

var AddMonitorProcess = &gormigrate.Migration{
	ID: "20251030-add-monitor-process",
	Migrate: func(tx *gorm.DB) error {
		return global.MonitorDB.AutoMigrate(&model.MonitorBase{})
	},
}

var UpdateCronJob = &gormigrate.Migration{
	ID: "20251105-update-cronjob",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(&model.Cronjob{})
	},
}

var UpdateTensorrtLLM = &gormigrate.Migration{
	ID: "20251110-update-tensorrt-llm",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(&model.TensorRTLLM{})
	},
}

var AddCommonDescription = &gormigrate.Migration{
	ID: "20251117-add-common-description",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(&model.CommonDescription{})
	},
}

var UpdateDatabase = &gormigrate.Migration{
	ID: "20251117-update-database",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(&model.Database{})
	},
}

var AddGPUMonitor = &gormigrate.Migration{
	ID: "20251122-add-gpu-monitor",
	Migrate: func(tx *gorm.DB) error {
		return global.GPUMonitorDB.AutoMigrate(&model.MonitorGPU{})
	},
}

var UpdateDatabaseMysql = &gormigrate.Migration{
	ID: "20251126-update-database-mysql",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.DatabaseMysql{}); err != nil {
			return err
		}
		var data []model.DatabaseMysql
		_ = tx.Where("1 = 1").Find(&data).Error
		for _, item := range data {
			if len(item.Collation) == 0 {
				collation := ""
				switch item.Format {
				case "utf8":
					collation = "utf8_general_ci"
				case "utf8mb4":
					collation = "utf8mb4_general_ci"
				case "gbk":
					collation = "gbk_chinese_ci"
				case "big5":
					collation = "big5_chinese_ci"
				default:
					collation = "utf8mb4_general_ci"
				}
				_ = tx.Model(&model.DatabaseMysql{}).Where("id = ?", item.ID).Updates(map[string]interface{}{"collation": collation}).Error
			}
		}
		return nil
	},
}

var AddDatabaseMongodb = &gormigrate.Migration{
	ID: "20260413-add-database-mongodb",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(&model.DatabaseMongodb{})
	},
}

var InitIptablesStatus = &gormigrate.Migration{
	ID: "20251201-init-iptables-status",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.Create(&model.Setting{Key: "IptablesStatus", Value: constant.StatusDisable}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "IptablesForwardStatus", Value: constant.StatusDisable}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "IptablesInputStatus", Value: constant.StatusDisable}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "IptablesOutputStatus", Value: constant.StatusDisable}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: constant.FirewallPortWhiteList, Value: constant.FirewallPortWhiteListValue}).Error; err != nil {
			return err
		}
		return nil
	},
}

var InitFirewallPortWhiteList = &gormigrate.Migration{
	ID: "20260601-init-firewall-port-whitelist",
	Migrate: func(tx *gorm.DB) error {
		var setting model.Setting
		if err := tx.Where("key = ?", constant.FirewallPortWhiteList).First(&setting).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return tx.Create(&model.Setting{Key: constant.FirewallPortWhiteList, Value: constant.FirewallPortWhiteListValue}).Error
			}
			return err
		}
		return nil
	},
}

var UpdateWebsite = &gormigrate.Migration{
	ID: "20251203-update-website",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(&model.Website{})
	},
}

var AddisIPtoWebsiteSSL = &gormigrate.Migration{
	ID: "20251223-update-website-ssl",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(&model.WebsiteSSL{})
	},
}

var InitPingStatus = &gormigrate.Migration{
	ID: "20251201-init-ping-status",
	Migrate: func(tx *gorm.DB) error {
		status := firewall.LoadPingStatus()
		if err := tx.Create(&model.Setting{Key: "BanPing", Value: status}).Error; err != nil {
			return err
		}
		return nil
	},
}

var UpdateApp = &gormigrate.Migration{
	ID: "20251228-update-app",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(&model.App{})
	},
}

var AddCronjobArgs = &gormigrate.Migration{
	ID: "20260106-add-cronjob-args",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(&model.Cronjob{})
	},
}

var AddWebsiteAcmeAccountColumn = &gormigrate.Migration{
	ID: "20260110-add-website-acme-account",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(&model.WebsiteAcmeAccount{})
	},
}

var AddAgentTables = &gormigrate.Migration{
	ID: "20260205-add-agent-tables",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(
			&model.Agent{},
			&model.AgentAccount{},
		)
	},
}

var AddAgentCustomModelFields = &gormigrate.Migration{
	ID: "20260224-add-agent-custom-model-fields",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.Agent{}, &model.AgentAccount{}); err != nil {
			return err
		}
		if err := tx.Model(&model.AgentAccount{}).Where("api_type = '' OR api_type IS NULL").Update("api_type", "openai-completions").Error; err != nil {
			return err
		}
		if err := tx.Model(&model.Agent{}).Where("api_type = '' OR api_type IS NULL").Update("api_type", "openai-completions").Error; err != nil {
			return err
		}
		return nil
	},
}

var AddAppInstallSortOrder = &gormigrate.Migration{
	ID: "20260222-add-app-install-sort-order",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(&model.AppInstall{})
	},
}

var AddAgentAccountRememberAPIKey = &gormigrate.Migration{
	ID: "20260225-add-agent-account-remember-api-key",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.AgentAccount{}); err != nil {
			return err
		}
		return tx.Model(&model.AgentAccount{}).Where("remember_api_key IS NULL").Update("remember_api_key", true).Error
	},
}

var AddEditionSetting = &gormigrate.Migration{
	ID: "20260224-add-edition-setting",
	Migrate: func(tx *gorm.DB) error {
		var setting model.Setting
		edition := common.LoadParamsWithoutPanic("PANEL_EDITION")
		if err := tx.Where("key = ?", "Edition").First(&setting).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return tx.Create(&model.Setting{Key: "Edition", Value: edition}).Error
			}
			return err
		}
		if setting.Value == "" {
			return tx.Model(&model.Setting{}).Where("key = ?", "Edition").Update("value", edition).Error
		}
		return nil
	},
}

var AddAgentTypeForAgents = &gormigrate.Migration{
	ID: "20260302-add-agent-type-for-agents",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.Agent{}); err != nil {
			return err
		}
		if err := tx.Model(&model.Agent{}).Where("agent_type = '' OR agent_type IS NULL").Update("agent_type", constant.AppOpenclaw).Error; err != nil {
			return err
		}
		return tx.Exec(
			"UPDATE agents SET agent_type = ? WHERE app_install_id IN (SELECT ai.id FROM app_installs ai JOIN apps a ON ai.app_id = a.id WHERE a.key = ?)",
			constant.AppCopaw,
			constant.AppCopaw,
		).Error
	},
}

var NormalizeAgentAccountVerifiedStatus = &gormigrate.Migration{
	ID: "20260303-normalize-agent-account-verified-status",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.AgentAccount{}); err != nil {
			return err
		}
		return tx.Model(&model.AgentAccount{}).
			Where("provider IN ?", []string{"custom", "ollama", "kimi-coding"}).
			Update("verified", false).Error
	},
}

var NormalizeOllamaAccountAPIType = &gormigrate.Migration{
	ID: "20260304-normalize-ollama-account-api-type",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.AgentAccount{}); err != nil {
			return err
		}
		return tx.Model(&model.AgentAccount{}).
			Where("provider = ?", "ollama").
			Update("api_type", "openai-responses").Error
	},
}

var InitAgentAccountModelPool = &gormigrate.Migration{
	ID: "20260319-init-agent-account-model-pool",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.AgentAccountModel{}); err != nil {
			return err
		}
		return migrationutils.MigrateAgentAccountModelPool(tx)
	},
}

var AddAgentAccountMasterID = &gormigrate.Migration{
	ID: "20260401-add-agent-account-master-id",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(&model.AgentAccount{})
	},
}

var NormalizeAgentAccountModelIDs = &gormigrate.Migration{
	ID: "20260716-normalize-agent-account-model-ids",
	Migrate: func(tx *gorm.DB) error {
		return migrationutils.NormalizeAgentAccountModelIDs(tx)
	},
}

var AddAgentAccountVerifyModel = &gormigrate.Migration{
	ID: "20260716-add-agent-account-verify-model",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.AgentAccount{}); err != nil {
			return err
		}
		var accounts []model.AgentAccount
		if err := tx.Where("verify_model = '' OR verify_model IS NULL").Find(&accounts).Error; err != nil {
			return err
		}
		for _, account := range accounts {
			var accountModel model.AgentAccountModel
			err := tx.Where("account_id = ?", account.ID).Order("sort_order ASC, id ASC").First(&accountModel).Error
			if errors.Is(err, gorm.ErrRecordNotFound) {
				continue
			}
			if err != nil {
				return err
			}
			if err := tx.Model(&model.AgentAccount{}).Where("id = ?", account.ID).Update("verify_model", accountModel.Model).Error; err != nil {
				return err
			}
		}
		return nil
	},
}

var AddAgentAccountAuthMode = &gormigrate.Migration{
	ID: "20260716-add-agent-account-auth-mode",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.AgentAccount{}); err != nil {
			return err
		}
		if err := tx.Model(&model.AgentAccount{}).
			Where("api_type = ? AND (auth_mode IS NULL OR auth_mode = '') AND provider IN ?", "anthropic-messages", []string{"bailian-coding-plan", "ark-coding-plan", "xiaomi"}).
			Update("auth_mode", providercatalog.AuthModeBearer).Error; err != nil {
			return err
		}
		return tx.Model(&model.AgentAccount{}).
			Where("api_type = ? AND (auth_mode IS NULL OR auth_mode = '')", "anthropic-messages").
			Update("auth_mode", providercatalog.AuthModeXAPIKey).Error
	},
}

var AddHostTable = &gormigrate.Migration{
	ID: "20260318-add-host-table",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.Host{}); err != nil {
			return err
		}
		if global.CoreDB == nil || !global.CoreDB.Migrator().HasTable("hosts") {
			if err := tx.Create(&model.Group{Name: "Default", Type: "host", IsDefault: true}).Error; err != nil {
				return err
			}
			return nil
		}

		var encryptSetting model.Setting
		if err := global.CoreDB.Where("key = ?", "EncryptKey").First(&encryptSetting).Error; err != nil {
			global.LOG.Errorf("failed to get encrypt key from core db, err: %v", err)
			return nil
		}
		coreEncryptKey := strings.TrimSpace(encryptSetting.Value)
		if coreEncryptKey == "" {
			global.LOG.Error("encrypt key from core db is empty")
			return nil
		}

		groupIDMap := make(map[uint]uint)
		defaultGroupID := uint(0)
		var coreGroups []model.Group
		if err := global.CoreDB.Where("type = ?", "host").Order("id asc").Find(&coreGroups).Error; err != nil {
			return err
		}
		for _, coreGroup := range coreGroups {
			agentGroup := model.Group{
				Name:      coreGroup.Name,
				Type:      "host",
				IsDefault: coreGroup.IsDefault,
			}
			if agentGroup.IsDefault {
				defaultGroupID = coreGroup.ID
			}
			if err := tx.Create(&agentGroup).Error; err != nil {
				global.LOG.Errorf("failed to create group, group id: %v, err: %v", coreGroup.ID, err)
				continue
			}
			groupIDMap[coreGroup.ID] = agentGroup.ID
		}

		var coreHosts []model.Host
		if err := global.CoreDB.Order("id asc").Find(&coreHosts).Error; err != nil {
			return err
		}
		for _, coreHost := range coreHosts {
			password, err := encrypt.StringDecryptWithKey(coreHost.Password, coreEncryptKey)
			if err != nil {
				global.LOG.Errorf("failed to decrypt host password, host id: %v, err: %v", coreHost.ID, err)
				continue
			}
			privateKey, err := encrypt.StringDecryptWithKey(coreHost.PrivateKey, coreEncryptKey)
			if err != nil {
				global.LOG.Errorf("failed to decrypt host private key, host id: %v, err: %v", coreHost.ID, err)
				continue
			}
			passPhrase, err := encrypt.StringDecryptWithKey(coreHost.PassPhrase, coreEncryptKey)
			if err != nil {
				global.LOG.Errorf("failed to decrypt host pass phrase, host id: %v, err: %v", coreHost.ID, err)
				continue
			}

			encryptedPassword, err := encrypt.StringEncrypt(password)
			if err != nil {
				global.LOG.Errorf("failed to encrypt host password, host id: %v, err: %v", coreHost.ID, err)
				continue
			}
			encryptedPrivateKey, err := encrypt.StringEncrypt(privateKey)
			if err != nil {
				global.LOG.Errorf("failed to encrypt host private key, host id: %v, err: %v", coreHost.ID, err)
				continue
			}
			encryptedPassPhrase, err := encrypt.StringEncrypt(passPhrase)
			if err != nil {
				global.LOG.Errorf("failed to encrypt host pass phrase, host id: %v, err: %v", coreHost.ID, err)
				continue
			}

			groupID := defaultGroupID
			if mappedGroupID, ok := groupIDMap[coreHost.GroupID]; ok && mappedGroupID != 0 {
				groupID = mappedGroupID
			}
			host := model.Host{
				GroupID:          groupID,
				Name:             coreHost.Name,
				Addr:             coreHost.Addr,
				Port:             coreHost.Port,
				User:             coreHost.User,
				AuthMode:         coreHost.AuthMode,
				Password:         encryptedPassword,
				PrivateKey:       encryptedPrivateKey,
				PassPhrase:       encryptedPassPhrase,
				RememberPassword: coreHost.RememberPassword,
				Description:      coreHost.Description,
			}
			if err := tx.Create(&host).Error; err != nil {
				global.LOG.Errorf("failed to create host, host id: %v, err: %v", coreHost.ID, err)
				continue
			}
		}
		return nil
	},
}

var AddAITerminalSettings = &gormigrate.Migration{
	ID: "20260318-add-ai-terminal-settings",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.Create(&model.Setting{Key: "AIStatus", Value: constant.StatusDisable}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "AIAccountID", Value: ""}).Error; err != nil {
			return err
		}
		if err := tx.Create(&model.Setting{Key: "AIPrefix", Value: constant.DefaultTerminalAIPrefix}).Error; err != nil {
			return err
		}
		return tx.Create(&model.Setting{
			Key:   "AIRiskCommands",
			Value: constant.DefaultTerminalAIRiskCommands,
		}).Error
	},
}

var UpdateAgentQuickJumpTitle = &gormigrate.Migration{
	ID: "20260324-update-agent-quick-jump-title",
	Migrate: func(tx *gorm.DB) error {
		return tx.Model(&model.QuickJump{}).
			Where("title = ?", "aiTools.agents.agents").
			Update("title", "aiTools.agents.agent").Error
	},
}

var FixOpenclaw20260323HTTPPort = &gormigrate.Migration{
	ID: "20260325-fix-openclaw-20260323-http-port",
	Migrate: func(tx *gorm.DB) error {
		return tx.Exec(
			`UPDATE app_installs
SET http_port = https_port,
    https_port = 0
WHERE version = ?
  AND https_port > 0
  AND app_id IN (SELECT id FROM apps WHERE key = ?)`,
			"2026.3.24",
			constant.AppOpenclaw,
		).Error
	},
}

var AddAgentRemarkColumn = &gormigrate.Migration{
	ID: "20260330-add-agent-remark-column",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(&model.Agent{})
	},
}

var AddAgentWebsiteBinding = &gormigrate.Migration{
	ID: "20260403-add-agent-website-binding",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.Agent{}); err != nil {
			return err
		}

		var agents []model.Agent
		if err := tx.Find(&agents).Error; err != nil {
			return err
		}
		if len(agents) == 0 {
			return nil
		}

		var websites []model.Website
		if err := tx.Where("type = ? AND app_install_id > 0", constant.Deployment).Find(&websites).Error; err != nil {
			return err
		}
		websiteMap := service.UniqueDeploymentWebsiteMapByAppInstall(websites)
		for _, agent := range agents {
			if agent.WebsiteID != 0 || agent.AppInstallID == 0 {
				continue
			}
			website, ok := websiteMap[agent.AppInstallID]
			if !ok {
				continue
			}
			if err := tx.Model(&model.Agent{}).Where("id = ?", agent.ID).Update("website_id", website.ID).Error; err != nil {
				return err
			}
		}
		return nil
	},
}

var AddFileManageAISettings = &gormigrate.Migration{
	ID: "20260330-add-file-manage-ai-settings",
	Migrate: func(tx *gorm.DB) error {
		rows := []model.Setting{
			{Key: "FileAIStatus", Value: constant.StatusDisable},
			{Key: "FileAIAccountID", Value: ""},
		}
		for i := range rows {
			var exist model.Setting
			if err := tx.Where("`key` = ?", rows[i].Key).First(&exist).Error; err != nil {
				if errors.Is(err, gorm.ErrRecordNotFound) {
					if err := tx.Create(&rows[i]).Error; err != nil {
						return err
					}
				} else {
					return err
				}
			}
		}
		return nil
	},
}

var AddFileShareTable = &gormigrate.Migration{
	ID: "20260410-add-file-share-table",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(&model.FileShare{})
	},
}

var AddFileHistoryTable = &gormigrate.Migration{
	ID: "20260414-add-file-history-table",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.FileHistory{}); err != nil {
			return err
		}
		defaultRows := []model.Setting{
			{Key: "FileHistoryStatus", Value: constant.StatusEnable},
			{Key: "FileHistoryMaxPerPath", Value: "20"},
			{Key: "FileHistoryDiskQuotaMB", Value: "1024"},
		}
		for i := range defaultRows {
			var exist model.Setting
			if err := tx.Where("`key` = ?", defaultRows[i].Key).First(&exist).Error; err != nil {
				if errors.Is(err, gorm.ErrRecordNotFound) {
					if err := tx.Create(&defaultRows[i]).Error; err != nil {
						return err
					}
				} else {
					return err
				}
			}
		}
		return nil
	},
}

func loadDatabaseUserTypesForMigration(tx *gorm.DB, mysqlName string) []string {
	var database model.Database
	if err := tx.Where("name = ?", mysqlName).First(&database).Error; err == nil && len(database.Type) != 0 {
		return []string{database.Type}
	}

	var appKey string
	_ = tx.Table("app_installs").
		Select("apps.`key`").
		Joins("JOIN apps ON apps.id = app_installs.app_id").
		Where("app_installs.name = ?", mysqlName).
		Scan(&appKey).Error
	switch appKey {
	case constant.AppMariaDB, constant.AppMysqlCluster:
		return []string{appKey}
	case constant.AppMysql:
		return []string{constant.AppMysql}
	default:
		return []string{constant.AppMysql, constant.AppMariaDB}
	}
}

func normalizeDatabaseUserHostsForMigration(permission string) []string {
	hostSet := make(map[string]struct{})
	for _, host := range strings.Split(permission, ",") {
		host = strings.TrimSpace(host)
		if len(host) == 0 {
			continue
		}
		hostSet[host] = struct{}{}
	}
	if len(hostSet) == 0 {
		return []string{"%"}
	}
	hosts := make([]string, 0, len(hostSet))
	for host := range hostSet {
		hosts = append(hosts, host)
	}
	return hosts
}

func isDatabaseSystemUserForMigration(username string) bool {
	switch strings.ToLower(username) {
	case "root",
		"mysql.session", "mysql.sys", "mysql.infoschema", "mysqlxsys",
		"mariadb.sys", "mariadb-sys",
		"debian-sys-maint":
		return true
	default:
		return false
	}
}

var AddDatabaseUserTable = &gormigrate.Migration{
	ID: "20260703-add-database-user-table",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.DatabaseUser{}, &model.DatabaseUserGrant{}); err != nil {
			return err
		}
		var mysqls []model.DatabaseMysql
		if err := tx.Find(&mysqls).Error; err != nil {
			return err
		}
		for _, item := range mysqls {
			if len(item.Username) == 0 || isDatabaseSystemUserForMigration(item.Username) {
				continue
			}
			for _, dbType := range loadDatabaseUserTypesForMigration(tx, item.MysqlName) {
				if len(dbType) == 0 {
					continue
				}
				for _, host := range normalizeDatabaseUserHostsForMigration(item.Permission) {
					user := model.DatabaseUser{
						Type:     dbType,
						Database: item.MysqlName,
						Username: item.Username,
						Host:     host,
						Password: item.Password,
					}
					if err := tx.Where("`type` = ? AND database = ? AND username = ? AND host = ?", dbType, item.MysqlName, item.Username, host).FirstOrCreate(&user).Error; err != nil {
						return err
					}
					grant := model.DatabaseUserGrant{
						Type:     dbType,
						Database: item.MysqlName,
						DBName:   item.Name,
						Username: item.Username,
						Host:     host,
					}
					if err := tx.Where("`type` = ? AND database = ? AND db_name = ? AND username = ? AND host = ?", dbType, item.MysqlName, item.Name, item.Username, host).FirstOrCreate(&grant).Error; err != nil {
						return err
					}
				}
			}
		}
		return nil
	},
}

// MigrateLegoV5 normalizes data persisted under lego v4 so that lego v5 can read it.
//
// Two things changed in lego v5 that affect existing rows:
//
//  1. certcrypto.KeyType string values were renamed:
//     "P256"->"EC256", "P384"->"EC384",
//     "2048"->"RSA2048", "3072"->"RSA3072", "4096"->"RSA4096", "8192"->"RSA8192".
//     We update every key_type column we own to the new form.
//
//  2. DnsPod provider was removed from upstream lego v5; the frontend already
//     marked it deprecated. Existing DnsPod website_dns_accounts rows are kept
//     so the user can decide what to do, but any website_ssls still pointing
//     at a DnsPod account would fail to renew. We log a warning row count and
//     leave deletion to the operator.
var MigrateLegoV5 = &gormigrate.Migration{
	ID: "20260523-migrate-lego-v5",
	Migrate: func(tx *gorm.DB) error {
		keyTypeMap := map[string]string{
			"P256": "EC256",
			"P384": "EC384",
			"2048": "RSA2048",
			"3072": "RSA3072",
			"4096": "RSA4096",
			"8192": "RSA8192",
		}
		for old, neu := range keyTypeMap {
			if err := tx.Model(&model.WebsiteAcmeAccount{}).
				Where("key_type = ?", old).
				Update("key_type", neu).Error; err != nil {
				return fmt.Errorf("migrate WebsiteAcmeAccount.key_type %s->%s: %w", old, neu, err)
			}
			if err := tx.Model(&model.WebsiteSSL{}).
				Where("key_type = ?", old).
				Update("key_type", neu).Error; err != nil {
				return fmt.Errorf("migrate WebsiteSSL.key_type %s->%s: %w", old, neu, err)
			}
			if err := tx.Model(&model.WebsiteCA{}).
				Where("key_type = ?", old).
				Update("key_type", neu).Error; err != nil {
				return fmt.Errorf("migrate WebsiteCA.key_type %s->%s: %w", old, neu, err)
			}
		}

		var dnsPodCount int64
		_ = tx.Model(&model.WebsiteDnsAccount{}).Where("type = ?", "DnsPod").Count(&dnsPodCount).Error
		if dnsPodCount > 0 {
			global.LOG.Warnf("lego v5 removed the DnsPod provider; %d existing DnsPod DNS account(s) will not be usable for renewal -- please switch them to TencentCloud", dnsPodCount)
		}

		return nil
	},
}

var AddMcpServerGatewayArgs = &gormigrate.Migration{
	ID: "20260714-add-mcp-server-gateway-args",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(&model.McpServer{})
	},
}

var AddBackupRecordArgs = &gormigrate.Migration{
	ID: "20260729-add-backup-record-args",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(&model.BackupRecord{})
	},
}

var AddFtpIdentity = &gormigrate.Migration{
	ID: "20260729-add-ftp-identity",
	Migrate: func(tx *gorm.DB) error {
		if err := tx.AutoMigrate(&model.Ftp{}); err != nil {
			return err
		}
		return tx.Model(&model.Ftp{}).
			Where("1 = 1").
			Updates(map[string]interface{}{
				"uid": constant.WebsiteUID,
				"gid": constant.WebsiteGID,
			}).Error
	},
}

var AddWebsiteTemplateTable = &gormigrate.Migration{
	ID: "20260728-add-website-template-table",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(
			&model.WebsiteTemplate{},
			&model.WebsiteTemplateOutput{},
		)
	},
}

var AddComposePinned = &gormigrate.Migration{
	ID: "20260729-add-compose-pinned",
	Migrate: func(tx *gorm.DB) error {
		return tx.AutoMigrate(&model.Compose{})
	},
}
