package v1

import (
	"fmt"
	"os"
	"log"
	"path"
	"reflect"
	"io/ioutil"
	"os/exec"
	"net/http"
	"encoding/json"
	"chain33-pai/pkg/e"
	"chain33-pai/pkg/app"
	"chain33-pai/pkg/setting"
	"github.com/gin-gonic/gin"
	"chain33-pai/service/pai_service"
	"bytes"
	"chain33-pai/pkg/chain33"
	"github.com/33cn/chain33/types"
	"strings"
	"time"
	"strconv"
)
var flag bool//判断是否正在更新

func ResetWallet(c *gin.Context) {
	appG := app.Gin{C: c}
	app.BityuanFlag.Lock.Lock()
	defer app.BityuanFlag.Lock.Unlock()
	if app.BityuanFlag.Flag {
		appG.Response(http.StatusOK, e.TRY_LATER,"other job is busy,please try later")
		return
	}
	app.BityuanFlag.Flag = true
	if app.NodeError!=nil{
		tlog.Error(app.NodeError.Error())
		appG.Response(http.StatusOK, e.NODE_ERROR,"node internal error,could not start node")
		app.BityuanFlag.Flag = false
		return
	}
	t,err := app.SafeCloseNode()
	if err != nil || !t {
		app.BityuanFlag.Flag = false
		appG.Response(http.StatusOK,e.NODE_ERROR,err)
		return
	}
	err=os.RemoveAll(fmt.Sprintf("%s/wallet",path.Dir(app.Bityuan.Path)))
	if err!=nil{
		appG.Response(http.StatusOK, e.NODE_ERROR, 	"fail to remove wallet")
		tlog.Error("ResetWallet","remove error",err)
		app.BityuanFlag.Flag = false
		return
	}

	err=app.StartProcess(setting.Chain33Pai.Auto)
	if err!=nil{
		appG.Response(http.StatusOK, e.NODE_ERROR,"fail to restart node")
		tlog.Error("ResetWallet","auto.sh error ",err)
		app.BityuanFlag.Flag = false
		return
	}
	appG.Response(http.StatusOK, e.SUCCESS,"reset complete")
	app.BityuanFlag.Flag = false
}

//钱包更新，只替换执行文件和配置文件
//具体更新逻辑不再代码实现
//oss下载包打包要保持执行权限
func UpdateNodeNew(c *gin.Context){
	appG := app.Gin{C: c}
	app.BityuanFlag.Lock.Lock()
	defer app.BityuanFlag.Lock.Unlock()
	if app.BityuanFlag.Flag {
		appG.Response(http.StatusOK,e.SUCCESS_RUNNING,"node is updating")
		return
	}
	if app.NodeError!=nil{
		appG.Response(http.StatusOK, e.NODE_ERROR,"node internal error,could not start node")
		return
	}
	absPath := app.GetAbsPath(setting.BityuanSetting.Name)+"/"
	pwd,err := os.Getwd()
	if err != nil {
		appG.Response(http.StatusOK, e.ERROR,"get pwd err")
		return
	}
	pwd = pwd + "/"
	app.BityuanFlag.Flag = true
	//app.UpdateInfo.Flag = false
	setting.FreshVersion()
	var pai pai_service.Pai

	latestVersion,err := pai.GetPaiLatestVersion(int32(1))
	if err != nil {
		appG.Response(http.StatusOK, e.NODE_ERROR,"get online version err")
		app.BityuanFlag.Flag = false
		//app.UpdateInfo.Flag = true
		return
	}
	if latestVersion==setting.BityuanSetting.Version{
		//app.UpdateInfo.Flag = true
		app.BityuanFlag.Flag = false
		appG.Response(http.StatusOK,e.SUCCESS,"current version is latest")
		return
	}

	if latestVersion==""{
		//app.UpdateInfo.Flag = true
		app.BityuanFlag.Flag = false
		appG.Response(http.StatusOK,e.NETWORK_ERROR,"network error,please checkout your network then try again")
		return
	}
	stderr := bytes.NewBuffer(nil)
	name := setting.BityuanSetting.Name+"_"+latestVersion+".tar.gz"
	url := setting.Chain33Pai.DownloadUrl+ name
	tlog.Info("UpdateNode","dir",setting.BityuanSetting.Name+"_"+latestVersion)
	tlog.Info("UpdateNode","path",absPath)
	err = app.DownLoadFile(url,name,int32(1))
	if err != nil {
		appG.Response(http.StatusOK, e.DOWNLOAD_ERROR, err)
		//app.UpdateInfo.Flag = true
		app.BityuanFlag.Flag = false
		return
	}
	dirs := strings.Split(absPath,"wallet-bty")
	if len(dirs) != 2 {
		appG.Response(http.StatusOK, e.ERROR, err)
		//app.UpdateInfo.Flag = true
		app.BityuanFlag.Flag = false
		return
	}
	//解压缩文件
	tar := exec.Command("tar","-xvf",name ,"-C",dirs[0])
	tar.Stderr = stderr
	err = tar.Start()
	if err != nil {
		tlog.Error("tar","err",err)
		appG.Response(http.StatusOK, e.TAR_XVF_ERROR, err)
		//app.UpdateInfo.Flag = true
		app.BityuanFlag.Flag = false
		return
	}
	err = tar.Wait()
	if err != nil {
		tlog.Error("tar","err",stderr.String())
		appG.Response(http.StatusOK, e.TAR_XVF_ERROR, stderr.String())
		//app.UpdateInfo.Flag = true
		app.BityuanFlag.Flag = false
		return
	}

	t,err := app.SafeCloseNode()
	if err != nil || !t {
		//app.UpdateInfo.Flag = true
		app.BityuanFlag.Flag = false
		appG.Response(http.StatusOK,e.NODE_ERROR,err)
		return
	}
	time.Sleep(time.Second*2)
	remove := exec.Command("rm","-rf",setting.BityuanSetting.Name+"_"+latestVersion,name)
	remove.Stderr = stderr
	err = remove.Start()
	if err != nil {
		appG.Response(http.StatusOK, e.RM_ERROR, err)
		//app.UpdateInfo.Flag = true
		app.BityuanFlag.Flag = false
		return
	}
	err = remove.Wait()
	if err != nil {
		appG.Response(http.StatusOK, e.RM_ERROR, stderr.String())
		//app.UpdateInfo.Flag = true
		app.BityuanFlag.Flag = false
		return
	}
	err=app.StartProcess(setting.Chain33Pai.Auto)
	if err!=nil{
		appG.Response(http.StatusOK, e.NODE_ERROR,"fail to restart node")
		tlog.Error("UpdateNodeNew","auto.sh error ",err)
		//app.UpdateInfo.Flag = true
		app.BityuanFlag.Flag = false
		return
	}
	app.BityuanFlag.Flag = false
	//app.UpdateInfo.Flag = true
	appG.Response(http.StatusOK,e.SUCCESS,"update job ok")
}

//重置节点只需要删除datadir 然后重启节点
func ResetNode(c* gin.Context){
	appG := app.Gin{C: c}
	app.BityuanFlag.Lock.Lock()
	defer app.BityuanFlag.Lock.Unlock()
	if app.BityuanFlag.Flag {
		appG.Response(http.StatusOK, e.TRY_LATER,"other job is busy,please try later")
		return
	}
	app.BityuanFlag.Flag = true
	_,err := app.SafeCloseNode()
	if err != nil {
		appG.Response(http.StatusOK,e.NODE_ERROR,"node err")
		app.BityuanFlag.Flag = false
		return
	}
	err = os.RemoveAll(fmt.Sprintf("%s/datadir",path.Dir(app.Bityuan.Path)))
	if err != nil {
		app.BityuanFlag.Flag = false
		tlog.Error("ResetNode","remove datadir err",err)
		appG.Response(http.StatusOK, e.RM_ERROR,"remove datadir err")
		return
	}
	err=app.StartProcess(setting.Chain33Pai.Auto)

	if err != nil {
		tlog.Error("ResetNode","start node err",err)
		app.BityuanFlag.Flag = false
		appG.Response(http.StatusOK, e.NODE_ERROR,"start auto sh err")
		return
	}
	app.BityuanFlag.Flag = false
	appG.Response(http.StatusOK,e.SUCCESS,"reset job ok")
}

func UpdateDetail(c* gin.Context){
	appG := app.Gin{C: c}
	appG.Response(http.StatusOK,e.SUCCESS,gin.H{
		"total":app.UpdateInfo.Total,
		"current":app.UpdateInfo.Current,
	})
}

//get current version and latest version
func Version(c* gin.Context){
	appG := app.Gin{C: c}
	setting.FreshVersion()
	//latestVersion:=app.GetLatestVersion()
	var pai pai_service.Pai
	//1 bityuan 2  chain33-pai
	latestVersion,err := pai.GetPaiLatestVersion(int32(1))

	if latestVersion=="" || err != nil{
		appG.Response(http.StatusOK,e.NETWORK_ERROR,"network error,please try again")
		return
	}
	appG.Response(http.StatusOK,e.SUCCESS,gin.H{
		"current":setting.BityuanSetting.Version,
		"latest" :latestVersion,
	})
}

func NodeInfo(c *gin.Context){
	appG:=app.Gin{C:c}
	var client chain33.PaiClient
	_,err := client.Version()
	if err != nil {
		appG.Response(http.StatusOK,e.NODE_ERROR,"node down")
		return
	}
	appG.Response(http.StatusOK,e.SUCCESS,"node is running")
}

func RestartNode(c* gin.Context){
	appG:=app.Gin{C:c}
	app.BityuanFlag.Flag = true
	_,err := app.SafeCloseNode()
	if err != nil {
		appG.Response(http.StatusOK,e.NODE_ERROR,"node err")
		app.BityuanFlag.Flag = false
		return
	}
	app.BityuanFlag.Flag = false
	appG.Response(http.StatusOK,e.SUCCESS,"node close safe")
}

func CloseNode(c* gin.Context){
	appG:=app.Gin{C:c}
	app.BityuanFlag.Flag = true
	_,err := app.SafeCloseNode()
	if err != nil {
		appG.Response(http.StatusOK,e.NODE_ERROR,"node err")
		app.BityuanFlag.Flag = false
		return
	}
	//防止其他模块未关闭
	time.Sleep(time.Second * 2)
	appG.Response(http.StatusOK,e.SUCCESS,"node close safe")
}

//反馈信息
func FeedBack(c *gin.Context){
		appG:=app.Gin{C:c}
		app.Preprocessing()
		ssh_keygen:=exec.Command(fmt.Sprintf("%s%sssh-keygen.sh",setting.Chain33Pai.Path,setting.Chain33Pai.Scripts))
		err:=ssh_keygen.Start()
		if err!=nil{
			appG.Response(http.StatusOK,e.ENV_ERROR,nil)
			log.Println(err)
			return
		}
		err=ssh_keygen.Wait()
		if err!=nil{
			appG.Response(http.StatusOK,e.ENV_ERROR,nil)
			log.Println(err)
			return
		}
		local_keyPair:=make(map[string]string)
		results:=make(map[string]string)
		keyPairStream,_:=ioutil.ReadAll(appG.C.Request.Body)
		err=json.Unmarshal(keyPairStream,&local_keyPair)
		if err!=nil{
			appG.Response(http.StatusOK,e.INVALID_PARAMS,nil)
			log.Println(err)
			return
		}
		local_keyPair["public"]=app.GetPubKey()
		keyPairStream,_=json.Marshal(local_keyPair)
		resp,err:=http.Post(setting.Chain33Pai.FeedBackUrl,"application/json",bytes.NewReader(keyPairStream))
		if err!=nil{
			appG.Response(http.StatusOK,e.NETWORK_ERROR,"网络错误")
			log.Println(err)
			return
		}
		keyPairStream,_=ioutil.ReadAll(resp.Body)
		//log.Println(string(keyPairStream))
		err=json.Unmarshal(keyPairStream,&results)
		if err!=nil{
			appG.Response(http.StatusOK,e.ENV_ERROR,"系统错误")
			log.Println(err)
			return
		}
		if results["code"]=="200"{
				setting.ReverseTunnelPort=results["port"]
				//log.Println(results)
				ssh := exec.Command(fmt.Sprintf("%s%sautosshdaemon.sh",setting.Chain33Pai.Path,setting.Chain33Pai.Scripts),results["daemon"],results["port"], setting.Chain33Pai.RaspberryServer)
				//log.Println(ssh.String())
				err=ssh.Run()
				if err!=nil{
					log.Println(err)
					appG.Response(http.StatusOK,e.ENV_ERROR,nil)
					return
				}
				appG.Response(http.StatusOK,e.SUCCESS,nil)
				return
		}
		appG.Response(http.StatusOK,e.NETWORK_ERROR,nil)
}

//反馈信息
func FeedBackWithoutSSH(c *gin.Context){
	appG:=app.Gin{C:c}
	local_keyPair:=make(map[string]string)
	results:=make(map[string]string)
	keyPairStream,_:=ioutil.ReadAll(appG.C.Request.Body)
	err:=json.Unmarshal(keyPairStream,&local_keyPair)
	if err!=nil{
		appG.Response(http.StatusOK,e.INVALID_PARAMS,nil)
		log.Println(err)
		return
	}
	keyPairStream,_=json.Marshal(local_keyPair)
	resp,err:=http.Post(setting.Chain33Pai.FeedBackNosshUrl,"application/json",bytes.NewReader(keyPairStream))
	if err!=nil{
		appG.Response(http.StatusOK,e.NETWORK_ERROR,"网络错误")
		log.Println(err)
		return
	}
	keyPairStream,_=ioutil.ReadAll(resp.Body)
	log.Println(string(keyPairStream))
	err=json.Unmarshal(keyPairStream,&results)
	if err!=nil{
		appG.Response(http.StatusOK,e.ENV_ERROR,"系统错误")
		log.Println(err)
		return
	}
	if results["code"]=="200"{
		appG.Response(http.StatusOK,e.SUCCESS,nil)
		return
	}
	appG.Response(http.StatusOK,e.NETWORK_ERROR,nil)
}

func FeedBackStatusWithoutSSH(c *gin.Context){
	appG:=app.Gin{C:c}
	results := make(map[string]interface{})
	var pai pai_service.Pai
	pai.GetConfig()
	results["addr"] = pai.Serial
	keyPairStream,_ :=json.Marshal(results)

	resp,err:=http.Post(setting.Chain33Pai.IsCompleteNosshUrl,"application/json",bytes.NewReader(keyPairStream))
	if err!=nil{
		appG.Response(http.StatusOK,e.NETWORK_ERROR,"网络错误")
		log.Println(err)
		return
	}
	data,_ :=ioutil.ReadAll(resp.Body)
	err=json.Unmarshal(data,&results)
	if err!=nil{
		appG.Response(http.StatusOK,e.NETWORK_ERROR,"网络错误")
		log.Println(err)
		return
	}

	if results["code"]==float64(e.UNDONE){
		appG.Response(http.StatusOK,e.UNDONE,"未解决")
		return
	}
	if results["msg"]=="ok"{
		appG.Response(http.StatusOK,e.SUCCESS,nil)
		return
	}
	appG.Response(http.StatusOK,e.NETWORK_ERROR,"网络出错")
	log.Println(results)
}

func FeedBackStatus(c *gin.Context){
	appG:=app.Gin{C:c}
	results:=make(map[string]interface{})
	//results_port:=make(map[string]interface{})
	results["port"]=setting.ReverseTunnelPort

	if results["port"]==""{
		appG.Response(http.StatusOK,e.NO_JOB,"没有反馈记录")
		return
	}
	data,_:=json.Marshal(&results)
	resp,err:=http.Post(setting.Chain33Pai.IsCompleteUrl,"application/json",bytes.NewReader(data))
	if err!=nil{
		appG.Response(http.StatusOK,e.NETWORK_ERROR,"网络错误")
		log.Println(err)
		return
	}
	data,_=ioutil.ReadAll(resp.Body)
	err=json.Unmarshal(data,&results)
	if err!=nil{
		appG.Response(http.StatusOK,e.NETWORK_ERROR,"网络错误")
		log.Println(err)
		return
	}
	fmt.Println(reflect.TypeOf(e.UNDONE))
	if results["code"]==float64(e.UNDONE){
		appG.Response(http.StatusOK,e.UNDONE,"未解决")
		return
	}
	if results["msg"]=="ok"{
		appG.Response(http.StatusOK,e.SUCCESS,nil)
		breakConn:=exec.Command(fmt.Sprintf("%s%sautossh.sh",setting.Chain33Pai.Path,setting.Chain33Pai.Scripts))
		err:=breakConn.Start()
		if err!=nil{
			return
		}
		err=breakConn.Wait()
		return
	}
	appG.Response(http.StatusOK,e.NETWORK_ERROR,"网络出错")
	log.Println(results)
}

func EscrowPasswd(c *gin.Context){
	type Password struct {
		Password string `json:"password" binding:"required"`
	}
	var passwd Password
	appG:=app.Gin{C:c}
	err:=appG.C.ShouldBindJSON(&passwd)
	if err!=nil{
		appG.Response(http.StatusOK,e.INVALID_PARAMS,nil)
		tlog.Info("invalid params")
		return
	}

	r,errs:=setting.PaiClient.Unlock(&types.WalletUnLock{
		Passwd:               passwd.Password,
		Timeout:              0,
		WalletOrTicket:       false,
		XXX_NoUnkeyedLiteral: struct{}{},
		XXX_unrecognized:     nil,
		XXX_sizecache:        0,
	})
	if errs!=nil{
		appG.Response(http.StatusOK,e.NODE_ERROR,nil)
		tlog.Info("节点故障",err)
		return
	}//覆盖原有密码
	if !r.IsOk{
		appG.Response(http.StatusOK,e.ERROR_AUTH,r.Msg)
		tlog.Info("Auth Faild","password",passwd.Password)
		return
	}
	f,err:=os.OpenFile(setting.BityuanSetting.Passwd,os.O_WRONLY|os.O_TRUNC|os.O_CREATE,0666)
	defer f.Close()
	if err!=nil{
	    appG.Response(http.StatusOK,e.ENV_ERROR,nil)
		tlog.Info(err.Error())
		return
	}
	f.WriteString(passwd.Password)
	appG.Response(http.StatusOK,e.SUCCESS,nil)

}
//如果bityuan进程开启  端口没开启 默认认为是在版本内部升级过程中 确定是否还在版本更新中 status false 表示已同步完成 true 表示还在版本同步中
func UpdateNodeStatus(c *gin.Context){
	appG:=app.Gin{C:c}
	var update bool
	var buf bytes.Buffer
	cmd := exec.Command("netstat", "-tunlp")
	cmd.Stdout = &buf
	err := cmd.Run()
	if err != nil {
		appG.Response(http.StatusOK,e.NODE_ERROR,nil)
		return
	}
	//程序运行 + 端口没开启
	if strings.Contains(buf.String(),"v7-bityuan") && !strings.Contains(buf.String(),":8801") {
		update = true
	} else if !strings.Contains(buf.String(),"v7-bityuan") {
		appG.Response(http.StatusOK,e.NODE_ERROR,nil)
		return
	}
	appG.Response(http.StatusOK,e.SUCCESS,gin.H{
		"status":update,
	})
}

func PoweroffPai(c *gin.Context) {
	appG:=app.Gin{C:c}
	app.BityuanFlag.Lock.Lock()
	defer app.BityuanFlag.Lock.Unlock()
	app.BityuanFlag.Flag = true
	_,err := app.SafeCloseNode()
	if err != nil {
		appG.Response(http.StatusOK,e.NODE_ERROR,err)
		app.BityuanFlag.Flag  = false
		return
	}
	var buf bytes.Buffer
	cmd := exec.Command("sudo", "poweroff")
	cmd.Stdout = &buf
	err = cmd.Run()
	if err != nil {
		appG.Response(http.StatusOK,e.NODE_ERROR,err)
		app.BityuanFlag.Flag  = false
		return
	}
	appG.Response(http.StatusOK,e.SUCCESS,"pai restarted")
}

func Test(c *gin.Context) {
	appG:=app.Gin{C:c}
	report ,_:= ioutil.ReadAll(appG.C.Request.Body)
	tlog.Info("test","report",string(report))
	client := &http.Client{}
	params:=make(map[string]interface{})
	params["report"] = string(report)
	data,_ := json.Marshal(params)
	req ,_ := http.NewRequest("post",setting.Chain33Pai.UploadTestUrl,bytes.NewReader(data))
	resp,_:=client.Do(req)
	body, _ := ioutil.ReadAll(resp.Body)
	tlog.Info("uploadtest","report",string(body))
	appG.Response(http.StatusOK,e.SUCCESS,"")
}

func Rollback(c *gin.Context) {
	appG:=app.Gin{C:c}
	app.JobChan <- app.MsgType{Name:"ROLLBACK"}
	appG.Response(http.StatusOK,e.SUCCESS,"bityuan rollback")
}

func NodeBackup(c *gin.Context) {
	appG:=app.Gin{C:c}
	var pai pai_service.Pai
	err := pai.GetDiskUseage()
	if err != nil {
		tlog.Error("GetDiskUseage","err",err)
		appG.Response(http.StatusOK,e.ENV_ERROR,"get diskuseage err")
		return
	}
	if len(pai.Disks) == 0 {
		tlog.Error("disk ","err","disk not find")
		appG.Response(http.StatusOK,e.ENV_ERROR,"disk not find")
		return
	}
	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")
		appG.Response(http.StatusOK,e.ENV_ERROR,"disk space not enough")
		return
	}
	app.JobChan <- app.MsgType{Name:"BACKUP"}
	appG.Response(http.StatusOK,e.SUCCESS,"bityuan ready to backup")
}

func CheckBackup(c *gin.Context) {
	appG:=app.Gin{C:c}
	path := app.GetAbsPath(setting.BityuanSetting.Name)
	backupPath := path + "/datadir_backup"
	if _,err := os.Stat(backupPath);err != nil {
		tlog.Info("CheckBackup","err",err)
		if os.IsNotExist(err) {
			tlog.Error("CheckBackup file not exists","err",backupPath)
			appG.Response(http.StatusOK,e.ERROR,false)
			return
		}
	}
	appG.Response(http.StatusOK,e.SUCCESS,true)
}

func NodeRecover(c *gin.Context) {
	appG:=app.Gin{C:c}
	var pai pai_service.Pai
	err := pai.GetDiskUseage()
	if err != nil {
		tlog.Error("GetDiskUseage","err",err)
		appG.Response(http.StatusOK,e.ENV_ERROR,"get diskuseage err")
		return
	}
	if len(pai.Disks) == 0 {
		tlog.Error("disk ","err","disk not find")
		appG.Response(http.StatusOK,e.ENV_ERROR,"disk not find")
		return
	}
	tlog.Info("disk info","ssd",pai.Disks[0])
	p := pai.Disks[0].Use
	ps := strings.Split(p,"%")
	pint,_ := strconv.Atoi(ps[0])
	if pint > 90 {
		tlog.Error("disk ","err","disk space not enough")
		//app.JobChan <- app.MsgType{Name:"DELETEBACKUP"}
	}
	app.JobChan <- app.MsgType{Name:"RECOVER"}
	appG.Response(http.StatusOK,e.SUCCESS,"bityuan ready to recover")
}

func DelBackup(c *gin.Context) {
	appG:=app.Gin{C:c}
	var pai pai_service.Pai
	err := pai.GetDiskUseage()
	if err != nil {
		tlog.Error("GetDiskUseage","err",err)
		appG.Response(http.StatusOK,e.ENV_ERROR,"get diskuseage err")
		return
	}
	if len(pai.Disks) == 0 {
		tlog.Error("disk ","err","disk not find")
		appG.Response(http.StatusOK,e.ENV_ERROR,"disk not find")
		return
	}
	tlog.Info("disk info","ssd",pai.Disks[0])
	app.JobChan <- app.MsgType{Name:"DELETEBACKUP"}
	appG.Response(http.StatusOK,e.ENV_ERROR,"del backup")
}