package pai_service

import (
	"bufio"
	"bytes"
	"chain33-pai/pkg/chain33"
	"chain33-pai/pkg/cmd"
	"chain33-pai/pkg/pai"
	"chain33-pai/pkg/setting"
	"encoding/json"
	"errors"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"strconv"
	"strings"

	log "github.com/33cn/chain33/common/log/log15"
	"github.com/33cn/chain33/types"
)

var (
	pai_serial string
	tlog       = log.New("service", "pai_Service")
)

//在网络通畅的情况下 ENum 每30s +1 20=>restart 40 => rollback 60 =>recover

type Pai struct {
	Hardware string `json:"hardware"`
	Revision string `json:"revision"`
	Serial   string `json:"serial"`
	//PeerList *types.PeerList
	LocalLastHeight  int64               `json:"local_last_height"`
	LastHeight       int64               `json:"last_height"`
	IsNtpSync        bool                `json:"is_ntp_sync"`
	WalletStatus     *types.WalletStatus `json:"wallet_status"`
	NetInfo          *types.NodeNetInfo  `json:"net_info"`
	BtyVersion       *types.VersionInfo  `json:"bty_version"`
	Err              string              `json:"err"`
	Arch             string              `json:"arch"`
	Disks            []*pai.Disk
	IsBackup         bool  `json:"is_backup"`
	ENum             int64 `json:"e_num"`
	LocalLastHeight2 int64 `json:"local_last_height_2"`
	ProLevel         int64 `json:"pro_level"`
	Temp             int64 `json:"temp"`
	PeerLen          int64 `json:"peer_len"`
}

type ReqUpdatePai struct {
	Version string `json:"version" binding:"required"`
}

func (p *Pai) SetPai() error {
	p.Err = ""
	p.GetConfig()
	p.GetDevstatus()
	p.GetBtyVersion()
	p.SetPaiEnv()
	p.GetDiskUseage()
	p.CheckBackup()
	p.GetTemp()
	return nil
}

func (p *Pai) SetPaiEnv() error {
	var buf bytes.Buffer
	cmd := exec.Command("arch")
	cmd.Stdout = &buf
	cmd.Run()
	arch := buf.String()
	p.Arch = strings.Replace(arch, "\n", "", -1)
	return nil
}

func (p *Pai) GetBtyVersion() error {
	client := &chain33.PaiClient{}
	v, err := client.Version()
	if err != nil {
		p.Err = err.Error()
		return err
	}
	p.BtyVersion = v
	return nil
}

func (p *Pai) GetConfig() bool {
	if pai_serial != "" {
		p.Serial = pai_serial
	}
	if p.Serial != "" {
		return true
	}
	if pai.GetArchType() == "arm" {
		config, err := getPaiConfig("cat", "/proc/cpuinfo")
		if err != nil {
			p.Err = err.Error()
			return false
		}
		if _, ok := config["serial"]; ok {
			p.Serial = config["serial"]
			p.Hardware = config["hardware"]
			p.Revision = config["revision"]
			return true
		}
	} else if pai.GetArchType() == "x86_64" {
		uuid := cmd.GetUUID()
		if uuid != "" {
			p.Serial = uuid
			return true
		}
	}

	return false
}

func (p *Pai) GetDevstatus() error {
	client := &chain33.PaiClient{}
	peerinfo, err := client.GetPeerInfo()
	if err != nil {
		tlog.Error("GetDevstatus peerinfo ", "err", err)
		p.Err = err.Error()
		return err
	}
	p.PeerLen = int64(len(peerinfo.Peers))
	for _, v := range peerinfo.Peers {
		if p.LastHeight < v.Header.Height {
			p.LastHeight = v.Header.Height
		}
		if v.Self {
			p.LocalLastHeight = v.Header.Height
		}
	}
	sync, err := client.IsNtpClockSync()
	if err != nil {
		p.Err = err.Error()
		tlog.Error("GetDevstatus IsNtpClockSync", " err", err)
		return err
	}
	p.IsNtpSync = sync.IsOk
	netinfo, err := client.GetNetInfo()
	if err != nil {
		p.Err = err.Error()
		tlog.Error("GetDevstatus GetNetInfo", " err", err)
		return err
	}
	p.NetInfo = netinfo
	walletstatus, err := client.GetWalletStatus()
	if err != nil {
		p.Err = err.Error()
		tlog.Error("GetDevstatus GetWalletStatus ", "err", err)
		return err
	}
	p.WalletStatus = walletstatus
	return nil
}

func getPaiConfig(command string, arg ...string) (config map[string]string, err error) {
	//获取操作系统版本信息

	list := make(map[string]string)
	cmd := exec.Command("cat", arg...)
	stdout, err := cmd.StdoutPipe()
	//tlog.Info(stdout)
	if err != nil {
		tlog.Info("getPaiConfig", err)
		return nil, err
	}
	defer stdout.Close()
	if err := cmd.Start(); err != nil {
		tlog.Info("getPaiConfig", err)
		return nil, err
	}
	rd := bufio.NewReader(stdout)
	for {
		line, err := rd.ReadString('\n')
		if err != nil || io.EOF == err {
			break
		} else {
			//tlog.Info("line", line)
			l := strings.Split(line, ":")

			if len(l) == 2 {
				k := strings.ToLower(strings.TrimRight(strings.Trim(strings.Replace(l[0], "	", " ", -1), " "), " "))
				v := strings.Trim(strings.Replace(l[1], "\n", "", -1), " ")
				//tlog.Info("KEY:",k, "VAL:", v)
				if _, ok := list[k]; !ok {
					list[k] = v
					if k == "serial" {
						pai_serial = v
					}
				}
			}
		}
	}

	return list, nil
}

func (p *Pai) GetPaiLatestVersion(t int32) (string, error) {
	data := make(map[string]interface{})
	data["type"] = t
	bytesData, _ := json.Marshal(data)
	resp, err := http.Post(setting.Chain33Pai.VersionUrl, "application/json", bytes.NewReader(bytesData))
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	type res struct {
		Code int64
		Msg  string
		Data string
	}
	var r res
	err = json.Unmarshal(body, &r)
	if err != nil {
		return "", err
	}
	return r.Data, nil
}

func (p *Pai) GetPaiVersionTest(t int32) (string, error) {
	data := make(map[string]interface{})
	data["type"] = t
	bytesData, _ := json.Marshal(data)
	resp, err := http.Post(setting.Chain33Pai.VersionTestUrl, "application/json", bytes.NewReader(bytesData))
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	type res struct {
		Code int64
		Msg  string
		Data string
	}
	var r res
	err = json.Unmarshal(body, &r)
	if err != nil {
		return "", err
	}
	return r.Data, nil
}

func (p *Pai) GetDiskUseage() error {
	disks, err := pai.MonitorServer()
	if err != nil {
		return err
	}
	if len(disks) > 0 {
		p.Disks = disks
	}
	return nil
}

func (p *Pai) CheckBackup() error {
	path := pai.FindAbsPath("wallet-bty")
	if path == "" {
		tlog.Error("IsBackup", "path", "node not exists")
		//p.IsBackup = false
		return errors.New("node not exists")
	}
	backupPath := path + "/datadir_backup"
	if _, err := os.Stat(backupPath); err != nil {
		if os.IsNotExist(err) {
			tlog.Error("backup file not exists", "err", backupPath)
			p.IsBackup = false
			return errors.New("backup file not exists")
		}
	}
	p.IsBackup = true
	return nil
}

func (p *Pai) GetTemp() error {
	var b bytes.Buffer
	cmd := exec.Command("cat", "/sys/class/thermal/thermal_zone0/temp")
	cmd.Stdout = &b
	err := cmd.Run()
	if err != nil {
		return err
	}
	list := strings.Split(b.String(), "\n")
	if list[0] != "" {
		temp, _ := strconv.Atoi(list[0])
		p.Temp = int64(temp)
	}
	return nil
}
