package main

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"github.com/go-resty/resty/v2"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"time"
)

// 定义请求体的结构体
type RequestBody struct {
	DeviceID string `json:"device_id"`
}

// 定义与响应 JSON 数据结构对应的结构体
type ResponseData struct {
	Data struct {
		Version     string `json:"version"`
		URL         string `json:"url"`
		MD5         string `json:"md5"`
		Description string `json:"description"`
		IsAssigned  bool   `json:"is_assigned"`
	} `json:"data"`
	ErrCode int    `json:"errCode"`
	ErrMsg  string `json:"errMsg"`
}

// 从 conf 文件中读取版本号
func readVersionFromConf(confPath string) (string, error) {
	data, err := os.ReadFile(confPath)
	if err != nil {
		if os.IsNotExist(err) {
			return "", nil
		}
		return "", err
	}
	return string(data), nil
}

// 将版本号写入 conf 文件
func writeVersionToConf(confPath, version string) error {
	return os.WriteFile(confPath, []byte(version), 0644)
}

func main() {
	// 定义文件路径
	filePath := "/home/orangepi/car/master/Deviceld.txt"
	// 目标 URL
	url := "http://47.119.190.60/api/v1/ota/latestVersion"
	// 定义 conf 文件路径
	confPath := "/home/orangepi/car/master/version.conf"
	// 定义下载和备份文件路径
	downloadPath := "/home/orangepi/car/master/main"
	backupDir := "/home/orangepi/car/master/backup"

	var fileData string
	// 无限循环，直到成功读取到数据
	for {
		// 读取文件内容
		data, err := os.ReadFile(filePath)
		if err != nil {
			if os.IsNotExist(err) {
				fmt.Printf("文件不存在，等待10s后重试...\n")
			} else {
				fmt.Printf("读取文件时出错: %v，等待1分钟后重试...\n", err)
			}
			time.Sleep(10 * time.Second)
			continue
		}
		
		// 检查文件是否为空
		if len(data) == 0 {
			fmt.Printf("文件为空，等待10s后重试...\n")
			time.Sleep(10 * time.Second)
			continue
		}
		
		// 将字节切片转换为字符串
		fileData = string(data)
		
		// 成功读取到数据，退出循环
		fmt.Println("成功读取文件内容:", fileData)
		break
	}

	// 创建 resty 客户端
	client := resty.New()


	for {
		time.Sleep(1 * time.Minute)
		// 从 conf 文件中读取上次的版本号
		lastVersion, err := readVersionFromConf(confPath)
		if err != nil {
			fmt.Printf("读取版本号配置文件出错: %v\n", err)
			continue
		}

		// 构建请求体
		requestBody := RequestBody{
			DeviceID: fileData,
		}

		// 发送 POST 请求并解析响应
		resp, err := client.R().
			SetBody(requestBody).
			SetResult(&ResponseData{}).
			SetError(&ResponseData{}).
			Post(url)

		if err != nil {
			fmt.Printf("发送请求失败: %v\n", err)
			continue
		}

		if resp.IsError() {
			fmt.Printf("请求出错，状态码: %d, 错误信息: %v\n", resp.StatusCode(), resp.Error())
			continue
		}

		// 解析响应数据
		result, ok := resp.Result().(*ResponseData)
		if !ok {
			fmt.Printf("响应数据类型转换失败\n")
			continue
		}

		// 检查版本号是否不同
		if result.Data.Version != lastVersion {
			// 停止服务
			err := stopCarStartService()
			if err != nil {
				fmt.Printf("停止 carstart.service 失败: %v\n", err)
				continue
			}

			if _, err := os.Stat(downloadPath); err == nil {
   				 // 文件存在，执行备份
				err = backupCurrentVersion(downloadPath, backupDir, lastVersion)
				if err != nil {
					fmt.Printf("备份当前版本文件失败: %v\n", err)
					startCarStartService()
					continue
				}
			} else if os.IsNotExist(err) {
				// 文件不存在，跳过备份
				fmt.Printf("文件 %s 不存在，跳过备份\n", downloadPath)
			} else {
				// 其他错误
				fmt.Printf("检查文件 %s 时出错: %v\n", downloadPath, err)
				startCarStartService()
				continue
			}

			// 下载文件
			downloadURL := result.Data.URL
			tempDownloadPath := downloadPath + ".tmp"
			fmt.Println("下载成功")
			err = downloadFile(downloadURL, tempDownloadPath)
			if err != nil {
				fmt.Printf("下载文件失败: %v\n", err)
				// 恢复备份
				rollbackToBackup(downloadPath, backupDir, lastVersion)
				startCarStartService()
				continue
			}

			// 计算下载文件的 MD5 值
			fileMD5, err := calculateFileMD5(tempDownloadPath)
			if err != nil {
				fmt.Printf("计算文件 MD5 值失败: %v\n", err)
				os.Remove(tempDownloadPath)
				rollbackToBackup(downloadPath, backupDir, lastVersion)
				startCarStartService()
				continue
			}

			// 进行 MD5 检验
			if fileMD5 == result.Data.MD5 {
				fmt.Println("MD5 检验通过")
				//os.Remove(tempDownloadPath) // 删除临时文件
				promoteTempToFinal(tempDownloadPath, downloadPath)
				err = writeVersionToConf(confPath, result.Data.Version)
				if err != nil {
					fmt.Printf("写入版本号配置文件出错: %v\n", err)
				}
				startCarStartService()
			} else {
				fmt.Printf("MD5 检验失败，期望的 MD5 值: %s，实际的 MD5 值: %s\n", result.Data.MD5, fileMD5)
				os.Remove(tempDownloadPath)
				rollbackToBackup(downloadPath, backupDir, lastVersion)
				startCarStartService()
			}
		} else {
			fmt.Println("版本号相同，无需下载")
		}
		//对文件进行授权
		permiss()
		// 轮询间隔，这里设置为 30 分钟
		time.Sleep(30 * time.Minute)
	}
}

// 停止 carstart.service
func stopCarStartService() error {
	cmd := exec.Command("sudo", "systemctl", "stop", "carstart.service")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("停止服务失败: %v, 输出: %s", err, string(output))
	}
	fmt.Println("成功停止 carstart.service")
	return nil
}

// 启动 carstart.service
func startCarStartService() error {
	cmd := exec.Command("sudo", "systemctl", "restart", "carstart.service")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("启动服务失败: %v, 输出: %s", err, string(output))
	}
	fmt.Println("成功启动 carstart.service")
	return nil
}

// 备份当前版本文件
func backupCurrentVersion(downloadPath, backupDir, version string) error {
	if _, err := os.Stat(backupDir); os.IsNotExist(err) {
		os.MkdirAll(backupDir, 0755)
	}
	backupPath := filepath.Join(backupDir, "main_"+version)
	return os.Rename(downloadPath, backupPath)
}

// 恢复备份文件
func rollbackToBackup(downloadPath, backupDir, version string) error {
	backupPath := filepath.Join(backupDir, "main_"+version)
	if _, err := os.Stat(backupPath); os.IsNotExist(err) {
		return fmt.Errorf("备份文件不存在: %s", backupPath)
	}
	return os.Rename(backupPath, downloadPath)
}

// 下载文件的函数
func downloadFile(url, filePath string) error {
	client := resty.New()
	resp, err := client.R().SetOutput(filePath).Get(url)
	if err != nil {
		return err
	}
	if resp.StatusCode() != 200 {
		return fmt.Errorf("下载文件失败，状态码: %d", resp.StatusCode())
	}
	return nil
}

// 计算文件 MD5 值的函数
func calculateFileMD5(filePath string) (string, error) {
	file, err := os.Open(filePath)
	if err != nil {
		return "", err
	}
	defer file.Close()

	hash := md5.New()
	if _, err := io.Copy(hash, file); err != nil {
		return "", err
	}

	hashInBytes := hash.Sum(nil)[:16]
	return hex.EncodeToString(hashInBytes), nil
}

// promoteTempToFinal 将临时文件变为正式文件（原子性操作）
func promoteTempToFinal(tempPath, finalPath string) error {
    // 1. 确保临时文件存在且有效
    if _, err := os.Stat(tempPath); os.IsNotExist(err) {
        return fmt.Errorf("临时文件不存在: %s", tempPath)
    }

    // 2. 删除已存在的正式文件（如果存在）
    if _, err := os.Stat(finalPath); err == nil {
        if err := os.Remove(finalPath); err != nil {
            return fmt.Errorf("删除旧文件失败: %v", err)
        }
    }

    // 3. 原子性重命名（系统保证操作的原子性）
    if err := os.Rename(tempPath, finalPath); err != nil {
        return fmt.Errorf("重命名失败: %v", err)
    }

    fmt.Printf("已将临时文件 %s 转为正式文件 %s\n", tempPath, finalPath)
    return nil
}

func permiss() error {
    cmd := exec.Command("sudo", "chmod", "+x", "/home/orangepi/car/master/main")
    output, err := cmd.CombinedOutput()
    if err != nil {
        // 返回一个包含错误信息的 error 类型值
        return fmt.Errorf("执行命令出错: %v，输出信息: %s", err, string(output))
    }
    fmt.Println("命令执行成功")
    // 命令执行成功，返回 nil 表示没有错误
    return nil
}