package app

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

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 deleteBackup fail ", "err", err)
				}
				updateJobMap(value.JobID,err)
				tlog.Info("RaspberryChan deleteBackup 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)
			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 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")
	}
	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
}
