package controller

/*
#cgo CFLAGS: -fvisibility=hidden
#cgo LDFLAGS:-lpthread -ldl -lstdc++ -lrt -lm -lc -lz -luv -lcjson -luvdbus -lcurl -lcrypto -ldbus-1 -lsqlite3 -ls2json -lquickmail -luuid -lcutils -lconfig
#include <stdio.h>
#include <stdlib.h>
#include <uvdbus/skins.h>
#include <uvdbus/libuv_dbus.h>
#include <uvdbus/smart_sound.h>
#include <uvdbus/ota.h>
#include <uvdbus/log.h>
*/
import "C"
import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	gohttp "net/http"
	"netease_control_center/globals"
	"netease_control_center/interfaces"
	"netease_control_center/interfaces/eventPoint"
	"netease_control_center/interfaces/http"
	"netease_control_center/interfaces/log"
	"netease_control_center/interfaces/schedule"
	"netease_control_center/modules/storage"
	"netease_control_center/nembd"
	"netease_control_center/utils"
	"os"
	"strconv"
	"time"
	"math/rand"
)

var (
	lastCheckNewVersion       string
	lastCheckNewVersionStruct globals.VersionInfo
)

type DbusOtaFileInfo struct {
	Url  []byte `json:"url"`
	Md5  string `json:"md5"`
	Size uint32 `json:"size"`
}

type DbusOtaCmd struct {
	Version     uint32          `json:"version"`
	OtaCmd      uint32          `json:"otaCmd"`
	OtaMode     uint32          `json:"otaMode"`
	OtaFileInfo DbusOtaFileInfo `json:"otaFileInfo"`
}

func otaEveryNightCheck() {
	curNightOtaTimes := GetNightOtaTimes() + 1
	log.Println(log.DebugLog, "OTA TIME begin, begin to check ota in midnight! ota night times:", curNightOtaTimes)
	SetNightOtaTimes(curNightOtaTimes)

	time.Sleep(time.Duration(rand.Intn(3600)) * time.Second)

	for {
		if time.Now().Local().Hour() >= globals.OTA_END_HOUR || time.Now().Local().Hour() < globals.OTA_BEGIN_HOUR {
			log.Println(log.DebugLog, "Cur not in right time,exit!")
			return
		}

		//check if there is a new version
		if isnew, err := OTACheckNewVersion(false, false); err != nil {
			log.Println(log.DebugLog, "Check new version err", err)
			time.Sleep(time.Minute)
			continue
		} else {
			if isnew == false {
				log.Println(log.DebugLog, "Has no new version exit")
				return
			} else {
				break
			}
		}
	}

	//clear app force update flag
	SetOtaSource(globals.OtaSource_NightSilent)

	for {
		if time.Now().Local().Hour() >= globals.OTA_END_HOUR || time.Now().Local().Hour() < globals.OTA_BEGIN_HOUR {
			log.Println(log.DebugLog, "Time out exit")
			return
		} else if globals.IdleTime() >= globals.OTA_IDLE_TIME || curNightOtaTimes >= globals.OTA_NIGHT_FORCE_TIMES {
			//do upgrade
			log.Println(log.DebugLog, "Idle time, do upgrade", globals.OTAProgress)
			if GetOTAStatus() == globals.OTAStatus_downloaded {
				log.Println(log.DebugLog, "begin to do upgrade")
				DoOTAUpgrade()
				return
			} else {
				log.Println(log.DebugLog, "Wait ota downloaded!")
				time.Sleep(5 * time.Second)
			}
		} else {
			log.Println(log.DebugLog, "System busy, 5sec after check if upgrade! Last Active times:", globals.LastActiveTriggerTime, " ota night times:", curNightOtaTimes)
			time.Sleep(20 * time.Second)
		}
	}

	//set app push false
	//var isAppPush bool = false
	//storage.Config_set(globals.MODULE_CONTROLLER, SQL_KEY_BINDUSER, &isAppPush)
}

func DoOtaTest(md5 string, url string, size string) {
	fmt.Println("Begin to do ota Md5:", md5, " url:", url, " size:", size)
	var versioninfo DbusOtaCmd
	versioninfo.Version = 123
	versioninfo.OtaFileInfo.Url = []byte(url)
	versioninfo.OtaFileInfo.Md5 = md5
	ss, _ := strconv.Atoi(size)
	versioninfo.OtaFileInfo.Size = uint32(ss)
	versioninfo.OtaMode = uint32(C.OTA_MODE_FORCE_NOW)
	versioninfo.OtaCmd = uint32(C.OTA_CMD_DOWNLOAD)
	SetOtaSource(globals.OtaSource_APP)
	log.Println(log.DebugLog, "Will upgrade ")
	if data, dataerr := json.Marshal(versioninfo); dataerr == nil {
		log.Println(log.DebugLog, "tell ota upgrade, ", string(data))
		SendOtaNotify(data)
	}
}

func SendOtaStatusByYunxin(status globals.OTAStatus, safe bool, replyid string) {
	var tmp struct {
		OtaStatus globals.OTAStatus `json:"ota"`
		Version   string            `json:"version"`
		Hardware  string            `json:"hardware"`
	}
	tmp.OtaStatus = status

	if status == globals.OTAStatus_normal || len(lastCheckNewVersionStruct.Version) == 0 {
		tmp.Version = *globals.GitTag
	} else {
		tmp.Version = lastCheckNewVersionStruct.Version
	}
	tmp.Hardware = *globals.Hv

	SendCmdByYunxin(globals.APP_CMD_OTASTATUS, tmp, safe, replyid)
}

func SendOtaSuccessByYunxin(safe bool, replyid string) {
	var tmp struct {
		OtaStatus globals.OTAStatus `json:"ota"`
		Version   string            `json:"version"`
		Hardware  string            `json:"hardware"`
	}

	tmp.Version = *globals.GitTag
	tmp.Hardware = *globals.Hv
	tmp.OtaStatus = globals.OTAStatus_success
	SendCmdByYunxin(globals.APP_CMD_OTASTATUS, tmp, safe, replyid)
}

func OTACheckNewVersion(ForceUpgrade bool, IsAppForce bool) (bool, error) {
	log.Println(log.DebugLog, "OTACheckNewVersion, force:", ForceUpgrade, " IsAppForce:", IsAppForce)
	//debug.PrintStack()
	if globals.IsInBackupSystem() {
		//do nothing
		log.Println(log.DebugLog, "Inbackup system. return.")
		return false, nil
	}

	if ForceUpgrade == false {
		switch GetOTAStatus() {
		case globals.OTAStatus_downloading:
			log.Println(log.DebugLog, "Ota now downloading firmware, just check ota status CMD_MISC_QUERY_DL_STATUS!")
			if IsAppForce == true {
				SetOtaSource(globals.OtaSource_APP)
			}
			interfaces.JustSendBusCmd(globals.MODULE_CONTROLLER, globals.MODULE_OTA, uint32(C.CMD_MISC_QUERY_DL_STATUS))
			return true, errors.New("ota is now downloading!")

		case globals.OTAStatus_downloaded_fail:
			log.Println(log.DebugLog, "Download fail, should redownload again:", lastCheckNewVersion)
			if IsAppForce == true {
				SetOtaSource(globals.OtaSource_APP)
			}

			if IsAppForce == true {
				log.Println(log.DebugLog, "App force download")
			} else {
				return true, errors.New("ota download fail")
			}

		case globals.OTAStatus_downloaded:
			if IsAppForce == true {
				SetOtaSource(globals.OtaSource_APP)
			}

			if ForceUpgrade == true || IsAppForce == true {
				log.Println(log.DebugLog, "Ota has downloaded the firmware, do upgrade!")
				DoOTAUpgrade()
			} else {
				log.Println(log.DebugLog, "Ota has downloaded the firmware, let ota confirm the files and clear ota flag!")
				SetOTAStatus(globals.OTAStatus_downloading)
				SendOtaNotify([]byte(lastCheckNewVersion))
			}
			return true, nil
		}
	}

	var req http.HttpReqNewestVersion
	var res http.HttpRspNewestVersion
	req.Version = *globals.GitTag
	if retcode, _, reterr := http.HttpPostAuto(req, &res); retcode != 1000 || reterr != nil {
		log.Println(log.ErrorLog, "Check new version error", retcode, reterr)
		return false, fmt.Errorf("Check new version erro %v %v", retcode, reterr)
	} else {
		if res.Upgrade == false {
			log.Println(log.DebugLog, "Current the newest version")
			return false, nil
		} else {
			log.Println(log.DebugLog, "Begin to upgrade")
			DoBackupFileToLocal()
			if err := DoBackupFiles2Server(globals.BackupFile_Local); err != nil {
				log.Println(log.ErrorLog, "backup files to server error:", err)
				//return false, fmt.Errorf("backup files error %v\n", err)
			}
			//do ota
			var versionInfo DbusOtaCmd
			versionInfo.Version = utils.VersionToUint32(res.VersionInfo.Version)
			versionInfo.OtaFileInfo.Url = []byte(res.VersionInfo.DownloadUrl)
			versionInfo.OtaFileInfo.Md5 = res.VersionInfo.Md5
			versionInfo.OtaFileInfo.Size = res.VersionInfo.Size

			if res.VersionInfo.IsForce == 1 || IsAppForce == true || ForceUpgrade == true {
				versionInfo.OtaMode = uint32(C.OTA_MODE_FORCE_NOW)
				if IsAppForce == false {
					SetOtaSource(globals.OtaSource_Force)
				}
			} else {
				versionInfo.OtaMode = uint32(C.OTA_MODE_NORMAL)
			}
			versionInfo.OtaCmd = uint32(C.OTA_CMD_DOWNLOAD)
			SetVersionInfo(res.VersionInfo)

			log.Println(log.DebugLog, "Will upgrade ", res)
			if data, dataerr := json.Marshal(versionInfo); dataerr == nil {
				lastCheckNewVersion = string(data)
				lastCheckNewVersionStruct = res.VersionInfo
				log.Println(log.DebugLog, "Tell ota upgrade:", lastCheckNewVersion)
				SendOtaNotify(data)
			}

			if IsAppForce == true {
				SetOtaSource(globals.OtaSource_APP)
			}

			return true, nil
		}
	}
}

func IsOTAfreeTime() bool {
	if time.Now().Local().Hour() > globals.OTA_END_HOUR || time.Now().Local().Hour() < globals.OTA_BEGIN_HOUR {
		return false
	} else if globals.IdleTime() >= globals.OTA_IDLE_TIME {
		return true
	} else {
		return false
	}
}

func DoOTAUpgrade() {
	var cmd DbusOtaCmd
	cmd.OtaMode = uint32(C.OTA_MODE_NORMAL)
	cmd.OtaCmd = uint32(C.OTA_CMD_EXEC)
	cmd.OtaFileInfo.Url = []byte("1234")
	if data, err := json.Marshal(cmd); err == nil {
		SendOtaNotify(data)
	}
}

func SendOtaNotify(data []byte) {
	interfaces.SendCommand(globals.MODULE_CONTROLLER, globals.MODULE_OTA, uint32(C.CMD_OTA_NOTIFY), &data)
}

//Do backup files
func DoBackupFiles2Server(filepath string) error {
	_, _, _, chipid, _, _ := nembd.BoardInfo()
	if len(chipid) == 0 {
		log.Println(log.ErrorLog, "Can not get chipid return")
		return errors.New("get chip id error")
	}

	return http.PostBackupFile(chipid, filepath)
}

//Do backup file to local
func DoBackupFileToLocal() {
	defer func() {
		if err := recover(); err != nil {
			log.Println(log.ErrorLog, "Do backup file to local fail!", err)
		}
	}()
	tmpfile := "/tmp/ccbackupfile"

	defer func() {
		os.Remove(tmpfile)
		if err := recover(); err != nil {
			log.Println(log.ErrorLog, "Backup file error:", err)
		}
	}()
	os.Remove(globals.BackupFile_Local)
	if err := utils.CompressFilesToLocalFile(globals.BackupFiles, tmpfile); err == nil {
		utils.EncryptFile(tmpfile, globals.BackupFile_Local, []byte(globals.BackupFile_Pass))
		log.Println(log.DebugLog, "Backup file to local success!")
	}
}

//Do restore file
func DoRestoreFileToLocal() {
	defer func() {
		if err := recover(); err != nil {
			log.Println(log.ErrorLog, "Do restore file to local fail!", err)
		}
	}()

	tmpfile := "/tmp/ccrestorebackupfile"

	defer func() {
		os.Remove(tmpfile)
		if err := recover(); err != nil {
			log.Println(log.ErrorLog, "Restore file error:", err)
		}
	}()
	if _, err := os.Stat(tmpfile); os.IsNotExist(err) == false {
		os.Remove(tmpfile)
	}
	utils.DecryptFile(globals.BackupFile_Local, tmpfile, []byte(globals.BackupFile_Pass))
	if err := utils.UnCompressFileFromLocalFile(globals.BackupFiles, tmpfile); err != nil {
		fmt.Println("Restore files error!", err)
	} else {
		fmt.Println("Restore files successfully!")
	}
}

//Restore backuped files
func DoRestoreBackupFiles() error {
	_, _, _, chipid, _, _ := nembd.BoardInfo()
	if len(chipid) == 0 {
		log.Println(log.ErrorLog, "Can not get chipid return")
		return errors.New("get chip id error")
	}

	var req http.HttpReqGetBackupFile
	var rsp http.HttpRspGetBackupFile
	req.VboxMac = chipid

	code, errdesc, err := http.HttpPostAuto(req, &rsp)
	if err != nil {
		log.Println(log.ErrorLog, err)
		return err
	}

	if code != 1000 {
		log.Println(log.ErrorLog, errdesc)
		return errors.New(errdesc)
	}

	if len(rsp.BackupFileUrl) == 0 {
		err := errors.New("BackupFileurl err")
		log.Println(log.ErrorLog, err)
		return err
	}

	rsp2, err2 := gohttp.Get(rsp.BackupFileUrl)
	if err2 == nil {
		defer rsp2.Body.Close()
	}
	if err2 == nil && (rsp2.StatusCode >= gohttp.StatusOK && rsp2.StatusCode < gohttp.StatusMultipleChoices) {
		if data, err := ioutil.ReadAll(rsp2.Body); err == nil {
			utils.UnCompressBuffer(data, globals.BackupFiles)
			return nil
		} else {
			return err
		}
	} else {
		log.Println(log.ErrorLog, "http req fail", rsp, err)
		return errors.New("http req fail")
	}

	return nil
}

func DbusOtaCmdDeal(data []byte) {
	var result struct {
		Status int `json:"status"`
		Val    int `json:"val"`
	}

	if err := json.Unmarshal(data, &result); err != nil {
		log.Println(log.ErrorLog, "Decode dbus ota cmd error!")
		return
	}
	log.Printf(log.DebugLog, "Ota cmd recv: %+v\n", result)
	switch result.Status {
	case int(C.OTA_DOWNLOAD_PROGRESS):
		if GetOTAStatus() != globals.OTAStatus_downloading && result.Val < 100 {
			SendOtaStatusByYunxin(globals.OTAStatus_downloading, false, "")
			SetOTAStatus(globals.OTAStatus_downloading)
		}
		globals.OTAProgress = uint8(result.Val)

	case int(C.OTA_DOWNLOAD_FILE):
		if result.Val == 0 {
			log.Println(log.DebugLog, "Download file success!")
			SetOTAStatus(globals.OTAStatus_downloaded)
			SendOtaStatusByYunxin(globals.OTAStatus_downloaded, false, "")
			eventPoint.EpRecord("B315", map[string]interface{}{})
		} else if result.Val < 0 {
			log.Println(log.DebugLog, "Download file fail, redo!")
			SendOtaStatusByYunxin(globals.OTAStatus_downloaded_fail, false, "")
			SetOTAStatus(globals.OTAStatus_downloaded_fail)
			if globals.IsInBackupSystem() {
				log.Println(log.DebugLog, "Ota download fail, jump backupsystem again!")
				if len(lastCheckNewVersion) > 0 {
					log.Println(log.DebugLog, "Re download!")
					time.Sleep(5 * time.Second)
					SendOtaNotify([]byte(lastCheckNewVersion))
				} else {
					log.Println(log.DebugLog, "Rejump backup sys!")
					StartBoard(globals.BootStage_BACKUPSYSTEM, false)
				}
			} else {
				if len(lastCheckNewVersion) > 0 {
					log.Println(log.DebugLog, "OTA download fail, sleep and redownload!", lastCheckNewVersion)
					time.Sleep(5 * time.Second)
					SendOtaNotify([]byte(lastCheckNewVersion))
				} else {
					log.Println(log.DebugLog, "OTA download fail, but lastCheckNewVersion is null, maybe cc be restart, re check version.")
					SetOTAStatus(globals.OTAStatus_normal)
					OTACheckNewVersion(false, false)
				}
			}
		} else if result.Val == 1 {
			log.Println(log.DebugLog, "OTA is downloading! do noting! in callback!")
		} else if result.Val == 2 {
			eventPoint.EpRecord("B314", map[string]interface{}{})
		}

	case int(C.OTA_UPGRADE_START):
		log.Println(log.DebugLog, "Sent ota status begin")
		eventPoint.EpRecord("B312", map[string]interface{}{"version": lastCheckNewVersionStruct})
		SetOTAStatus(globals.OTAStatus_begin)
		SendOtaStatusByYunxin(globals.OTAStatus_begin, false, "")
		schedule.SendSCmd(schedule.SCMD_SuperTask_System_BeginOta)

	case int(C.OTA_SUCCESED):
		switch result.Val {
		case 1:
			//normal
			globals.CurBootMode = globals.BootMode_NORMAL
		case -1:
			//backup mode
			globals.CurBootMode = globals.BootMode_BACKUPSYSTEM
			//StartBoard(globals.BootStage_BACKUPSYSTEM, false)
		case 0:
			//ota
			globals.CurBootMode = globals.BootMode_OTA
		}

	case int(C.OTA_CURRENT_REBOOT_TIME):
		SetOtaTimes(result.Val)

	case int(C.OTA_ERR_CODE):
		if result.Val == -2011 {
			log.Println(log.DebugLog, "ota 2011 err, ignore!!")
			return
		}
		SetOTAStatus(globals.OTAStatus_fail)
		SendOtaStatusByYunxin(globals.OTAStatus_fail, false, "")
		log.Println(log.DebugLog, "OTA fail!")
		eventPoint.EpRecord("B313", map[string]interface{}{"success": false, "NSOSV": lastCheckNewVersionStruct.Version})

	case int(C.OTA_REBOOT_SYSTEM):
		ctx, ctxcancel := context.WithTimeout(context.Background(), time.Second*30)
		go func() {
			//do upload files
			log.Println(log.DebugLog, "Begin to upload files")
			C.SysPointMarkUpload()
			log.Println(log.DebugLog, "Upload files success!")
			ctxcancel()
		}()
		log.Println(log.DebugLog, "Begin to wait ota upload files finish!")
		select {
		case <-ctx.Done():
			log.Println(log.DebugLog, "Get done, begin to reboot!")
			C.SystemSafeReboot()
		}
	}
}

func CSystemSafeReboot() {
	C.SystemSafeReboot()
}

func CheckOTAStatusWhenBooting() globals.BootMode {
	log.Println(log.DebugLog, "Wait ota status begin!", globals.CurBootMode)
	for globals.CurBootMode == globals.BootMode_UNKNOW {
		interfaces.JustSendBusCmd(globals.MODULE_CONTROLLER, globals.MODULE_OTA, uint32(C.CMD_MISC_QUERY_OTA_STATUS))
		time.Sleep(10 * time.Millisecond)
	}
	defer func() {
		log.Println(log.DebugLog, "Wait ota status exit", globals.CurBootMode)
	}()

	return globals.CurBootMode
}

func InBackUpSystem(ctx context.Context) (isNoticeOTA bool) {
	globals.Uuid, globals.BindUser, globals.YxToken, globals.AesKey = GetSqlUUIDS()
	if err := CheckWifi(ctx); err != nil {
		globals.InternetConnect = false
		return false
	} else {
		globals.InternetConnect = true
	}
	var req http.HttpReqLastNormalVersion
	var res http.HttpRspLastNormalVersion
	sqlv := GetVersionInfo()
	if code, errmsg, err := http.HttpPostAuto(req, &res); err == nil && code == 1000 {
		log.Printf(log.DebugLog, "Server req new version successfully! %#v\n", res)
		if GetOtaTimes() <= 2 || len(res.LastVersionInfo.Md5) == 0 {
			TellOtaUpgrade(res.NewestVersionInfo, true)
		} else {
			TellOtaUpgrade(res.LastVersionInfo, true)
		}
		return true
	} else {
		log.Println(log.DebugLog, "OTA Check new version error", code, errmsg, err)
		if len(sqlv.DownloadUrl) > 0 {
			log.Println(log.DebugLog, "Upgrade sql version:", sqlv)
			TellOtaUpgrade(sqlv, true)
			return true
		} else {
			log.Println(log.DebugLog, "Sql has no version, i have nothing to do!")
		}

		return false
	}
}

func GetOTAStatus() globals.OTAStatus {
	if err := storage.Config_get(globals.MODULE_CONTROLLER, globals.SQL_KEY_OTASTATUS, &globals.CurOTAStatus); err != nil {
		log.Println(log.ErrorLog, "Get ota flag err:", err)
		return globals.OTAStatus_normal
	} else {
		return globals.CurOTAStatus
	}
}

func SetOTAStatus(status globals.OTAStatus) {
	if status != globals.CurOTAStatus {
		log.Println(log.DebugLog, "Set ota status:", status)
		globals.CurOTAStatus = status
		if err := storage.Config_set(globals.MODULE_CONTROLLER, globals.SQL_KEY_OTASTATUS, status); err != nil {
			log.Println(log.ErrorLog, "Set ota flag err:", err)
		}
	}
}

func SetOtaTimes(times int) {
	storage.Config_set(globals.MODULE_CONTROLLER, globals.SQL_KEY_OTATIMES, times)
}

func GetOtaTimes() int {
	var tmp int
	storage.Config_get(globals.MODULE_CONTROLLER, globals.SQL_KEY_OTATIMES, &tmp)
	return tmp
}

func SetNightOtaTimes(times int) {
	storage.Config_set(globals.MODULE_CONTROLLER, globals.SQL_KEY_OTA_NIGHT_TIMES, times)
}

func GetNightOtaTimes() int {
	var tmp int
	storage.Config_get(globals.MODULE_CONTROLLER, globals.SQL_KEY_OTA_NIGHT_TIMES, &tmp)
	return tmp
}

func SetVersionInfo(msg globals.VersionInfo) {
	storage.Config_set(globals.MODULE_CONTROLLER, globals.SQL_KEY_VERSIONMSG, msg)
}

func GetVersionInfo() globals.VersionInfo {
	var tmp globals.VersionInfo
	storage.Config_get(globals.MODULE_CONTROLLER, globals.SQL_KEY_VERSIONMSG, &tmp)
	return tmp
}

func SetOtaSource(source globals.OtaSource) {
	storage.Config_set(globals.MODULE_CONTROLLER, globals.SQL_KEY_APP_PUSH_OTA, source)
}

func GetOtaSource() (source globals.OtaSource) {
	if err := storage.Config_get(globals.MODULE_CONTROLLER, globals.SQL_KEY_APP_PUSH_OTA, &source); err != nil {
		source = globals.OtaSource_Unknow
	}

	return
}

func TellOTABooted() {
	data := []byte(`{"status" : 0}`)
	interfaces.SendCommand(globals.MODULE_CONTROLLER, globals.MODULE_OTA, uint32(C.CMD_SYSTEM_STANDBY), &data)
}

func TellOtaUpgrade(Ver globals.VersionInfo, isForce bool) {
	var versionInfo DbusOtaCmd
	versionInfo.Version = utils.VersionToUint32(Ver.Version)
	versionInfo.OtaFileInfo.Url = []byte(Ver.DownloadUrl)
	versionInfo.OtaFileInfo.Md5 = Ver.Md5
	versionInfo.OtaFileInfo.Size = Ver.Size
	if Ver.IsForce == 1 || isForce == true {
		versionInfo.OtaMode = uint32(C.OTA_MODE_FORCE_NOW)
	} else {
		versionInfo.OtaMode = uint32(C.OTA_MODE_NORMAL)
	}
	versionInfo.OtaCmd = uint32(C.OTA_CMD_DOWNLOAD)
	SetVersionInfo(Ver)
	lastCheckNewVersionStruct = Ver

	log.Println(log.DebugLog, "Will upgrade ", isForce, versionInfo)
	if data, dataerr := json.Marshal(versionInfo); dataerr == nil {
		log.Println(log.DebugLog, "tell ota upgrade")
		lastCheckNewVersion = string(data)
		SendOtaNotify(data)
	}
}
