package main

import (
	"crypto/sha256"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
)

func main() {
	packageURL := flag.String("package-url", "", "下载地址")
	sha256Hash := flag.String("sha256", "", "SHA256 校验值")
	target := flag.String("target", "", "目标 exe 路径")
	ver := flag.String("version", "", "目标版本号")
	force := flag.Bool("force", false, "强制升级")
	backupDir := flag.String("backup-dir", "", "备份目录")
	flag.Parse()

	logPath := filepath.Join(filepath.Dir(*target), "updater.log")
	logFile, err := os.Create(logPath)
	if err != nil {
		fmt.Println("无法创建日志文件:", err)
		os.Exit(1)
	}
	defer logFile.Close()
	log.SetOutput(logFile)
	log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds)

	log.Printf("updater 启动: version=%s target=%s force=%v", *ver, *target, *force)

	if *packageURL == "" || *target == "" {
		log.Fatal("缺少必要参数: --package-url, --target")
	}
	if *sha256Hash == "" {
		log.Fatal("缺少 --sha256 参数")
	}

	// 等待主程序退出
	time.Sleep(2 * time.Second)

	// 下载
	tmpFile, err := downloadWithRetry(*packageURL, 3)
	if err != nil {
		log.Fatal("下载失败: ", err)
	}
	defer os.Remove(tmpFile)
	log.Println("下载完成:", tmpFile)

	// 校验
	if err := verifySHA256(tmpFile, *sha256Hash); err != nil {
		log.Fatal("校验失败: ", err)
	}
	log.Println("SHA256 校验通过")

	// 备份
	if *backupDir == "" {
		*backupDir = filepath.Join(filepath.Dir(*target), "backup")
	}
	os.MkdirAll(*backupDir, 0755)
	backupPath := filepath.Join(*backupDir, fmt.Sprintf("old-%s.exe", time.Now().Format("20060102-150405")))
	if err := copyFile(*target, backupPath); err != nil {
		log.Println("备份失败(继续):", err)
	} else {
		log.Println("已备份:", backupPath)
	}

	// 替换: zip 包解压，exe 直接覆盖
	if strings.HasSuffix(strings.ToLower(*packageURL), ".zip") {
		if err := extractAndReplace(tmpFile, *target); err != nil {
			rollback(backupPath, *target)
			log.Fatal("解压替换失败: ", err)
		}
	} else {
		if err := copyFile(tmpFile, *target); err != nil {
			rollback(backupPath, *target)
			log.Fatal("替换失败: ", err)
		}
	}
	log.Println("替换完成")

	// 启动新版本
	cmd := exec.Command(*target)
	if err := cmd.Start(); err != nil {
		rollback(backupPath, *target)
		log.Fatal("启动新版本失败: ", err)
	}

	// 监听新进程 5 秒
	time.Sleep(5 * time.Second)
	if cmd.Process == nil {
		rollback(backupPath, *target)
		log.Fatal("新进程异常")
	}

	// 检查新进程是否仍在运行
	// NOTE: Windows 下无法直接用 cmd.Process.Signals，用简单的进程存活性判断
	if err := cmd.Process.Release(); err != nil {
		log.Println("新进程已退出，回滚")
		rollback(backupPath, *target)
		os.Exit(1)
	}

	log.Println("升级成功, updater 退出")
}

func downloadWithRetry(url string, maxRetries int) (string, error) {
	var lastErr error
	for i := 0; i < maxRetries; i++ {
		if i > 0 {
			log.Printf("重试下载 (%d/%d)...", i+1, maxRetries)
			time.Sleep(5 * time.Second)
		}
		tmpFile, err := os.CreateTemp("", "updater-*.download")
		if err != nil {
			return "", fmt.Errorf("创建临时文件失败: %w", err)
		}
		tmpPath := tmpFile.Name()

		resp, err := http.Get(url)
		if err != nil {
			tmpFile.Close()
			os.Remove(tmpPath)
			lastErr = err
			continue
		}

		if resp.StatusCode != 200 {
			tmpFile.Close()
			os.Remove(tmpPath)
			resp.Body.Close()
			lastErr = fmt.Errorf("HTTP %d", resp.StatusCode)
			continue
		}

		_, err = io.Copy(tmpFile, resp.Body)
		resp.Body.Close()
		tmpFile.Close()
		if err != nil {
			os.Remove(tmpPath)
			lastErr = err
			continue
		}
		return tmpPath, nil
	}
	return "", fmt.Errorf("下载失败(重试 %d 次): %w", maxRetries, lastErr)
}

func verifySHA256(path, expected string) error {
	f, err := os.Open(path)
	if err != nil {
		return err
	}
	defer f.Close()

	h := sha256.New()
	if _, err := io.Copy(h, f); err != nil {
		return err
	}

	actual := fmt.Sprintf("%x", h.Sum(nil))
	if actual != strings.ToLower(expected) {
		return fmt.Errorf("SHA256 不匹配: 期望 %s, 实际 %s", expected, actual)
	}
	return nil
}

func copyFile(src, dst string) error {
	in, err := os.Open(src)
	if err != nil {
		return err
	}
	defer in.Close()

	out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer out.Close()

	if _, err := io.Copy(out, in); err != nil {
		return err
	}
	// 复制可执行权限
	if fi, err := os.Stat(src); err == nil {
		out.Chmod(fi.Mode())
	}
	return nil
}

func extractAndReplace(zipPath, target string) error {
	// TODO: 如需 zip 解压支持，引入 archive/zip
	// 初期可直接分发 exe，此路径不执行
	return fmt.Errorf("zip 解压暂未实现，请使用 exe 分发")
}

func rollback(backupPath, target string) {
	if backupPath == "" {
		return
	}
	if _, err := os.Stat(backupPath); err != nil {
		return
	}
	log.Println("回滚: 从备份恢复", backupPath)
	if err := copyFile(backupPath, target); err != nil {
		log.Println("回滚失败:", err)
		return
	}
	cmd := exec.Command(target)
	cmd.Start()
	log.Println("已回滚并启动旧版本")
}
