package app

import (
	"bytes"
	pai2 "chain33-pai/pkg/pai"
	"chain33-pai/pkg/setting"
	"chain33-pai/pkg/util"
	"chain33-pai/service/pai_service"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

var (
	btyPath    string
	JobID      int32
	JobDoneMap = make(map[int32]string)
)

func RaspberryChan() {
	for {
		if value, ok := <-JobChan; ok {
			tlog.Info("raspberrychan", "msg", value)
			if value.Name == "" {
				continue
			}
			path := GetAbsPath(setting.BityuanSetting.Name)
			if path != "" {
				tlog.Info("GetAbsPath node", "path", path)
				btyPath = path
			}
			switch value.Name {
			case "ROLLBACK":
				err := rollback()
				if err != nil {
					tlog.Error("RaspberryChan rollback fail ", "err", err)
				}
				updateJobMap(value.JobID, err)
				tlog.Info("RaspberryChan rollback success", "err", nil)
			case "BACKUP":
				err := backup()
				if err != nil {
					tlog.Error("RaspberryChan backup fail ", "err", err)
				}
				updateJobMap(value.JobID, err)
				tlog.Info("RaspberryChan backup success", "err", nil)
			case "RECOVER":
				err := recoverNode()
				if err != nil {
					tlog.Error("RaspberryChan recover fail ", "err", err)
				}
				updateJobMap(value.JobID, err)
				tlog.Info("RaspberryChan recover success", "err", nil)
			case "DELETEBACKUP":
				err := deleteBackup()
				if err != nil {
					tlog.Error("RaspberryChan deleteBackup fail ", "err", err)
				}
				updateJobMap(value.JobID, err)
				tlog.Info("RaspberryChan deleteBackup success", "err", nil)
			case "RESTART":
				err := restartNode()
				if err != nil {
					tlog.Error("RaspberryChan RESTART fail ", "err", err)
				}
				updateJobMap(value.JobID, err)
				tlog.Info("RaspberryChan RESTART success", "err", nil)
			case "RESET":
				err := resetNode()
				if err != nil {
					tlog.Error("RaspberryChan resetNode fail ", "err", err)

				}
				updateJobMap(value.JobID, err)
				tlog.Info("RaspberryChan resetNode success", "err", nil)
			case "RESETWALLET":
				err := resetWallet()
				if err != nil {
					tlog.Error("RaspberryChan resetWallet fail ", "err", err)
				}
				updateJobMap(value.JobID, err)
				tlog.Info("RaspberryChan resetWallet success", "err", nil)
			case "CLOSE":
				err := closeNode()
				if err != nil {
					tlog.Error("RaspberryChan resetWallet fail ", "err", err)
				}
				updateJobMap(value.JobID, err)
				tlog.Info("RaspberryChan resetWallet success", "err", nil)
			case "NODEUPDATE":
				err := nodeUpdate()
				if err != nil {
					tlog.Error("RaspberryChan nodeUpdate fail ", "err", err)
				}
				updateJobMap(value.JobID, err)
				tlog.Info("RaspberryChan nodeUpdate", "err", err)
			case "CLEARLOG": //清理日志 为其他操作做准备
				err := clearLog()
				if err != nil {
					tlog.Error("RaspberryChan clearLog fail ", "err", err)
				}
				updateJobMap(value.JobID, err)
				tlog.Info("RaspberryChan clearLog", "err", err)
			default:
				tlog.Error("RaspberryChan not support type", "msg", value.Name)
				updateJobMap(value.JobID, errors.New("RaspberryChan not support type"))
			}

		}
		time.Sleep(time.Second * 1)
	}
}

func clearLog() error {
	BityuanFlag.Lock.Lock()
	defer BityuanFlag.Lock.Unlock()
	if btyPath != "" {
		remove := exec.Command("rm", "-rf", btyPath+"/logs/*.gz")
		err := remove.Run()
		if err != nil {
			tlog.Error("rm -rf datadir_backup", "err", err)
			return err
		}
		return nil
	}
	tlog.Error("clearLog ", "err", "btypath not exists")
	return errors.New("btypath is empty")
}

func nodeUpdate() error {
	BityuanFlag.Lock.Lock()
	defer BityuanFlag.Lock.Unlock()
	var pai pai_service.Pai
	var latestVersion string
	err := pai.GetBtyVersion()
	if err != nil {
		tlog.Error("nodeupdate", "err", err)
		return err
	}
	//判断当前环境为测试环境还是正式环境
	if setting.Chain33Pai.Env == "formal" {
		latestVersion, err = pai.GetPaiLatestVersion(int32(1))
		if err != nil {
			return err
		}
	} else if setting.Chain33Pai.Env == "test" {
		latestVersion, err = pai.GetPaiVersionTest(int32(1))
		if err != nil {
			return err
		}
	}

	tlog.Info("nodeupdate", "node version", pai.BtyVersion.App, "last version", latestVersion)
	if util.VersionCompare(latestVersion) <= util.VersionCompare(pai.BtyVersion.App) {
		return nil
	}
	name := setting.BityuanSetting.Name + "_" + latestVersion + ".tar.gz"
	url := setting.Chain33Pai.DownloadUrl + name
	if pai2.GetArchType() == "x86_64" {
		name = setting.BityuanSetting.Name + "-linux_" + latestVersion + ".tar.gz"
		url = setting.Chain33Pai.DownloadUrl + name
	} else if pai2.GetArchType() == "arm" {
		name = setting.BityuanSetting.Name + "-arm_" + latestVersion + ".tar.gz"
		url = setting.Chain33Pai.DownloadUrl + name
	}
	tlog.Info("chan UpdateNode", "dir", setting.BityuanSetting.Name+"_"+latestVersion)
	tlog.Info("chan UpdateNode", "path", btyPath)
	tlog.Info("chan downloadUrl", "url", url)
	err = DownLoadFile(url, name, 1)
	if err != nil {
		tlog.Error("nodeupdate download", "err", err)
		return err
	}
	_, err = SafeCloseNode()
	if err != nil {
		return err
	}
	isRun := MakeSureBtyIsNotRun()
	if isRun {
		tlog.Error("MakeSureBtyIsNotRun", "isRun", isRun)
		return nil
	}
	btyPathSplit := strings.Split(btyPath, "wallet-bty")
	if len(btyPathSplit) != 2 {
		return errors.New("btyPath not contains wallet-bty")
	}
	tar := exec.Command("tar", "-xvf", name, "-C", btyPathSplit[0])
	stdErr := bytes.NewBuffer(nil)
	tar.Stderr = stdErr
	err = tar.Start()
	if err != nil {
		tlog.Error("tar ", "err", string(stdErr.Bytes()))
		return err
	}
	err = tar.Wait()
	if err != nil {
		tlog.Error("tar ", "err", string(stdErr.Bytes()))
		return err
	}
	time.Sleep(time.Second * 1)
	rm := exec.Command("rm", "-rf", name)
	rm.Stderr = stdErr
	err = rm.Start()
	if err != nil {
		tlog.Error("rm ", "err", string(stdErr.Bytes()))
		return err
	}
	err = rm.Wait()
	if err != nil {
		tlog.Error("rm ", "err", string(stdErr.Bytes()))
		return err
	}
	StartProcess(setting.Chain33Pai.Auto)
	return nil

}

func rollback() error {
	BityuanFlag.Lock.Lock()
	defer BityuanFlag.Lock.Unlock()
	BityuanFlag.Flag = true
	_, err := SafeCloseNode()
	if err != nil {
		BityuanFlag.Flag = false
		return err
	}
	var pai pai_service.Pai
	err = pai.SetPai()
	if err != nil {
		tlog.Error("SetPai", "err", err)
		BityuanFlag.Flag = false
		return err
	}
	if pai.LocalLastHeight == 0 {
		tlog.Error("LocalLastHeight", "height", pai.LocalLastHeight)
		BityuanFlag.Flag = false
		return err
	}
	rollheight := pai.LocalLastHeight - 1000
	if (pai.LocalLastHeight - 1000) <= 0 {
		rollheight = 1
	}
	isrun := MakeSureBtyIsNotRun()
	if isrun {
		BityuanFlag.Flag = false
		return errors.New("bty is running")
	}
	tlog.Info("rollback start", "height", pai.LocalLastHeight, "rollbackheight", rollheight)
	var buf bytes.Buffer
	if btyPath == "" {
		tlog.Error("GetAbsPath", "err", "btyPath not exists")
		BityuanFlag.Flag = false
		return err
	}
	cmd := exec.Command(btyPath+"/"+setting.BityuanSetting.Name, "-rollback", fmt.Sprintf("%d", rollheight))
	cmd.Stdout = &buf
	err = cmd.Run()
	if err != nil {
		BityuanFlag.Flag = false
		return err
	}
	tlog.Info("rollback end", "height", pai.LocalLastHeight, "rollbackheight", rollheight)
	BityuanFlag.Flag = false
	return nil
}

func backup() error {
	BityuanFlag.Lock.Lock()
	defer BityuanFlag.Lock.Unlock()
	if BityuanFlag.Flag {
		return errors.New("env err")
	}
	check := checkDiskStatus()
	if !check {
		return errors.New("disk not aviable")
	}
	BityuanFlag.Flag = true
	ok, err := SafeCloseNode()
	if err != nil {
		BityuanFlag.Flag = false
		tlog.Error("SafeCloseNode", "err", err)
		return err
	}
	if !ok {
		BityuanFlag.Flag = false
		return errors.New("env err")
	}
	isrun := MakeSureBtyIsNotRun()
	if isrun {
		BityuanFlag.Flag = false
		return errors.New("bty is running")
	}
	err = deleteBackup()
	if err != nil && err != errors.New("backup file not exists") {
		BityuanFlag.Flag = false
		return err
	}
	err = SafeBackup()
	if err != nil {
		return err
	}
	BityuanFlag.Flag = false
	StartProcess(setting.Chain33Pai.Auto)
	return nil
}

func checkDiskStatus() bool {
	var pai pai_service.Pai
	err := pai.GetDiskUseage()
	if err != nil {
		tlog.Error("GetDiskUseage", "err", err)
		return false
	}
	if len(pai.Disks) == 0 {
		tlog.Error("disk ", "err", "disk not find")
		return false
	}
	tlog.Info("disk info", "ssd", pai.Disks[0])
	p := pai.Disks[0].Use
	ps := strings.Split(p, "%")
	pint, _ := strconv.Atoi(ps[0])
	if pint*2 > 80 {
		tlog.Error("disk space ", "err", "space not enough to backup")
		return false
	}
	return true
}

func SafeBackup() error {
	s := time.Now()
	defer func() {
		tlog.Info("SafeBackup", "cost", time.Since(s))
	}()
	var buf bytes.Buffer
	backup := exec.Command("cp", "-r", btyPath+"/datadir", btyPath+"/datadir_backup")
	backup.Stderr = &buf
	if err := backup.Run(); err != nil {
		tlog.Error("SafeBackup", "err", buf.String())
		return err
	}
	return nil
}

func recoverNode() error {
	BityuanFlag.Lock.Lock()
	defer BityuanFlag.Lock.Unlock()
	if btyPath == "" {
		tlog.Error("btyPath empty", "err", "node not exists")
		return errors.New("node not exists")
	}
	_, err := SafeCloseNode()
	if err != nil {
		return err
	}
	isrun := MakeSureBtyIsNotRun()
	if isrun {
		return errors.New("bty is running")
	}
	backupPath := btyPath + "/datadir_backup"
	if _, err := os.Stat(backupPath); err != nil {
		if os.IsNotExist(err) {
			tlog.Error("backup file not exists", "err", backupPath)
			return errors.New("backup file not exists")
		}
	}
	remove := exec.Command("rm", "-rf", btyPath+"/datadir")
	err = remove.Run()
	if err != nil {
		tlog.Error("rm -rf datadir", "err", err)
		return err
	}

	recover := exec.Command("cp", "-r", backupPath, btyPath+"/datadir")
	err = recover.Run()
	if err != nil {
		tlog.Error("cp -r datadir_backup datadir", "err", err)
		return err
	}
	StartProcess(setting.Chain33Pai.Auto)
	return nil
}

func deleteBackup() error {
	s := time.Now()
	defer func() {
		tlog.Info("deleteBackup", "cost", time.Since(s))
	}()
	backupPath := btyPath + "/datadir_backup"
	if _, err := os.Stat(backupPath); err != nil {
		if os.IsNotExist(err) {
			tlog.Error("backup file not exists", "err", backupPath)
			return nil
		}
	}
	remove := exec.Command("rm", "-rf", backupPath)
	err := remove.Run()
	if err != nil {
		tlog.Error("rm -rf datadir_backup", "err", err)
		return err
	}
	return nil
}

func restartNode() error {
	BityuanFlag.Lock.Lock()
	defer BityuanFlag.Lock.Unlock()
	if btyPath == "" {
		tlog.Error("btyPath empty", "err", "node not exists")
		return errors.New("node not exists")
	}
	_, err := SafeCloseNode()
	if err != nil {
		return err
	}
	isrun := MakeSureBtyIsNotRun()
	if isrun {
		return errors.New("bty is running")
	}
	StartProcess(setting.Chain33Pai.Auto)
	return nil
}

func resetNode() error {
	BityuanFlag.Lock.Lock()
	defer BityuanFlag.Lock.Unlock()
	if btyPath == "" {
		tlog.Error("btyPath empty", "err", "node not exists")
		return errors.New("node not exists")
	}
	_, err := SafeCloseNode()
	if err != nil {
		return err
	}
	isrun := MakeSureBtyIsNotRun()
	if isrun {
		return errors.New("bty is running")
	}
	err = os.RemoveAll(fmt.Sprintf("%s/datadir", btyPath))
	if err != nil {
		return err
	}
	StartProcess(setting.Chain33Pai.Auto)
	return nil
}

func resetWallet() error {
	BityuanFlag.Lock.Lock()
	defer BityuanFlag.Lock.Unlock()
	if btyPath == "" {
		tlog.Error("btyPath empty", "err", "node not exists")
		return errors.New("node not exists")
	}
	_, err := SafeCloseNode()
	if err != nil {
		return err
	}
	isrun := MakeSureBtyIsNotRun()
	if isrun {
		return errors.New("bty is running")
	}
	err = os.RemoveAll(fmt.Sprintf("%s/wallet", btyPath))
	if err != nil {
		return err
	}
	StartProcess(setting.Chain33Pai.Auto)
	return nil
}

func closeNode() error {
	BityuanFlag.Lock.Lock()
	defer BityuanFlag.Lock.Unlock()
	if btyPath == "" {
		tlog.Error("btyPath empty", "err", "node not exists")
		return errors.New("node not exists")
	}
	_, err := SafeCloseNode()
	if err != nil {
		return err
	}
	isrun := MakeSureBtyIsNotRun()
	if isrun {
		return errors.New("bty is running")
	}
	BityuanFlag.Flag = true
	return nil
}

func updateJobMap(id int32, err error) bool {
	if _, exists := JobDoneMap[id]; !exists {
		if err == nil {
			JobDoneMap[id] = ""
		} else {
			JobDoneMap[id] = err.Error()
		}

		return true
	}
	tlog.Error("updateJobMap error", "jobid", id)
	return false
}

func MakeSureBtyIsNotRun() bool {
	s := time.Now()
	defer func() {
		tlog.Info("MakeSureBtyIsNotRun", "cost", time.Since(s))
	}()
	num := 0
	for {
		if num > 1000 {
			break
		}
		info := getProcessInfo(setting.BityuanSetting.Name)
		if info == "" {
			return false
		}
		num++
		time.Sleep(time.Second * 1)
	}
	return true
}
