// SPDX-FileCopyrightText: 2018 - 2022 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"math/rand"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"slices"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/godbus/dbus/v5"
	"github.com/linuxdeepin/go-lib/gettext"
	"github.com/linuxdeepin/go-lib/utils"
	"github.com/linuxdeepin/lastore-daemon/src/internal/system"
	"github.com/linuxdeepin/lastore-daemon/src/internal/system/apt"
	"github.com/linuxdeepin/lastore-daemon/src/internal/system/dut"
	"github.com/linuxdeepin/lastore-daemon/src/internal/updateplatform"
	debVersion "pault.ag/go/debian/version"
)

func prepareUpdateSource() {
	partialFilePaths := []string{
		"/var/lib/apt/lists/partial",
		"/var/lib/lastore/lists/partial",
		"/var/cache/apt/archives/partial",
		"/var/cache/lastore/archives/partial",
	}
	for _, partialFilePath := range partialFilePaths {
		infos, err := os.ReadDir(partialFilePath)
		if err != nil {
			continue
		}
		for _, info := range infos {
			err = os.RemoveAll(filepath.Join(partialFilePath, info.Name()))
			if err != nil {
				logger.Warning(err)
			}
		}
	}

}

func (m *Manager) beforeUpdateSourceEnvCheck() (bool, string) {
	supportArchs, err := system.SystemArchitectures()
	if err != nil {
		m.updatePlatform.PostProcessEventMessage(updateplatform.ProcessEvent{
			TaskID:       1,
			EventType:    updateplatform.CheckEnv,
			EventStatus:  false,
			EventContent: err.Error(),
		})
		return false, err.Error()
	}
	installedMap, err := loadPkgStatusVersion()
	if err != nil {
		m.updatePlatform.PostProcessEventMessage(updateplatform.ProcessEvent{
			TaskID:       1,
			EventType:    updateplatform.CheckEnv,
			EventStatus:  false,
			EventContent: err.Error(),
		})
		return false, err.Error()
	}

	cmd := exec.Command("dpkg", "--audit")
	if err := cmd.Run(); err != nil {
		m.updatePlatform.PostProcessEventMessage(updateplatform.ProcessEvent{
			TaskID:       1,
			EventType:    updateplatform.CheckEnv,
			EventStatus:  false,
			EventContent: err.Error(),
		})
		return false, err.Error()
	}

	var errMsgBuilder strings.Builder
	for pkgName, installedPkg := range installedMap {
		if installedPkg.status != "ii" {
			errMsgBuilder.WriteString(fmt.Sprintf("package %s is not installed\n", pkgName))
			continue
		}

		if installedPkg.architecture != "all" && !slices.ContainsFunc(supportArchs, func(arch system.Architecture) bool {
			return installedPkg.architecture == string(arch)
		}) {
			errMsgBuilder.WriteString(fmt.Sprintf("%s architecture %s is not supported\n", pkgName, installedPkg.architecture))
		}
	}

	coreFiles := []string{
		"/etc/os-version",
	}

	for _, coreFile := range coreFiles {
		if !utils.IsFileExist(coreFile) {
			errMsgBuilder.WriteString(fmt.Sprintf("file %s does not exist\n", coreFile))
		}
	}
	errMsg := errMsgBuilder.String()

	status := true
	if len(errMsg) > 0 {
		status = false
	}
	m.updatePlatform.PostProcessEventMessage(updateplatform.ProcessEvent{
		TaskID:       1,
		EventType:    updateplatform.CheckEnv,
		EventStatus:  status,
		EventContent: errMsg,
	})
	return status, errMsg
}

// updateSource 检查更新主要步骤:1.从更新平台获取数据并解析;2.apt update;3.最终可更新内容确定(模拟安装的方式);4.数据上报;
// 任务进度划分: 0-10%-80%-90%-100%
func (m *Manager) updateSource(sender dbus.Sender) (*Job, error) {
	var err error
	var environ map[string]string
	if !system.IsAuthorized() {
		return nil, errors.New("not authorized, don't allow to exec update")
	}

	if m.ImmutableAutoRecovery {
		logger.Info("immutable auto recovery is enabled, don't allow to exec update")
		return nil, errors.New("immutable auto recovery is enabled, don't allow to exec update")
	}

	defer func() {
		if err == nil {
			err1 := m.config.UpdateLastCheckTime()
			if err1 != nil {
				logger.Warning(err1)
			}
		}
		if err := m.updateAutoCheckSystemUnit(); err != nil {
			logger.Warning(err)
		}
	}()

	environ, err = makeEnvironWithSender(m, sender)
	if err != nil {
		return nil, err
	}

	_ = os.Setenv("http_proxy", environ["http_proxy"])
	_ = os.Setenv("https_proxy", environ["https_proxy"])
	// 检查任务开始后,从更新平台获取仓库、更新注记等信息
	// 从更新平台获取数据:系统更新和安全更新流程都包含
	if err := m.updatePlatform.GenUpdatePolicyByToken(); err != nil {
		if m.config.PlatformUpdate {
			return nil, &system.JobError{
				ErrType:   system.ErrorPlatformUnreachable,
				ErrDetail: "failed to get update policy by token" + err.Error(),
			}
		} else {
			logger.Warningf("updatePlatform gen token failed: %v", err)
		}
	}

	prepareUpdateSource()
	m.reloadOemConfig(true)
	m.updatePlatform.Token = updateplatform.UpdateTokenConfigFile(m.config.IncludeDiskInfo, m.config.GetHardwareIdByHelper)
	m.jobManager.dispatch() // 解决 bug 59351问题（防止CreatJob获取到状态为end但是未被删除的job）
	var job *Job
	var isExist bool
	var updateType system.UpdateType
	if m.config.IntranetUpdate {
		updateType = system.SystemUpdate //Intranet updates are limited to system updates only
	} else {
		updateType = system.AllCheckUpdate
	}
	err = system.CustomSourceWrapper(updateType, func(path string, unref func()) error {
		m.do.Lock()
		defer m.do.Unlock()
		isExist, job, err = m.jobManager.CreateJob("", system.UpdateSourceJobType, nil, environ, nil)
		if err != nil {
			logger.Warningf("UpdateSource error: %v\n", err)
			if unref != nil {
				unref()
			}
			return err
		}
		if isExist {
			if unref != nil {
				unref()
			}
			logger.Info(JobExistError)
			return JobExistError
		}
		// 设置apt命令参数
		info, err := os.Stat(path)
		if err != nil {
			if unref != nil {
				unref()
			}
			return err
		}
		if info.IsDir() {
			job.option = map[string]string{
				"Dir::Etc::SourceList":  "/dev/null",
				"Dir::Etc::SourceParts": path,
			}
		} else {
			job.option = map[string]string{
				"Dir::Etc::SourceList":  path,
				"Dir::Etc::SourceParts": "/dev/null",
			}
		}
		job.subRetryHookFn = func(j *Job) {
			handleUpdateSourceFailed(j)
		}
		job.setPreHooks(map[string]func() error{
			string(system.RunningStatus): func() error {
				// 检查更新需要重置备份状态,主要是处理备份失败后再检查更新,会直接显示失败的场景
				m.statusManager.SetABStatus(system.AllCheckUpdate, system.NotBackup, system.NoABError)
				return nil
			},
			string(system.SucceedStatus): func() error {
				job.setUpdatePolicy(m.updatePlatform.Tp)
				m.refreshUpdateInfos(true)
				m.PropsMu.Lock()
				m.updateSourceOnce = true
				m.PropsMu.Unlock()
				if len(m.UpgradableApps) > 0 {
					go m.reportLog(updateStatusReport, true, "")
					// 开启自动下载时触发自动下载,发自动下载通知,不发送可更新通知;
					// 关闭自动下载时,发可更新的通知;
					if !m.updater.AutoDownloadUpdates {
						// msg := gettext.Tr("New system edition available")
						msg := gettext.Tr("New version available!")
						action := []string{"view", gettext.Tr("View")}
						var hints map[string]dbus.Variant
						if m.config.IntranetUpdate {
							hints = map[string]dbus.Variant{"x-deepin-action-view": dbus.MakeVariant("dde-control-center,-m,updateprivate")}
							if m.updatePlatform.Tp == updateplatform.NormalUpdate {
								msg = gettext.Tr("New version available! Please go to control-center to check")
								go m.sendNotify(updateNotifyShowOptional, 0, "preferences-system", "", msg, action, hints, system.NotifyExpireTimeoutPrivate)
							}
						} else {
							hints = map[string]dbus.Variant{"x-deepin-action-view": dbus.MakeVariant("dde-control-center,-m,update")}
							go m.sendNotify(updateNotifyShowOptional, 0, "preferences-system", "", msg, action, hints, system.NotifyExpireTimeoutDefault)
						}
					}
				} else {
					go m.reportLog(updateStatusReport, false, "")
				}
				go func() {
					m.inhibitAutoQuitCountAdd()
					defer m.inhibitAutoQuitCountSub()
					msgContent := "update source success"
					m.updatePlatform.PostStatusMessage(updateplatform.StatusMessage{
						Type:   "info",
						Detail: msgContent,
					}, false)

					m.updatePlatform.PostProcessEventMessage(updateplatform.ProcessEvent{
						TaskID:       1,
						EventType:    updateplatform.GetUpdateEvent,
						EventStatus:  true,
						EventContent: msgContent,
					})
				}()
				m.updatePlatform.SaveCache(m.config)
				job.setPropProgress(1.0)

				checkType := dut.PostUpdateCheck
				if systemErr := dut.CheckSystem(checkType, nil); systemErr != nil {
					logger.Warning(systemErr)
					go func(err *system.JobError) {
						m.updatePlatform.PostProcessEventMessage(updateplatform.ProcessEvent{
							TaskID:       1,
							EventType:    updateplatform.PostUpdateCheck,
							EventStatus:  false,
							EventContent: err.ErrDetail,
						})
					}(systemErr)
				} else {
					go m.updatePlatform.PostProcessEventMessage(updateplatform.ProcessEvent{
						TaskID:       1,
						EventType:    updateplatform.PostUpdateCheck,
						EventStatus:  true,
						EventContent: fmt.Sprintf("%v success", checkType),
					})
				}

				return nil
			},
			string(system.FailedStatus): func() error {
				// 网络问题检查更新失败和空间不足下载索引失败,需要发通知
				var errorContent system.JobError
				err = json.Unmarshal([]byte(job.Description), &errorContent)
				if err == nil {
					if strings.Contains(errorContent.ErrType.String(), system.ErrorFetchFailed.String()) || strings.Contains(errorContent.ErrType.String(), system.ErrorIndexDownloadFailed.String()) {
						msg := gettext.Tr("Failed to check for updates. Please check your network.")
						action := []string{"view", gettext.Tr("View")}
						hints := map[string]dbus.Variant{"x-deepin-action-view": dbus.MakeVariant("dde-control-center,-m,network")}
						go m.sendNotify(updateNotifyShowOptional, 0, "preferences-system", "", msg, action, hints, system.NotifyExpireTimeoutDefault)
					}
					if strings.Contains(errorContent.ErrType.String(), system.ErrorInsufficientSpace.String()) {
						msg := gettext.Tr("Failed to check for updates. Please clean up your disk first.")
						go m.sendNotify(updateNotifyShowOptional, 0, "preferences-system", "", msg, nil, nil, system.NotifyExpireTimeoutDefault)
					}
				}
				// 发通知 end
				go func() {
					m.inhibitAutoQuitCountAdd()
					defer m.inhibitAutoQuitCountSub()
					m.reportLog(updateStatusReport, false, job.Description)
					msg := fmt.Sprintf("apt-get update failed, detail is %v , option is %+v", job.Description, job.option)
					m.updatePlatform.PostStatusMessage(updateplatform.StatusMessage{
						Type:           "error",
						JobDescription: job.Description,
						Detail:         msg,
					}, false)
					m.updatePlatform.PostProcessEventMessage(updateplatform.ProcessEvent{
						TaskID:       1,
						EventType:    updateplatform.GetUpdateEvent,
						EventStatus:  false,
						EventContent: msg,
					})
				}()

				checkType := dut.PostUpdateCheck
				if systemErr := dut.CheckSystem(checkType, nil); systemErr != nil {
					logger.Warning(systemErr)
					go func(err *system.JobError) {
						m.updatePlatform.PostProcessEventMessage(updateplatform.ProcessEvent{
							TaskID:       1,
							EventType:    updateplatform.PostUpdateCheck,
							EventStatus:  false,
							EventContent: err.ErrDetail,
						})
					}(systemErr)
				} else {
					go m.updatePlatform.PostProcessEventMessage(updateplatform.ProcessEvent{
						TaskID:       1,
						EventType:    updateplatform.PostUpdateCheck,
						EventStatus:  true,
						EventContent: fmt.Sprintf("%v success", checkType),
					})
				}

				return nil
			},
			string(system.EndStatus): func() error {
				// wrapper的资源释放
				if unref != nil {
					unref()
				}
				return nil
			},
		})
		job.setAfterHooks(map[string]func() error{
			string(system.RunningStatus): func() error {
				job.setPropProgress(0.01)
				if m.config.IntranetUpdate {
					// 独立客户端为了减少更新平台的瞬时负载，采取0-58秒内随机开始检查更新任务
					time.Sleep(time.Duration(rand.Intn(59)) * time.Second)
				}
				if m.updater == nil {
					return errors.New("Manager.updater is nil")
				}

				// 刷新P2P下载服务状态，判断是否启用P2P更新
				m.updater.refreshUpgradeDeliveryService()
				useP2PUpdate := m.updater.P2PUpdateSupport && m.updater.P2PUpdateEnable
				if m.config.PlatformUpdate {
					// 从更新平台同步仓库源配置和InRelease，若启用P2P则替换为delivery协议
					m.updatePlatform.SyncRepoAndInRelease(useP2PUpdate)
				} else if useP2PUpdate {
					// 公网并且启用P2P时，将系统更新和安全更新的APT源的http(s)协议统一替换为delivery协议
					repos := m.updatePlatform.GetPlatformRepoSources()
					if err := system.UpdateP2pDefaultSourceDir(system.SystemUpdate, repos); err != nil {
						logger.Warning(err)
					}
					if err := system.UpdateP2pDefaultSourceDir(system.SecurityUpdate, repos); err != nil {
						logger.Warning(err)
					}
				}

				if updateplatform.IsForceUpdate(m.updatePlatform.Tp) && m.updatePlatform.Tp != updateplatform.UpdateRegularly {
					m.stopTimerUnit(lastoreRegularlyUpdate)
				}
				if m.updatePlatform.TimerHasChanged {
					msg := gettext.Tr("timer has changed. Please reboot to take effect")
					go m.sendNotify(updateNotifyShowOptional, 0, "preferences-system", "", msg, nil, nil, system.NotifyExpireTimeoutPrivate)
				}

				if m.config.IntranetUpdate {
					if err = m.refreshThrottlingFromPlatform(); err != nil {
						logger.Warning("updatePlatform gen download speed limit failed", err)
					}

					if err := m.updatePlatform.GenIpfsConfig(); err != nil {
						logger.Warningf("failed to gen ipfs config: %v", err)
					} else if err := m.updatePlatform.UpdateDeliverySpeedLimit(); err != nil {
						logger.Warningf("failed to update delivery speed limit: %v", err)
					}
				}

				err = m.updatePlatform.UpdateAllPlatformDataSync()
				if err != nil {
					logger.Warning(err)
					if m.config.PlatformUpdate {
						job.retry = 0
						return &system.JobError{
							ErrType:   system.ErrorPlatformUnreachable,
							ErrDetail: "failed to get update info by update platform: " + err.Error(),
						}
					} else {
						return nil
					}
				}
				m.updatePlatform.PrepareCheckScripts()
				m.updater.setPropUpdateTarget(m.updatePlatform.GetUpdateTarget()) // 更新目标 历史版本控制中心获取UpdateTarget,获取更新日志

				checkType := dut.PreUpdateCheck
				if systemErr := dut.CheckSystem(checkType, nil); systemErr != nil {
					logger.Warning(systemErr)
					go func(err *system.JobError) {
						m.updatePlatform.PostProcessEventMessage(updateplatform.ProcessEvent{
							TaskID:       1,
							EventType:    updateplatform.PreUpdateCheck,
							EventStatus:  false,
							EventContent: err.ErrDetail,
						})
					}(systemErr)
				} else {
					go m.updatePlatform.PostProcessEventMessage(updateplatform.ProcessEvent{
						TaskID:       1,
						EventType:    updateplatform.PreUpdateCheck,
						EventStatus:  true,
						EventContent: fmt.Sprintf("%v success", checkType),
					})
				}

				// 从更新平台获取数据并处理完成后,进度更新到10%
				job.setPropProgress(0.10)
				return nil
			},
		})

		if err = m.jobManager.addJob(job); err != nil {
			logger.Warning(err)
			if unref != nil {
				unref()
			}
			return err
		}
		return nil
	})
	if err != nil && !errors.Is(err, JobExistError) { // exist的err无需返回
		logger.Warning(err)
		return nil, err
	}
	return job, nil
}

// 暂时废弃获取可更新列表的详细信息
var getUpgradablePackageListMap = map[system.UpdateType]func([]string) (map[string]system.PackageInfo, map[string]system.PackageInfo, error){
	system.SystemUpdate:   getSystemUpgradablePackagesMap,
	system.SecurityUpdate: getSecurityUpgradablePackagesMap,
	system.UnknownUpdate:  getUnknownUpgradablePackagesMap,
}

func (m *Manager) refreshThrottlingFromPlatform() error {
	var downloadSpeed downloadSpeedLimitConfig
	if err := json.Unmarshal([]byte(m.config.LocalDownloadSpeedLimitConfig), &downloadSpeed); err != nil {
		downloadSpeed = downloadSpeedLimitConfig{
			DownloadSpeedLimitEnabled: true,
			LimitSpeed:                strconv.FormatInt(defaultSpeedLimit, 10),
			IsOnlineSpeedLimit:        false,
		}
	}
	if err := m.updatePlatform.GenThrottlingByToken(); err != nil {
		return fmt.Errorf("failed to get throttling data %w", err)
	}

	m.applyOnlineRateLimit(&downloadSpeed, m.updatePlatform.OnlineRateLimit.ServerTime)

	logger.Infof("set download limit %+v --> %+v by platform", m.updater.DownloadSpeedLimitConfig, downloadSpeed)
	if err := m.updater.setDownloadSpeedLimit(downloadSpeed); err != nil {
		logger.Warningf("Failed to set download speed limit %v", err)
	}

	if downloadSpeed.IsOnlineSpeedLimit {
		// 如果启用了在线限速，禁用本地限速，并将本地限速速度同步为平台下发的限速值
		if err := m.disableLocalSpeedLimitConfig(downloadSpeed.LimitSpeed); err != nil {
			logger.Warningf("Failed to disable local speed limit %v", err)
		}
	} else {
		// 如果启用了本地限速，禁用在线限速
		if err := m.disableOnlineSpeedLimitConfig(); err != nil {
			logger.Warningf("Failed to disable online speed limit %v", err)
		}
	}

	return nil
}

func (m *Manager) disableOnlineSpeedLimitConfig() error {
	var downloadSpeed downloadSpeedLimitConfig
	if err := json.Unmarshal([]byte(m.config.DownloadSpeedLimitConfig), &downloadSpeed); err != nil {
		downloadSpeed = downloadSpeedLimitConfig{
			DownloadSpeedLimitEnabled: true,
			LimitSpeed:                strconv.FormatInt(defaultSpeedLimit, 10),
			IsOnlineSpeedLimit:        true,
		}
	}
	if !downloadSpeed.DownloadSpeedLimitEnabled {
		return nil
	}
	downloadSpeed.DownloadSpeedLimitEnabled = false
	jsonStr, err := json.Marshal(downloadSpeed)
	if err != nil {
		return fmt.Errorf("failed to marshal download speed limit config: %w", err)
	}
	if err := m.config.SetDownloadSpeedLimitConfig(string(jsonStr)); err != nil {
		return fmt.Errorf("failed to set download speed limit config: %w", err)
	}
	return nil
}

func (m *Manager) disableLocalSpeedLimitConfig(limitSpeed string) error {
	var downloadSpeed downloadSpeedLimitConfig
	if err := json.Unmarshal([]byte(m.config.LocalDownloadSpeedLimitConfig), &downloadSpeed); err != nil {
		downloadSpeed = downloadSpeedLimitConfig{
			DownloadSpeedLimitEnabled: true,
			LimitSpeed:                strconv.FormatInt(defaultSpeedLimit, 10),
			IsOnlineSpeedLimit:        false,
		}
	}
	oldDownloadSpeed := downloadSpeed
	if limitSpeed != "" {
		downloadSpeed.LimitSpeed = limitSpeed
	}
	downloadSpeed.DownloadSpeedLimitEnabled = false
	downloadSpeed.IsOnlineSpeedLimit = false
	if reflect.DeepEqual(downloadSpeed, oldDownloadSpeed) {
		return nil
	}
	jsonStr, err := json.Marshal(downloadSpeed)
	if err != nil {
		return fmt.Errorf("failed to marshal local download speed limit config: %w", err)
	}
	if err := m.config.SetLocalDownloadSpeedLimitConfig(string(jsonStr)); err != nil {
		return fmt.Errorf("failed to set local download speed limit config: %w", err)
	}
	return nil
}

// 生成系统更新内容和安全更新内容
func (m *Manager) generateUpdateInfo() (errList []error) {
	propPkgMap := make(map[string][]string) // updater的ClassifiedUpdatablePackages用
	var propPkgMapMu sync.Mutex
	var errListMu sync.Mutex
	appendErrorSafe := func(err error) {
		errListMu.Lock()
		errList = append(errList, err)
		errListMu.Unlock()
	}
	updatePropPkgMapSafe := func(t string, packageList []string) {
		propPkgMapMu.Lock()
		propPkgMap[t] = packageList
		propPkgMapMu.Unlock()
	}

	var wg sync.WaitGroup
	for updateType, getFn := range getUpgradablePackageList {
		wg.Add(1)
		fn := getFn
		t := updateType
		go func() {
			logger.Infof("start get %v upgradable package", t.JobType())
			installList, err := fn(m.coreList)
			if err != nil {
				appendErrorSafe(err)
			} else {
				updatePropPkgMapSafe(t.JobType(), installList)
			}
			wg.Done()
		}()
	}
	wg.Wait()
	m.updater.setClassifiedUpdatablePackages(propPkgMap)
	return
}

func getSystemUpgradablePackagesMap(coreList []string) (map[string]system.PackageInfo, map[string]system.PackageInfo, error) {
	if len(coreList) == 0 {
		return nil, nil, errors.New("coreList is nil,can not get system update package list")
	}
	var err error

	var emulateInstallPkgList map[string]system.PackageInfo
	var emulateRemovePkgList map[string]system.PackageInfo

	// 模拟安装更新平台下发所有包(不携带版本号)，获取可升级包的版本
	systemSource := system.GetCategorySourceMap()[system.SystemUpdate]
	var options []string
	if info, err := os.Stat(systemSource); err == nil {
		if info.IsDir() {
			options = []string{
				"-o", "Dir::Etc::sourcelist=/dev/null",
				"-o", fmt.Sprintf("Dir::Etc::SourceParts=%v", systemSource),
			}
		} else {
			options = []string{
				"-o", fmt.Sprintf("Dir::Etc::sourcelist=%v", systemSource),
				"-o", "Dir::Etc::SourceParts=/dev/null",
				"-o", "Dir::Etc::preferences=/dev/null", // 系统更新仓库来自更新平台，为了不收本地优先级配置影响，覆盖本地优先级配置
				"-o", "Dir::Etc::PreferencesParts=/dev/null",
			}
		}
	}

	emulateInstallPkgList, emulateRemovePkgList, err = apt.GenOnlineUpdatePackagesByEmulateInstall(coreList, options)
	if err != nil {
		return nil, nil, err
	}
	return emulateInstallPkgList, emulateRemovePkgList, nil
}

func getSecurityUpgradablePackagesMap(coreList []string) (map[string]system.PackageInfo, map[string]system.PackageInfo, error) {
	return apt.GenOnlineUpdatePackagesByEmulateInstall(nil, []string{
		"-o", fmt.Sprintf("Dir::Etc::sourcelist=%v", system.GetCategorySourceMap()[system.SecurityUpdate]),
		"-o", "Dir::Etc::SourceParts=/dev/null",
	})
}

func getUnknownUpgradablePackagesMap(coreList []string) (map[string]system.PackageInfo, map[string]system.PackageInfo, error) {
	return apt.GenOnlineUpdatePackagesByEmulateInstall(nil, []string{
		"-o", fmt.Sprintf("Dir::Etc::SourceParts=%v", system.GetCategorySourceMap()[system.UnknownUpdate]),
		"-o", "Dir::Etc::sourcelist=/dev/null",
	})
}

var getUpgradablePackageList = map[system.UpdateType]func([]string) ([]string, error){
	system.SystemUpdate:   getSystemUpgradablePackageList,
	system.SecurityUpdate: getSecurityUpgradablePackageList,
	system.UnknownUpdate:  getUnknownUpgradablePackageList,
}

func getSystemUpgradablePackageList(coreList []string) ([]string, error) {
	return apt.ListDistUpgradePackages(system.GetCategorySourceMap()[system.SystemUpdate], coreList)
}

func getSecurityUpgradablePackageList(coreList []string) ([]string, error) {
	return apt.ListDistUpgradePackages(system.GetCategorySourceMap()[system.SecurityUpdate], coreList)
}

func getUnknownUpgradablePackageList(coreList []string) ([]string, error) {
	return apt.ListDistUpgradePackages(system.GetCategorySourceMap()[system.UnknownUpdate], coreList)
}

type statusVersion struct {
	status       string
	version      string
	architecture string
}

func loadPkgStatusVersion() (map[string]statusVersion, error) {
	out, err := exec.Command("dpkg-query", "-f", "${Package} ${db:Status-Abbrev} ${Version} ${Architecture}\n", "-W").Output() // #nosec G204
	if err != nil {
		return nil, err
	}
	reader := bytes.NewReader(out)
	scanner := bufio.NewScanner(reader)
	result := make(map[string]statusVersion)
	for scanner.Scan() {
		line := scanner.Bytes()
		fields := bytes.Fields(line)
		if len(fields) != 4 {
			continue
		}
		pkg := string(fields[0])
		status := string(fields[1])
		version := string(fields[2])
		architecture := string(fields[3])
		result[pkg] = statusVersion{
			status:       status,
			version:      version,
			architecture: architecture,
		}
	}
	err = scanner.Err()
	if err != nil {
		return nil, err
	}
	return result, nil
}

// ver1 >= ver2
func compareVersionsGe(ver1, ver2 string) bool {
	gt, err := compareVersionsGeFast(ver1, ver2)
	if err != nil {
		return compareVersionsGeDpkg(ver1, ver2)
	}
	return gt
}

// ver1 < ver2
func compareVersionLt(ver1, ver2 string) bool {
	return compareVersionsLtDpkg(ver1, ver2)
}

func compareVersionsGeFast(ver1, ver2 string) (bool, error) {
	v1, err := debVersion.Parse(ver1)
	if err != nil {
		return false, err
	}
	v2, err := debVersion.Parse(ver2)
	if err != nil {
		return false, err
	}
	return debVersion.Compare(v1, v2) >= 0, nil
}

func compareVersionsGeDpkg(ver1, ver2 string) bool {
	err := exec.Command("dpkg", "--compare-versions", "--", ver1, "ge", ver2).Run() // #nosec G204
	return err == nil
}

func compareVersionsLtDpkg(ver1, ver2 string) bool {
	err := exec.Command("dpkg", "--compare-versions", "--", ver1, "lt", ver2).Run() // #nosec G204
	return err == nil
}

func listDistUpgradePackages(updateType system.UpdateType) ([]string, error) {
	sourcePath := system.GetCategorySourceMap()[updateType]
	return apt.ListDistUpgradePackages(sourcePath, nil)
}

func (m *Manager) getCoreList(online bool) []string {
	if !m.config.EnableCoreList {
		return nil
	}
	if online {
		return getCoreListOnline()
	}
	return getCoreListFromCache()
}

const TimeOnly = "15:04:05"

func (m *Manager) refreshUpdateInfos(sync bool) {
	// 检查更新时,同步修改canUpgrade状态;检查更新时需要同步操作
	if sync {
		err := exec.Command("/var/lib/lastore/scripts/build_system_info", "-now").Run()
		if err != nil {
			logger.Warning(err)
		}

		// 检查更新后，先下载解析coreList，获取必装清单
		m.coreList = m.getCoreList(true)
		logger.Debug("generateUpdateInfo get coreList:", m.coreList)
		for _, e := range m.generateUpdateInfo() {
			go func() {
				m.inhibitAutoQuitCountAdd()
				defer m.inhibitAutoQuitCountSub()
				msg := fmt.Sprintf("generate package list error, detail is %v:", e)
				m.updatePlatform.PostStatusMessage(updateplatform.StatusMessage{
					Type:   "error",
					Detail: msg,
				}, false)
				m.updatePlatform.PostProcessEventMessage(updateplatform.ProcessEvent{
					TaskID:       1,
					EventType:    updateplatform.GetUpdateEvent,
					EventStatus:  false,
					EventContent: msg,
				})
			}()
			logger.Warning(e)
		}
		m.statusManager.updateSourceOnce = true
		m.statusManager.UpdateModeAllStatusBySize(m.coreList)
		m.statusManager.UpdateCheckCanUpgradeByEachStatus()
	} else {
		m.coreList = m.getCoreList(false)
		go func() {
			// 刷新一下包信息
			if isFirstBoot() {
				for _, e := range m.generateUpdateInfo() {
					go func() {
						m.inhibitAutoQuitCountAdd()
						defer m.inhibitAutoQuitCountSub()
						msg := fmt.Sprintf("generate package list error, detail is %v:", e)
						m.updatePlatform.PostStatusMessage(updateplatform.StatusMessage{
							Type:   "error",
							Detail: msg,
						}, false)
						m.updatePlatform.PostProcessEventMessage(updateplatform.ProcessEvent{
							TaskID:       1,
							EventType:    updateplatform.GetUpdateEvent,
							EventStatus:  false,
							EventContent: msg,
						})
					}()
					logger.Warning(e)
				}
			}
			m.statusManager.UpdateModeAllStatusBySize(m.coreList)
			m.statusManager.UpdateCheckCanUpgradeByEachStatus()
		}()
	}
	m.updateUpdatableProp(m.updater.ClassifiedUpdatablePackages)
	m.statusManager.SetFrontForceUpdate(m.updatePlatform.Tp == updateplatform.UpdateShutdown)
	if updateplatform.IsForceUpdate(m.updatePlatform.Tp) {
		go func() {
			m.inhibitAutoQuitCountAdd()
			logger.Info("auto download force updates")
			_, err := m.prepareDistUpgrade(dbus.Sender(m.service.Conn().Names()[0]), system.SystemUpdate, initiatorAuto) // TODO system.SystemUpdate
			if err != nil {
				logger.Error("failed to prepare dist-upgrade:", err)
			}
			m.inhibitAutoQuitCountSub()
		}()
		if len(m.updater.UpdatablePackages) == 0 {
			// 没有可升级应用时，清空定时更新时间，避免影响后续的更新策略识别
			m.updatePlatform.UpdateTime = time.Unix(0, 0)
			if err := m.config.SetInstallUpdateTime(""); err != nil {
				logger.Warningf("failed to clear install update time: %v", err)
			}
		}
		if !m.updatePlatform.UpdateNowForce && !m.updatePlatform.UpdateTime.IsZero() && len(m.updater.UpdatablePackages) > 0 {
			timeStr := m.updatePlatform.UpdateTime.Format(TimeOnly)
			if timeStr != m.updateTime {
				m.updateTime = timeStr
				msg := fmt.Sprintf(gettext.Tr("The computer will be updated at %s"), m.updateTime)
				go m.sendNotify(updateNotifyShow, 0, "preferences-system", "", msg, nil, nil, system.NotifyExpireTimeoutDefault)
			}
		}
		if m.updatePlatform.Tp == updateplatform.UpdateRegularly && len(m.updater.UpdatablePackages) > 0 {
			_ = m.updateTimerUnit(lastoreRegularlyUpdate)
		}
		// 强制更新开启后，以强制更新下载策略优先
		return
	}
	if m.updater.AutoDownloadUpdates && len(m.updater.UpdatablePackages) > 0 && sync && !m.updater.getIdleDownloadEnabled() {
		logger.Info("auto download updates")
		go func() {
			m.inhibitAutoQuitCountAdd()
			_, err := m.prepareDistUpgrade(dbus.Sender(m.service.Conn().Names()[0]), m.CheckUpdateMode, initiatorAuto)
			if err != nil {
				logger.Error("failed to prepare dist-upgrade:", err)
			}
			m.inhibitAutoQuitCountSub()
		}()
	}
}

func (m *Manager) updateUpdatableProp(infosMap map[string][]string) {
	m.PropsMu.RLock()
	updateType := m.UpdateMode
	m.PropsMu.RUnlock()
	filterInfos := getFilterPackages(infosMap, updateType)
	m.updatableApps(filterInfos) // Manager的UpgradableApps实际为可更新的包,而非应用;
	m.updater.setUpdatablePackages(filterInfos)
	m.updater.updateUpdatableApps()
}

func (m *Manager) ensureUpdateSourceOnce() {
	m.PropsMu.Lock()
	updateOnce := m.updateSourceOnce
	m.PropsMu.Unlock()

	if updateOnce {
		return
	}

	_, err := m.updateSource(dbus.Sender(m.service.Conn().Names()[0]))
	if err != nil {
		logger.Warning(err)
		return
	}
}

// retry次数为1
// 默认检查为 AllCheckUpdate
// 重试检查为 AllInstallUpdate
func handleUpdateSourceFailed(j *Job) {
	retry := j.retry
	if retry > 1 {
		retry = 1
		j.retry = retry
	}
	retryMap := map[int]system.UpdateType{
		1: system.SystemUpdate | system.SecurityUpdate | system.AppendUpdate,
	}
	updateType := retryMap[retry]
	err := system.CustomSourceWrapper(updateType, func(path string, unref func()) error {
		// 重新设置apt命令参数
		info, err := os.Stat(path)
		if err != nil {
			if unref != nil {
				unref()
			}
			return err
		}
		if info.IsDir() {
			j.option = map[string]string{
				"Dir::Etc::SourceList":  "/dev/null",
				"Dir::Etc::SourceParts": path,
			}
		} else {
			j.option = map[string]string{
				"Dir::Etc::SourceList":  path,
				"Dir::Etc::SourceParts": "/dev/null",
			}
		}
		j.wrapPreHooks(map[string]func() error{
			string(system.EndStatus): func() error {
				if unref != nil {
					unref()
				}
				return nil
			},
		})
		return nil
	})
	if err != nil {
		logger.Warning(err)
	}
}
