package linux

// lspci | grep -i vga
import (
	"client/internal/db"
	message "client/internal/msg"
	"client/internal/utils"
	"fmt"
	"log"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"gopkg.in/ini.v1"
)

type LinuxClient struct {
	mu             sync.Mutex
	Auth           string
	MachineCode    string
	ID             string
	MiningConfig   message.MiningConfig
	db             *db.SQLiteServer
	Status         int             // 当前client状态，1正在工作，2空闲
	currentProcess *currentProcess // 当前挖矿进程及类型
}

type currentProcess struct {
	process *exec.Cmd
	miner   string
}

// CheckPermission 检查当前进程是否具备运行本客户端所需的权限（Linux）
// 要求使用 root 运行，否则很多命令（如 dmidecode、nvidia-smi 等）可能失败。
func CheckPermission() error {
	u, err := user.Current()
	if err != nil {
		return fmt.Errorf("获取当前用户信息失败: %v", err)
	}
	if u.Uid != "0" {
		return fmt.Errorf("当前用户(%s)不是 root 用户，请使用 sudo 或 root 账号运行本程序", u.Username)
	}
	return nil
}

func NewLinuxClient(auth string) *LinuxClient {
	cfg, err := ini.Load("mining.linux.conf")
	if err != nil {
		log.Printf("获取挖矿配置失败: %v", err)
		log.Printf("客户端已退出，请重新检查配置文件(%s)是否存在", "mining.linux.conf")
		return nil
	}
	// 解析配置
	var miningConfig message.MiningConfig

	// 解析 [lolminer] 部分
	sectionLolMiner := cfg.Section("lolminer")
	miningConfig.LolMinerPath = sectionLolMiner.Key("path").String()

	// 解析 [rigel] 部分
	sectionRigel := cfg.Section("rigel")
	miningConfig.RigelPath = sectionRigel.Key("path").String()

	// 解析 [proxy] 部分
	sectionProxy := cfg.Section("proxy")
	miningConfig.ProxyEnabled, _ = sectionProxy.Key("proxy").Bool()

	if miningConfig.LolMinerPath != "" {
		result := utils.CheckFileExists(miningConfig.LolMinerPath)
		if !result {
			log.Printf("未检测到lolminer挖矿软件的存在，请确认是否已经安装lolminer，并核对下列路径：%s", miningConfig.LolMinerPath)
			log.Fatalln("客户端已退出，确认路径后请重启客户端")
			return nil
		}
	}

	if miningConfig.RigelPath != "" {
		result := utils.CheckFileExists(miningConfig.RigelPath)
		if !result {
			log.Printf("未检测到rigel挖矿软件的存在，请确认是否已经安装rigel，并核对下列路径：%s", miningConfig.RigelPath)
			log.Fatalln("客户端已退出，确认路径后请重启客户端")
			return nil
		}
	}
	var client = &LinuxClient{}
	client.Auth = auth
	client.MiningConfig = miningConfig
	client.Status = 2

	// 初始化sqlite3数据库
	db := db.NewSQLiteServer()
	client.db = db
	client.initHistoryTask()
	// 获取主机身份
	mac, err := client.GetMACAddress()
	if err != nil {
		log.Println(err)
		log.Fatalln("获取当前主机信息失败，程序已退出，请检查网络后重新启动本客户端。")
	}
	client.MachineCode = mac
	client.ID = auth + "." + mac

	return client
}

func (l *LinuxClient) initHistoryTask() {
	exsits, task := l.db.LoadMiningTask()
	if exsits {
		currentTs := time.Now().Unix()
		if currentTs < int64(task.EndTimestamp) {
			l.mu.Lock()
			l.Status = 1
			l.mu.Unlock()
			err := l.Mining(task)
			if err != nil {
				log.Printf("重新开启挖矿任务失败，请手动检查：%v", err)
				return
			}
		} else {
			l.db.FinishMiningTask(task)
		}
	}
}

// 获取 NVIDIA 显卡信息
func getNvidiaGPUInfo() (map[int]message.GPU, error) {
	// 使用 nvidia-smi 列出所有 GPU
	cmd := exec.Command("nvidia-smi", "--query-gpu=name,memory.total", "--format=csv,noheader,nounits")
	output, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("无法执行 nvidia-smi 命令: %v", err)
	}

	// 解析输出
	info := strings.TrimSpace(string(output))
	lines := strings.Split(info, "\n")

	gpus := make(map[int]message.GPU)

	for i, line := range lines {
		parts := strings.Split(strings.TrimSpace(line), ", ")
		if len(parts) < 2 {
			return nil, fmt.Errorf("nvidia-smi 输出格式错误")
		}

		// 解析显存容量
		mem, err := parseMemory(parts[1])
		if err != nil {
			return nil, fmt.Errorf("无法解析显存容量: %v", err)
		}

		// 将每个 GPU 的信息存储到 map 中
		gpus[i] = message.GPU{
			Brand: "NVIDIA",
			Model: parts[0],
			Mem:   mem,
		}
	}

	return gpus, nil
}

// 解析显存容量字符串并返回 float64 类型
func parseMemory(memStr string) (float64, error) {
	var mem float64
	_, err := fmt.Sscanf(memStr, "%f", &mem)
	if err != nil {
		return 0, fmt.Errorf("显存解析失败: %v", err)
	}
	return mem, nil
}

// 获取其他显卡信息（如 Intel 或 AMD）
func getOtherGPUInfo() (map[int]message.GPU, error) {
	// 使用 lspci 列出所有显卡设备
	cmd := exec.Command("lspci", "-v")
	output, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("无法执行 lspci 命令: %v", err)
	}

	// 解析输出，获取显卡信息
	info := strings.TrimSpace(string(output))
	lines := strings.Split(info, "\n")

	gpus := make(map[int]message.GPU)
	gpuIndex := 0

	for _, line := range lines {
		// 假设输出中包含 Intel 或 AMD GPU
		if strings.Contains(line, "VGA compatible controller") {
			if strings.Contains(line, "Intel") || strings.Contains(line, "AMD") {
				// 将 Intel/AMD GPU 信息存储到 map 中
				gpus[gpuIndex] = message.GPU{
					Brand: "Intel/AMD",
					Model: line, // 这里可以根据实际 lspci 输出提取显卡型号
					Mem:   0,    // 无法通过 lspci 获取显存
				}
				gpuIndex++
			}
		}
	}

	if len(gpus) == 0 {
		return nil, fmt.Errorf("未找到显卡信息")
	}

	return gpus, nil
}

// 获取所有 GPU 信息
func (l *LinuxClient) GetGPUInfo() (map[int]message.GPU, error) {
	// 尝试获取 NVIDIA GPU 信息
	gpus, err := getNvidiaGPUInfo()
	if err == nil {
		return gpus, nil
	}

	// 如果没有 NVIDIA 显卡，尝试获取其他类型显卡信息
	return getOtherGPUInfo()
}

// 获取 MAC 地址
// 获取“机器码”：Linux 下改为使用主机 UUID
// 优先从 /sys/class/dmi/id/product_uuid 读取，失败时再尝试 dmidecode
func (l *LinuxClient) GetMACAddress() (string, error) {
	// 1. 优先读取内核提供的 product_uuid 文件（大多数物理机/虚拟机都支持）
	const uuidPath = "/sys/class/dmi/id/product_uuid"
	if data, err := os.ReadFile(uuidPath); err == nil {
		uuid := strings.TrimSpace(string(data))
		if uuid != "" && !strings.EqualFold(uuid, "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF") {
			uuid = strings.Trim(uuid, "{}")
			uuid = strings.ToUpper(uuid)
			return uuid, nil
		}
	}

	// 2. 退回使用 dmidecode（需要有权限）
	cmd := exec.Command("dmidecode", "-s", "system-uuid")
	out, err := cmd.Output()
	if err != nil {
		return "", fmt.Errorf("获取主机 UUID 失败: %v", err)
	}
	uuid := strings.TrimSpace(string(out))
	if uuid == "" || strings.EqualFold(uuid, "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF") {
		return "", fmt.Errorf("获取到的主机 UUID 无效: %q", uuid)
	}
	uuid = strings.Trim(uuid, "{}")
	uuid = strings.ToUpper(uuid)
	return uuid, nil
}

/*
配置lolminer
#!/bin/bash

POOL=47.108.221.51:3333
WALLET=m2test.11x12
ALGO=NEXA

./lolMiner --algo $ALGO --pool $POOL --user $WALLET $@
*/
func (l *LinuxClient) lolminer(cfg message.ConfigurationMiningMsg) {
	l.mu.Lock()
	if l.Status != 2 {
		log.Printf("当前还有挖矿任务正在进行中：币=%s, 算法=%s, 矿池=%s, 截止时间=%d", cfg.Coin, cfg.Algo, cfg.Pool, cfg.EndTimestamp)
		return
	}
	l.Status = 1
	l.mu.Unlock()

	var address string
	if cfg.WalletMining {
		address = cfg.WalletAddress
	} else {
		address = cfg.PoolUser
	}

	dir := l.MiningConfig.LolMinerPath
	name := filepath.Join(dir, "lolMiner")
	args := []string{"--algo", cfg.Coin, "--pool", cfg.PoolUrl, "--user", address + "." + cfg.WorkerID}
	cmd := exec.Command(name, args...)
	cmd.Dir = dir
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	log.Printf("执行命令：%s %s", name, strings.Join(args, " "))
	// 启动进程
	err := cmd.Start()
	if err != nil {
		log.Printf("Error starting lolMiner: %v", err)
		return
	}
	// 添加执行记录
	go func() {
		err := l.db.InsertMiningTask(cfg)
		if err != nil {
			log.Printf("本次挖矿任务记录失败：%v", err)
		}
	}()
	// 获取 lolMiner 的进程 ID
	fmt.Printf("lolMiner started with PID: %d\n", cmd.Process.Pid)

	// 记录当前挖矿进程
	l.mu.Lock()
	l.currentProcess = &currentProcess{
		process: cmd,
		miner:   "lolminer",
	}
	l.mu.Unlock()
	// 获取当前时间戳（秒级）
	currentTimestamp := time.Now().Unix()
	endTimestamp := int64(cfg.EndTimestamp)
	// 计算目标时间戳和当前时间戳之间的差值
	if endTimestamp <= currentTimestamp {
		// 如果目标时间已经到达，立即结束挖矿进程
		fmt.Println("目标时间已经到达，立即结束 lolMiner 挖矿进程")
		l.StopMining()
	} else {
		// 计算需要等待的秒数
		waitDuration := time.Second * time.Duration(endTimestamp-currentTimestamp)
		fmt.Printf("当前时间戳：%d，目标时间戳：%d，剩余时间：%v\n", currentTimestamp, endTimestamp, waitDuration)
		// 使用 time.Sleep 等待直到目标时间戳
		time.Sleep(waitDuration)
		fmt.Println("目标时间到达，开始执行操作，停止 lolMiner 挖矿进程")
		// 通过 StopMining 统一停止挖矿进程
		l.StopMining()
		// 修改执行记录
		go func() {
			err := l.db.FinishMiningTask(cfg)
			if err != nil {
				log.Printf("修改执行记录失败：%v", err)
			}
		}()
		// 输出进程被杀死的信息
		fmt.Println("lolMiner process killed.")
	}
	log.Printf("当前挖矿任务已执行完毕：币=%s, 算法=%s, 矿池=%s, 截止时间=%d", cfg.Coin, cfg.Algo, cfg.Pool, cfg.EndTimestamp)
	l.mu.Lock()
	l.Status = 2
	l.mu.Unlock()

}

/*
配置bzminer
#!/bin/bash

POOL=47.108.221.51:3333
WALLET=m2test.11x12
ALGO=NEXA

./bzminer -a $ALGO -w $WALLET -p $POOL
*/
func (l *LinuxClient) bzminer(cfg message.ConfigurationMiningMsg) {
	l.mu.Lock()
	if l.Status != 2 {
		log.Printf("当前还有挖矿任务正在进行中：币=%s, 算法=%s, 矿池=%s, 截止时间=%d", cfg.Coin, cfg.Algo, cfg.Pool, cfg.EndTimestamp)
		return
	}
	l.Status = 1
	l.mu.Unlock()
	var address string
	if cfg.WalletMining {
		address = cfg.WalletAddress
	} else {
		address = cfg.PoolUser
	}
	dir := l.MiningConfig.BzMinerPath
	name := filepath.Join(dir, "bzminer")
	args := []string{"-a", cfg.Coin, "-w", address + "." + cfg.WorkerID, "-p", cfg.PoolUrl}
	cmd := exec.Command(name, args...)
	cmd.Dir = dir
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	log.Printf("执行命令：%s %s", name, strings.Join(args, " "))
	// 启动进程
	err := cmd.Start()
	if err != nil {
		log.Printf("Error starting bzminer: %v", err)
		return
	}
	// 添加执行记录
	go func() {
		err := l.db.InsertMiningTask(cfg)
		if err != nil {
			log.Printf("本次挖矿任务记录失败：%v", err)
		}
	}()
	// 获取 bzminer 的进程 ID
	fmt.Printf("bzminer started with PID: %d\n", cmd.Process.Pid)

	// 记录当前挖矿进程
	l.mu.Lock()
	l.currentProcess = &currentProcess{
		process: cmd,
		miner:   "bzminer",
	}
	l.mu.Unlock()
	// 获取当前时间戳（秒级）
	currentTimestamp := time.Now().Unix()
	endTimestamp := int64(cfg.EndTimestamp)
	// 计算目标时间戳和当前时间戳之间的差值
	if endTimestamp <= currentTimestamp {
		fmt.Println("目标时间已经到达，停止 bzminer 挖矿进程")
		l.StopMining()
	} else {
		// 计算需要等待的秒数
		waitDuration := time.Second * time.Duration(endTimestamp-currentTimestamp)
		fmt.Printf("当前时间戳：%d，目标时间戳：%d，剩余时间：%v\n", currentTimestamp, endTimestamp, waitDuration)
		// 使用 time.Sleep 等待直到目标时间戳
		time.Sleep(waitDuration)
		fmt.Println("目标时间到达，开始执行操作，停止 bzminer 挖矿进程")
		// 通过 StopMining 统一停止挖矿进程
		l.StopMining()
		// 修改执行记录
		go func() {
			err := l.db.FinishMiningTask(cfg)
			if err != nil {
				log.Printf("修改执行记录失败：%v", err)
			}
		}()
		// 输出进程被杀死的信息
		fmt.Println("bzminer process killed.")
	}
	log.Printf("当前挖矿任务已执行完毕：币=%s, 算法=%s, 矿池=%s, 截止时间=%d", cfg.Coin, cfg.Algo, cfg.Pool, cfg.EndTimestamp)
	l.mu.Lock()
	l.Status = 2
	l.mu.Unlock()
}

/*
配置rigel
#!/bin/bash

POOL=47.108.221.51:3333
WALLET=m2test
USER=11x12
ALGO=nexapow

./rigel -a $ALGO -o $POOL -u $WALLET -w $USER --log-file logs/miner.log
*/
func (l *LinuxClient) rigel(cfg message.ConfigurationMiningMsg) {
	l.mu.Lock()
	if l.Status != 2 {
		log.Printf("当前还有挖矿任务正在进行中：币=%s, 算法=%s, 矿池=%s, 截止时间=%d", cfg.Coin, cfg.Algo, cfg.Pool, cfg.EndTimestamp)
		return
	}
	l.Status = 1
	l.mu.Unlock()
	var address string
	if cfg.WalletMining {
		address = cfg.WalletAddress
	} else {
		address = cfg.PoolUser
	}
	dir := l.MiningConfig.RigelPath
	name := filepath.Join(dir, "rigel")
	// 禁用 rigel 内置 watchdog，避免自动拉起子进程
	args := []string{"--no-watchdog", "-a", strings.ToLower(cfg.Algo), "-o", cfg.PoolUrl, "-u", address, "-w", cfg.WorkerID, "--log-file", "logs/miner.log"}
	cmd := exec.Command(name, args...)
	cmd.Dir = dir
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	log.Printf("执行命令：%s %s", name, strings.Join(args, " "))
	// 启动进程
	err := cmd.Start()
	if err != nil {
		log.Printf("Error starting rigel: %v", err)
		return
	}
	// 添加执行记录
	go func() {
		err := l.db.InsertMiningTask(cfg)
		if err != nil {
			log.Printf("本次挖矿任务记录失败：%v", err)
		}
	}()
	// 获取 rigel 的进程 ID
	fmt.Printf("rigel started with PID: %d\n", cmd.Process.Pid)

	// 记录当前挖矿进程
	l.mu.Lock()
	l.currentProcess = &currentProcess{
		process: cmd,
		miner:   "rigel",
	}
	l.mu.Unlock()
	// 获取当前时间戳（秒级）
	currentTimestamp := time.Now().Unix()
	endTimestamp := int64(cfg.EndTimestamp)
	// 计算目标时间戳和当前时间戳之间的差值
	if endTimestamp <= currentTimestamp {
		// 如果目标时间已经到达，立即结束 rigel 挖矿进程
		fmt.Println("目标时间已经到达，立即结束 rigel 挖矿进程")
		l.StopMining()
	} else {
		// 计算需要等待的秒数
		waitDuration := time.Second * time.Duration(endTimestamp-currentTimestamp)
		fmt.Printf("当前时间戳：%d，目标时间戳：%d，剩余时间：%v\n", currentTimestamp, endTimestamp, waitDuration)
		// 使用 time.Sleep 等待直到目标时间戳
		time.Sleep(waitDuration)
		fmt.Println("目标时间到达，开始执行操作，结束 rigel 挖矿进程")
		// 通过 StopMining 统一停止挖矿进程
		l.StopMining()
		// 修改执行记录
		go func() {
			err := l.db.FinishMiningTask(cfg)
			if err != nil {
				log.Printf("修改执行记录失败：%v", err)
			}
		}()
		// 输出进程被结束的信息
		fmt.Println("rigel process killed.")
	}
	log.Printf("当前挖矿任务已执行完毕：币=%s, 算法=%s, 矿池=%s, 截止时间=%d", cfg.Coin, cfg.Algo, cfg.Pool, cfg.EndTimestamp)
	l.mu.Lock()
	l.Status = 2
	l.mu.Unlock()
}

// XTM-rigel(1%), XNA-bzminer(1%), CLORE-bzminer(1%), CFX-rigel(2%), IRON-lolminer(1%), NEXA-lolminer(2%), KLS-lolminer(1%), RVN-bzminer(1%), ERG-bzminer(1%), XEL-rigel(2%)
func (l *LinuxClient) Mining(cfg message.ConfigurationMiningMsg) error {
	info := cfg.Coin + "-" + cfg.Algo
	switch info {
	case "XTM-SHA3X":
		go l.lolminer(cfg)
	case "XNA-KawPow":
		go l.bzminer(cfg)
	case "CLORE-KawPow":
		go l.bzminer(cfg)
	case "CFX-Octopus":
		go l.rigel(cfg)
	case "IRON-IronFish":
		go l.lolminer(cfg)
	case "NEXA-NexaPow":
		go l.lolminer(cfg)
	case "KLS-KarlsenHash":
		go l.lolminer(cfg)
	case "RVN-KawPow":
		go l.bzminer(cfg)
	case "ERG-Autolykos":
		go l.bzminer(cfg)
	case "XEL-Xelishashv2":
		go l.rigel(cfg)
	default:
		return fmt.Errorf("不支持%s算法", info)
	}
	return nil
}

// 主动终止当前挖矿进程
func (l *LinuxClient) StopMining() {
	l.mu.Lock()
	cp := l.currentProcess
	l.mu.Unlock()

	if cp == nil || cp.process == nil || cp.process.Process == nil {
		log.Println("当前没有正在进行的挖矿任务")
		return
	}

	log.Printf("准备停止当前挖矿任务，miner=%s, pid=%d", cp.miner, cp.process.Process.Pid)

	// 根据挖矿软件类型选择合适的停止方式
	switch strings.ToLower(cp.miner) {
	case "rigel":
		// 优先尝试优雅退出（Interrupt），失败再 Kill
		if err := cp.process.Process.Signal(os.Interrupt); err != nil {
			log.Printf("向 rigel 发送 Interrupt 失败，尝试 Kill: %v", err)
			if errKill := cp.process.Process.Kill(); errKill != nil {
				log.Printf("Kill rigel 失败：%v", errKill)
			}
		}
	default:
		// 其它挖矿软件直接 Kill
		if err := cp.process.Process.Kill(); err != nil {
			log.Printf("停止挖矿进程失败：%v", err)
		}
	}

	// 清理状态
	l.mu.Lock()
	l.currentProcess = nil
	l.Status = 2
	l.mu.Unlock()

	log.Println("当前挖矿任务已被手动停止")
}
