package app

import (
	"bytes"
	"chain33-pai/pkg/chain33"
	"chain33-pai/pkg/file"
	"chain33-pai/pkg/pai"
	"chain33-pai/pkg/setting"
	"chain33-pai/pkg/util"
	"chain33-pai/service/pai_service"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/33cn/chain33/common/log"
	"github.com/33cn/chain33/types"
	types2 "github.com/33cn/plugin/plugin/dapp/ticket/types"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"sync"
	"time"
)

var tlog = log.New("pkg", "app")

type ProcessInfo struct {
	PName string `json:"p_name"`
	Pid   string `json:"pid"`
	User  string `json:"user"`
	Path  string `json:"path"`
}

type updateInfo struct {
	Flag    bool  `json:"flag"` //接口处理完成
	Total   int64 `json:"total"`
	Current int64 `json:"current"`
}

type Server struct {
	Disks           []*pai.Disk `json:"disks"`             //硬盘信息
	NodeProcessInfo ProcessInfo `json:"node_process_info"` //节点信息
	Error           string      `json:"error"`             //错误信息
}

type BTYDownloadInfo struct {
	IsDownload bool
	Version    string
}

type BityuanOp struct {
	Flag bool
	Lock sync.Mutex
}

var BityuanFlag BityuanOp //true表示有任务在占用，禁止其他重复操作
var Bityuan ProcessInfo
var UpdateInfo updateInfo
var NodeError error
var ServerStatus Server
var RebootP bool

func getProcessInfo(keyfile string) string {
	var buffer bytes.Buffer     //
	var buffer_res bytes.Buffer //
	lsof := exec.Command("ps", "aux")
	grep := exec.Command("grep", keyfile)
	head := exec.Command("grep", "-v", "grep")
	read, write := io.Pipe() // Create a pipe
	defer read.Close()
	defer write.Close()
	lsof.Stdout = write   //write output into pipe write
	grep.Stdin = read     // grep read from pipe
	grep.Stdout = &buffer // grep output
	lsof.Start()
	grep.Start()
	lsof.Wait()
	write.Close()
	grep.Wait()
	head.Stdin = &buffer
	head.Stdout = &buffer_res
	head.Start()
	head.Wait()
	return strings.Replace(buffer_res.String(), "\n", "", -1)
}

func getWalletInfo() error {
	rawProcessInfo := getProcessInfo(setting.BityuanSetting.Name)
	tlog.Info("getWalletInfo", "wallet", rawProcessInfo)
	if rawProcessInfo == "" {
		//钱包节点未启动
		StartProcess(setting.Chain33Pai.Auto)
		var buffer bytes.Buffer
		cmd := exec.Command("find", "/media", "-name", setting.BityuanSetting.Name)
		cmd.Stdout = &buffer
		cmd.Start()
		cmd.Wait()
		if buffer.String() == "" {
			tlog.Info("node dose not exist")
			return fmt.Errorf("node dose not exist")
		}
		fmt.Println(buffer.String())
		rawProcessInfo = getProcessInfo(setting.BityuanSetting.Name)
		if rawProcessInfo == "" {
			tlog.Info("node down")
			return errors.New("node down")
		}

	}

	record := StrFilter(strings.Split(rawProcessInfo, " "), "")
	{
		Bityuan.User = record[0]
		Bityuan.Pid = record[1]
		Bityuan.Path = record[10]
		Bityuan.PName = path.Base(Bityuan.Path)
		ServerStatus.NodeProcessInfo = Bityuan
	}
	return nil
}

func StrFilter(str []string, filter string) (res []string) {
	for i := 0; i < len(str); i++ {
		if str[i] != filter {
			res = append(res, str[i])
		}
	}

	return
}

//corn job for collecting chain33 process info
func BityuanJob(ticker *time.Ticker) {

	//NodeError = getWalletInfo()
	for {
		select {
		case <-ticker.C:
			BityuanFlag.Lock.Lock()
			if !BityuanFlag.Flag && file.CheckNotExist("/home/pi/chain33-pai/Lock.file") { //有更新，重置等节点重启操作时 禁止运行，防止冲突
				//节点未启动 会调用auto启动
				NodeError = getWalletInfo()
				//解锁钱包
				if NodeError == nil {
					status, err := GetWalletStatus()
					if err != nil {
						tlog.Error("GetWalletStatus", "err", err)
					} else if status != nil && status.IsHasSeed {
						if status.IsTicketLock && status.IsWalletLock {
							err := Unlock()
							if err != nil {
								tlog.Error("unlock wallet", "err", err)
							}
						}
						//if !status.IsAutoMining {
						//	err := AutoOpenTicket()
						//	if err != nil {
						//		tlog.Error("AutoOpenTicket", "err", err)
						//	}
						//}
					}
				}
				//升级6.4.0bityuan
				//updateBityuan640()
			}
			BityuanFlag.Lock.Unlock()
		}
	}
}

func Copy(src, dst string) {
	cp := exec.Command("cp", "-r", src, dst)
	cp.Start()
	cp.Wait()
}

func StartProcess(scriptsAddr string) (err error) {
	cmd := exec.Command(scriptsAddr)
	err = cmd.Start()
	if err != nil {
		tlog.Info("StartProcess", "exec shell err", err)
		return
	}

	err = cmd.Wait()
	if err != nil {
		tlog.Info("StartProcess", "exec shell err", err)
		return
	}
	return
}

var DPercent DownloadPercent

type DownloadPercent struct {
	Total   int64
	Current int64
	Flag    bool
}

func DownLoadFile(url string, file string, ty int32) error {
	var (
		buf = make([]byte, 32*1024)
	)
	r, err := http.Get(url)
	if err != nil {
		return err
	}
	defer func() {
		r.Body.Close()
	}()

	f, err := os.Create(file)
	if err != nil {
		return err
	}
	defer func() {
		f.Close()
	}()
	if ty == int32(1) {
		UpdateInfo.Total = r.ContentLength
	} else {
		DPercent.Total = r.ContentLength
	}

	//reader := &Reader{
	//	Reader:r.Body,
	//	Total:r.ContentLength,
	//}
	//io.Copy(f,reader)
	for {
		//读取bytes
		nr, er := r.Body.Read(buf)
		if nr > 0 {
			//写入bytes
			nw, ew := f.Write(buf[0:nr])
			//数据长度大于0
			if nw > 0 {
				if ty == int32(1) {
					UpdateInfo.Current += int64(nw)
				} else {
					DPercent.Current += int64(nw)
				}

			}
			//写入出错
			if ew != nil {
				err = ew
				break
			}
			//读取是数据长度不等于写入的数据长度
			if nr != nw {
				err = io.ErrShortWrite
				break
			}
		}
		if er != nil {
			if er != io.EOF {
				err = er
			}
			break
		}
		//没有错误了快使用 callback
	}

	return nil
}

func GetAbsPath(key string) string {
	str := getProcessInfo(key)
	sstr := strings.Split(str, "\n")
	regstr := util.DeleteExtraSpace(sstr[0])
	params := strings.Split(regstr, " ")
	if len(params) == 11 {
		if params[10] != "" {
			return path.Dir(params[10])
		}
		return ""
	}
	return ""
}

func GetNodePid(key string) string {
	str := getProcessInfo(key)
	sstr := strings.Split(str, "\n")
	regstr := util.DeleteExtraSpace(sstr[0])
	params := strings.Split(regstr, " ")
	if len(params) == 11 {
		if params[1] != "" {
			return params[1]
		}
		return ""
	}
	return ""
}

func SafeCloseNode() (bool, error) {
	//var client chain33.PaiClient
	//reply,err := client.Close()
	//if err != nil {
	//	return false,err
	//}
	//if !reply.IsOk {
	//	return false,nil
	//}
	//return true,nil
	path := GetAbsPath(setting.BityuanSetting.Name)
	if path == "" {
		return false, nil
	}
	close := exec.Command(path+"/"+setting.BityuanSetting.Name+"-cli", "close")
	stderr := bytes.NewBuffer(nil)
	close.Stderr = stderr
	err := close.Start()
	if err != nil {
		return false, err
	}
	err = close.Wait()
	if err != nil {
		return false, errors.New(stderr.String())
	}
	return true, nil
}

func GetPubKey() string {
	f, err := os.Open("/root/.ssh/id_rsa.pub")
	if err != nil {
		tlog.Info(err.Error())
	}
	r, _ := ioutil.ReadAll(f)

	return string(r)
}

func Preprocessing() { //判断密钥对是否存在,存在则删除
	_, err := os.Stat("/root/.ssh")
	if err == nil || os.IsExist(err) {
		os.RemoveAll("/root/.ssh")
	}
}

func cp(src, dst string) { //复制文件并赋予执行权限
	cp := exec.Command("cp", "-rf", src, dst)
	os.Chmod(src, 0755)
	cp.Run()
}
func MVScripts(src, dst string) { //Absolute Path
	path, err := os.Getwd()
	err = os.Chdir(src)
	if err != nil {
		tlog.Info("invalid directory")
	}
	dir, _ := ioutil.ReadDir("./")
	for _, f := range dir {
		if f.IsDir() && f.Name() == "scripts" {
			dir2, _ := ioutil.ReadDir(f.Name() + "/")
			os.Chdir(f.Name())
			for _, s := range dir2 {
				if s.Name() == ".gitkeep" {
					continue
				}
				cp(s.Name(), path+"/"+f.Name()+"/"+s.Name())
			}

		}
	}
	os.Chdir(path)
}

func Unlock() error {
	var passwd []byte
	f, err := os.Open(setting.BityuanSetting.Passwd)
	if err != nil {
		tlog.Info("password dose not set")
		return err
	}
	passwd, err = ioutil.ReadAll(f)
	if err != nil {
		tlog.Info("Read passwd failed")
		return err
	}
	pass := strings.Replace(string(passwd), "\n", "", -1)
	reply, err := setting.PaiClient.Unlock(&types.WalletUnLock{
		Passwd:               pass,
		Timeout:              0,
		WalletOrTicket:       false,
		XXX_NoUnkeyedLiteral: struct{}{},
		XXX_unrecognized:     nil,
		XXX_sizecache:        0,
	})
	if err != nil {
		tlog.Info("unlock auth failed")
		return err
	}
	if !reply.IsOk {
		tlog.Error("unlock auth failed", "err", string(reply.Msg))
	}
	return nil
}

type clientRequest struct {
	Method string         `json:"method"`
	Params [1]interface{} `json:"params"`
	ID     uint64         `json:"id"`
}

func AutoOpenTicket() error {
	params := clientRequest{}
	params.ID = 1
	params.Method = "ticket.SetAutoMining"
	p := [1]interface{}{}
	p[0] = types2.MinerFlag{Flag: 1}
	params.Params = p
	data, err := json.Marshal(params)
	if err != nil {
		tlog.Error("AutoOpenTicket", "marshal", err)
		return err
	}
	client := &http.Client{}
	req, err := http.NewRequest("POST", "http://localhost:8801", bytes.NewReader(data))
	if err != nil {
		tlog.Error("AutoOpenTicket", "NewRequest", err)
		return err
	}
	resp, err := client.Do(req)
	if err != nil {
		tlog.Error("AutoOpenTicket", "NewRequest do", err)
		return err
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		tlog.Error("AutoOpenTicket", "read", err)
		return err
	}
	tlog.Info("AutoOpenTicket", "err", string(body))
	return nil
}

func GetWalletStatus() (*types.WalletStatus, error) {
	var p chain33.PaiClient
	return p.GetWalletStatus()
}

func PaiJob(tick *time.Ticker) {
	for {
		select {
		case <-tick.C:
			AutoUpdate()
			AutoSSHClean()

		}
	}
}

type PaiProcessInfo struct {
	Pid   int32
	Stime int64
	Cmd   string
}

func AutoSSHClean() error {
	pai, err := getAutoSSHProcessInfo()

	if err != nil {
		return err
	}
	tlog.Info("getAutoSSHProcessInfo", "pai", *pai)

	if pai.Stime > 0 && (time.Now().Unix()-pai.Stime) > 72*3600 && pai.Pid > 0 {
		err := KillAutoSSH(pai.Pid)
		if err != nil {
			tlog.Error("KillAutoSSH", "err", err)
			return err
		}
		tlog.Info("KillAutoSSH Success", "pid", pai.Pid)
	}
	return nil
}

func KillAutoSSH(pid int32) error {
	ps := fmt.Sprintf("%d", pid)
	cmd := exec.Command("kill", ps)
	return cmd.Run()
}

func getAutoSSHProcessInfo() (*PaiProcessInfo, error) {
	var pai PaiProcessInfo
	var buf bytes.Buffer
	cmd := exec.Command("bash", "-c", "ps -eo pid,lstart,cmd |grep autossh | grep -v grep")
	cmd.Stdout = &buf
	err := cmd.Run()
	if err != nil {
		tlog.Error("getAutoSSHProcessInfo cmd", "err", err)
		return nil, err
	}
	trimBuf := strings.Trim(buf.String(), " ")
	list := strings.Split(trimBuf, " ")
	if len(list) < 7 {
		return nil, errors.New("ps return exception")
	}
	lstart := list[1] + " " + list[2] + " " + list[3] + " " + list[4] + " " + list[5]
	location, _ := time.LoadLocation("Local")
	tt, err := time.ParseInLocation(time.ANSIC, lstart, location)
	if err != nil {
		tlog.Error("getAutoSSHProcessInfo", "err", err)
		return nil, err
	}
	start := tt.Unix()

	p, _ := strconv.Atoi(list[0])
	pai.Pid = int32(p)
	pai.Stime = start

	return &pai, nil
}

func AutoUpdate() {
	if RebootP { //重启pai
		err := KillPai()
		if err != nil {
			tlog.Error("kill pai", "err", err)
		} else {
			err := StartProcess(setting.Chain33Pai.Start)
			if err != nil {
				tlog.Error("restart pai fail", "err", err)
			} else {
				tlog.Info("kill restart pai success ")
			}
		}
	} else if !DPercent.Flag {
		//没有其他任务占用
		DPercent.Flag = true
		err := AutoUpdatePai()
		if err != nil {
			tlog.Error("autoupdatepai", "err", err)
		}
		tlog.Info("autoupdatepai", "err", GetVersion())
		DPercent.Flag = false
	}
}

func AutoUpdatePai() error {
	//对比版本号
	version := util.VersionCompare(GetVersion())
	//检查最新版本号online数据库
	var p pai_service.Pai
	latest, err := p.GetPaiLatestVersion(int32(2))
	if err != nil {
		return err
	}

	latestversion := util.VersionCompare(latest)
	if version >= latestversion {
		return nil
	}

	name := setting.Chain33Pai.Name + "_" + latest + ".tar.gz"
	if pai.GetArchType() == "x86_64" {
		name = setting.Chain33Pai.Name + "-x86_64_" + latest + ".tar.gz"
	}
	url := setting.Chain33Pai.DownloadUrl + name
	//oss下载更新包
	err = DownLoadFile(url, name, int32(2))
	if err != nil {
		return err
	}
	var serr bytes.Buffer
	//解压缩文件
	tar := exec.Command("tar", "-xvf", name, "-C", "../")
	tar.Stdout = &serr
	err = tar.Start()
	if err != nil {
		tlog.Error("tar", "err", err)
		return err
	}
	err = tar.Wait()
	if err != nil {
		tlog.Error("tar2", "err", serr.String())
		return err
	}
	remove := exec.Command("rm", "-rf", name)
	err = remove.Start()
	if err != nil {
		tlog.Error("rm ", "err", err)
		return err
	}
	err = remove.Wait()
	if err != nil {
		tlog.Error("rm wait", "err", err)
		return err
	}
	RebootP = true
	return nil
}

func KillPai() error {
	err := StartProcess(setting.Chain33Pai.StopPai)
	if err != nil {
		tlog.Error("stop chain33 pai ", "err", err)
	}
	return nil
}

func UploadInfo(tick *time.Ticker) {
	for {
		select {
		case <-tick.C:
			uploadInfo()
		}
	}
}

//上传基本信息
func uploadInfo() error {
	params := make(map[string]string)
	params["version"] = GetVersion()
	var pai pai_service.Pai
	err := pai.SetPai()
	if err != nil {
		tlog.Error("SetPai", "err", err)
		return err
	}
	jpai, _ := json.Marshal(pai)
	params["addr"] = pai.Serial
	params["info"] = string(jpai)

	data, err := json.Marshal(params)
	client := &http.Client{}
	req, err := http.NewRequest("POST", setting.Chain33Pai.UploadVersionUrl, bytes.NewReader(data))
	if err != nil {
		tlog.Error("uploadInfo", "NewRequest", err)
		return err
	}
	resp, err := client.Do(req)
	if err != nil {
		tlog.Error("uploadInfo", "NewRequest do", err)
		return err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		tlog.Error("uploadInfo", "read", err)
		return err
	}
	tlog.Info("uploadInfo", "result", string(body))
	return nil
}

func uploadVersion() error {
	client := &http.Client{}
	params := make(map[string]interface{})
	params["version"] = GetVersion()
	data, err := json.Marshal(params)
	if err != nil {
		tlog.Error("uploadVersion", "marshal", err)
		return err
	}
	req, err := http.NewRequest("POST", setting.Chain33Pai.UploadVersionUrl, bytes.NewReader(data))
	if err != nil {
		tlog.Error("uploadVersion", "NewRequest", err)
		return err
	}
	resp, err := client.Do(req)
	if err != nil {
		tlog.Error("uploadVersion", "NewRequest do", err)
		return err
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		tlog.Error("uploadVersion", "read", err)
		return err
	}
	tlog.Info("uploadversion", "version", string(body))
	return nil
}

//update bityuan 6.3.x to  6.4.0
func updateBityuan640() error {

	BityuanFlag.Lock.Lock()
	defer BityuanFlag.Lock.Unlock()
	BityuanFlag.Flag = true
	//2020/4/2 20:0:0
	plantime := int64(1585828800)
	nowtime := time.Now().Unix()
	if nowtime < plantime {
		tlog.Error("waiting plantime to update", "nowtime", nowtime)
		BityuanFlag.Flag = false
		return errors.New("waiting plantime to update")
	}
	update := GetNodeUpdateStatus()
	if update {
		tlog.Info("updateBityuan640 is updating", "update", update)
		BityuanFlag.Flag = false
		return nil
	}
	var pai chain33.PaiClient
	v, err := pai.Version()
	if err != nil {
		tlog.Error("get version ", "err", err)
		BityuanFlag.Flag = false
		return err
	}
	if strings.Contains(v.App, "6.4.2") {
		tlog.Info("bityuan do not need update", "version", v.App)
		BityuanFlag.Flag = false
		return nil
	}

	stderr := bytes.NewBuffer(nil)
	absPath := GetAbsPath(setting.BityuanSetting.Name) + "/"
	name := setting.BityuanSetting.Name + "_6.4.2.tar.gz"
	url := setting.Chain33Pai.DownloadUrl + name
	tlog.Info("UpdateNode", "dir", name)
	tlog.Info("UpdateNode", "path", absPath)
	err = DownLoadFile(url, name, int32(1))
	if err != nil {
		tlog.Error("updateBityuan640", "err", err)
		BityuanFlag.Flag = false
		return err
	}
	dirs := strings.Split(absPath, "wallet-bty")
	if len(dirs) != 2 {
		tlog.Error("updateBityuan640", "err", err)
		BityuanFlag.Flag = false
		return err
	}
	//解压缩文件
	tar := exec.Command("tar", "-xvf", name, "-C", dirs[0])
	tar.Stderr = stderr
	err = tar.Start()
	if err != nil {
		tlog.Error("tar", "err", err)
		BityuanFlag.Flag = false
		return err
	}
	err = tar.Wait()
	if err != nil {
		tlog.Error("tar", "err", stderr.String())
		BityuanFlag.Flag = false
		return err
	}
	t, err := SafeCloseNode()
	if err != nil || !t {
		tlog.Error("updateBityuan640 close node", "err", err)
		BityuanFlag.Flag = false
		return err
	}
	//其他模块可能没有即时关闭，在高速刷盘的情形下
	time.Sleep(time.Second * 2)
	remove := exec.Command("rm", "-rf", name)
	remove.Stderr = stderr
	err = remove.Start()
	if err != nil {
		tlog.Error("rm", "err", stderr.String())
		BityuanFlag.Flag = false
		return err
	}
	err = remove.Wait()
	if err != nil {
		tlog.Error("rm", "err", stderr.String())
		BityuanFlag.Flag = false
		return err
	}
	err = StartProcess(setting.Chain33Pai.Auto)
	if err != nil {
		tlog.Error("updateBityuan640", "auto.sh error ", err)
		BityuanFlag.Flag = false
		return err
	}
	tlog.Info("updateBityuan640 success please waiting hours", "version", "6.3.2->6.4.2")
	BityuanFlag.Flag = false
	return nil
}
func GetNodeUpdateStatus() bool {
	var buf bytes.Buffer
	cmd := exec.Command("netstat", "-tunlp")
	cmd.Stdout = &buf
	err := cmd.Run()
	if err != nil {
		return false
	}
	//程序运行 + 端口没开启
	if strings.Contains(buf.String(), "v7-bityuan") && !strings.Contains(buf.String(), ":8801") && strings.Contains(buf.String(), ":6060") {
		return true
	} else if !strings.Contains(buf.String(), "v7-bityuan") {
		return false
	}
	return false
}
