package http

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/base64"
	"encoding/json"
	"errors"
	"io/ioutil"
	"net"
	"net/http"
	"netease_control_center/globals"
	"netease_control_center/interfaces/log"
	"netease_control_center/nembd"
	"netease_control_center/utils"
	"netease_control_center/utils/ntp"
	"os/exec"
	"reflect"
	"strconv"
	"time"
)

var (
	Reqbase HttpReqbase
	ApiUrls map[string]ApiContainer = make(map[string]ApiContainer)
)

func RegisteApi(ReqDataStruct interface{}, api ApiContainer) {
	ApiUrls[reflect.TypeOf(ReqDataStruct).Name()] = api
}

func HttpInit() {
	Reqbase.Bizid = "1"
	Reqbase.Os = "0"
	//_, _, Reqbase.DeviceId, _, _, _ = nembd.BoardInfo()
	Reqbase.DeviceId = globals.CPU_SERIAL
	Reqbase.ChipId = globals.CPU_ID
	Reqbase.Osv, _ = nembd.ExecOutput("uname", "-s")
	Reqbase.Apn = "wifi"
	Reqbase.V0 = globals.GitTag
	Reqbase.V1 = &globals.BuildDate
	//Reqbase.Uuid = "0-29E3775B7BDFA9EC"
	Reqbase.Uuid = &globals.Uuid
	if Reqbase.DeviceId == "" {
		Reqbase.DeviceId = "deviceid"
	}
	if Reqbase.ChipId == "" {
		Reqbase.ChipId = "chipId"
	}
	Reqbase.Hv = globals.Hv

	log.Printf(log.DebugLog, "Http reqbase:%+v\n", Reqbase)
	log.Println(log.DebugLog, "v0:", *Reqbase.V0, " hv:", *Reqbase.Hv)

	/*Init*/
	RegisteApi(HttpReqInit{}, ApiContainer{Url: "vbox/init/vboxinit", ReqparamIsString: false, IsEncrypted: false})
	RegisteApi(HttpReqFirstRun{}, ApiContainer{Url: "vbox/init/vboxinit.do", ReqparamIsString: false, IsEncrypted: false})
	RegisteApi(HttpReqRebindUser{}, ApiContainer{Url: "vbox/user/bind", ReqparamIsString: true, IsEncrypted: true})
	RegisteApi(HttpReqNewestVersion{}, ApiContainer{Url: "vbox/version/newest", ReqparamIsString: true, IsEncrypted: false})
	RegisteApi(HttpReqLastNormalVersion{}, ApiContainer{Url: "vbox/version/getlastenable", ReqparamIsString: true, IsEncrypted: false})
	RegisteApi(HttpReqGetVersionInfo{}, ApiContainer{Url: "vbox/version/getone", ReqparamIsString: true, IsEncrypted: false})
	RegisteApi(HttpReqGetBackupFile{}, ApiContainer{Url: "vbox/backupfile/get", ReqparamIsString: true, IsEncrypted: false})
	RegisteApi(HttpReqQueryBindUser{}, ApiContainer{Url: "vbox/common/qryVboxUser", ReqparamIsString: true, IsEncrypted: false})

	/*Alarms*/
	RegisteApi(HttpReqAddAlarm{}, ApiContainer{Url: "vbox/alarmclock/add", ReqparamIsString: true, IsEncrypted: false})
	RegisteApi(HttpReqMultiDelAlarms{}, ApiContainer{Url: "vbox/alarmclock/batchdelete", ReqparamIsString: true, IsEncrypted: false})
	RegisteApi(HttpReqGetRefleshedAlarmList{}, ApiContainer{Url: "vbox/alarmclock/refresh", ReqparamIsString: true, IsEncrypted: false})
	RegisteApi(HttpReqUpdateAlarmClocks{}, ApiContainer{Url: "vbox/alarmclock/batchupdate", ReqparamIsString: true, IsEncrypted: false})
	RegisteApi(HttpReqAlarmClockRing{}, ApiContainer{Url: "vbox/inform/push", ReqparamIsString: true, IsEncrypted: false})

	/*Remind*/
	RegisteApi(HttpReqRemindGetList{}, ApiContainer{Url: "vbox/remind/getlist", ReqparamIsString: true, IsEncrypted: false})
	RegisteApi(HttpReqRefreshReminds{}, ApiContainer{Url: "vbox/remind/refresh", ReqparamIsString: true, IsEncrypted: false})
	RegisteApi(HttpReqBatchUpdateReminds{}, ApiContainer{Url: "vbox/remind/batchupdate", ReqparamIsString: true, IsEncrypted: false})
	RegisteApi(HttpReqBatchDeleteReminds{}, ApiContainer{Url: "vbox/remind/batchdelete", ReqparamIsString: true, IsEncrypted: false})
	RegisteApi(HttpReqRemindRing{}, ApiContainer{Url: "vbox/inform/push", ReqparamIsString: true, IsEncrypted: false})

	/*Holidays*/
	RegisteApi(HttpReqGetHoliday{}, ApiContainer{Url: "vbox/holiday/getHoliday", ReqparamIsString: true, IsEncrypted: false})

	/*Time*/
	RegisteApi(HttpReqGetTimeStamp{}, ApiContainer{Url: "vbox/time/get", ReqparamIsString: true, IsEncrypted: false})

	/*Scene*/
	RegisteApi(HttpReqSceneById{}, ApiContainer{Url: "vbox/scene/getSceneById", ReqparamIsString: true, IsEncrypted: false})
	RegisteApi(HttpReqRefreshScene{}, ApiContainer{Url: "vbox/scene/refresh", ReqparamIsString: true, IsEncrypted: false})
	RegisteApi(HttpReqSceneConfirmTts{}, ApiContainer{Url: "vbox/vbox/scene/getSceneTts", ReqparamIsString: true, IsEncrypted: false})
	RegisteApi(HttpReqReportSceneProgress{}, ApiContainer{Url: "vbox/scene/changeTaskStatus", ReqparamIsString: true, IsEncrypted: false})

	/*hotfix*/
	RegisteApi(HttpReqHotfix{}, ApiContainer{Url: "vbox/version/resource/hotfix", ReqparamIsString: true, IsEncrypted: false})

	/* inform push msg/event */
	RegisteApi(HttpReqInform{}, ApiContainer{Url: "vbox/inform/push", ReqparamIsString: true, IsEncrypted: false})
	RegisteApi(HttpReqInform109{}, ApiContainer{Url: "vbox/inform/push", ReqparamIsString: true, IsEncrypted: false})

	/* iot */
	RegisteApi(HttpReqDevDiscovery{}, ApiContainer{Url: "vbox/broadLink/cloudIdentity", ReqparamIsString: true, IsEncrypted: false})
	RegisteApi(HttpReqDeviceSharedKey{}, ApiContainer{Url: "vbox/broadLink/deviceIdentity", ReqparamIsString: true, IsEncrypted: false})
	RegisteApi(HttpReqDeviceRegiste{}, ApiContainer{Url: "vbox/broadLink/deviceRegister", ReqparamIsString: true, IsEncrypted: false})

	RegisteApi(reqCfgList{}, ApiContainer{Url: "vbox/runtimeConfig/getImmediateChild", ReqparamIsString: true, IsEncrypted: false})
	RegisteApi(reqCfgList2{}, ApiContainer{Url: "vbox/runtimeConfig/getImmediateChild", ReqparamIsString: true, IsEncrypted: false})
}

func (this *HttpReq) SetReqBody(body interface{}) *HttpReq {
	this.Base = &Reqbase
	this.Body = body
	return this
}

func (this *HttpReq) Init() *HttpReq {
	this.Base = &Reqbase
	return this
}

func (this *HttpReq) ToJson() []byte {
	data, _ := json.Marshal(this)
	return data
}

func Url(api string) string {
	return ServerIp + api
}

var netTransport = &http.Transport{
	Proxy: http.ProxyFromEnvironment,
	DialContext: (&net.Dialer{
		Timeout:   30 * time.Second,
		KeepAlive: 30 * time.Second,
		DualStack: true,
	}).DialContext,
	MaxIdleConns:          100,
	IdleConnTimeout:       90 * time.Second,
	TLSHandshakeTimeout:   8 * time.Second,
	ExpectContinueTimeout: 3 * time.Second,
	DialTLS: func(network, addr string) (net.Conn, error) {
		dialer := &net.Dialer{
			Timeout:   8 * time.Second,
			KeepAlive: 30 * time.Second,
		}

		return tls.DialWithDialer(dialer, network, addr, nil)
	},
}
var netClient = &http.Client{
	Timeout:   8 * time.Second,
	Transport: netTransport,
}

func GetDefaultClient() *http.Client {
	return netClient
}

func (this *HttpReq) PostBase(url string, v interface{}, IsEncryped bool) (int, string, error) {
	defer utils.DumpPanicError()
	var hrsp HttpRspBody

	//http.DefaultClient.Timeout = 15 * time.Second
	//http.DefaultClient.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true},}

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(this.ToJson()))
	if err != nil {
		log.Println(log.ErrorLog, "Can not new post request", err)
		return -1, globals.Net_ERR_Post.Error(), globals.Net_ERR_Post
	}
	req.Header.Set("Content-Type", "application/json")
	req.Close = false

	rsp, err := netClient.Do(req)
	if err != nil {
		log.Println(log.ErrorLog, "Can not send post request", err)
		return -1, globals.Net_ERR_Post.Error(), globals.Net_ERR_Post
	}
	defer rsp.Body.Close()
	body, bodyError := ioutil.ReadAll(rsp.Body)
	if bodyError != nil {
		log.Println(log.ErrorLog, "Can not read req body", bodyError)
		return -1, globals.Net_ERR_Post.Error(), globals.Net_ERR_Post
	}

	log.Println(log.DebugLog, url, err, string(this.ToJson()), string(body))

	//maybe the rsp is string
	var tmp string
	hrsp.Detail = &tmp
	if err = json.Unmarshal(body, &hrsp); err == nil {
		if IsEncryped {
			if len(tmp) > 0 {
				if aesdata, dataerr := base64.StdEncoding.DecodeString(tmp); dataerr == nil {
					//log.Println(log.DebugLog, "recv after base64:", aesdata)
					if data, err := utils.AesDecryptBase64Key(aesdata, globals.AesKey); err == nil {
						log.Println(log.DebugLog, "http rev body:", string(data), ", err = ", err)
						err = json.Unmarshal(data, v)
					} else {
						log.Println(log.ErrorLog, "Can not dec data", err)
						return -1, "Can not dec data", err
					}
				} else {
					log.Println(log.ErrorLog, "can not base64 decode", dataerr)
					return -1, "can not base64 decode", dataerr
				}
			}
		} else {
			if len(tmp) > 0 {
				err = json.Unmarshal([]byte(tmp), v)
			}
		}
	} else {
		//may be a object
		hrsp.Detail = v
		err = json.Unmarshal(body, &hrsp)
	}

	//check time if right, by server return timestamp
	SetTimeToLocal(time.Unix(0, hrsp.ServerTime*1e6), 5*time.Minute)

	if err != nil {
		//服务器出错，返回了错误的页面，类似404等，统一处理播报：Server_ERR_RET 错误类型 即tts("网络有点抖动，暂时没法为你继续播放啦")
		hrsp.Code = -1
		hrsp.Desc = "can not find json object"
	}

	return hrsp.Code, hrsp.Desc, err
}

func SetTimeToLocal(toset time.Time, difftime time.Duration) {
	//check toset if right, 1508137123 ---
	if toset.Before(time.Unix(1508137123, 0)) {
		return
	}
	localtime := time.Now().Local()
	currentdiff := localtime.Sub(toset)
	if currentdiff > difftime || currentdiff < -difftime {
		cmd := exec.Command("date", "-s", toset.Format("2006.01.02-15:04:05"))
		cmd.Run()
		log.Println(log.DebugLog, "to set time to local now:", localtime.String(), " to set:", toset.String())
	}
}

func (this *HttpReq) PostAuto(reqBody interface{}, resDetail interface{}) (int, string, error) {
	defer utils.DumpPanicError()
	this.Init()
	api, ok := ApiUrls[reflect.TypeOf(reqBody).Name()]
	var url string

	if ok == false {
		return -1, "", errors.New("can not find the url")
	}

	if api.IsEncrypted {
		url = api.Url + "?aes=1"
	} else {
		url = api.Url + "?aes=0"
	}

	if api.IsEncrypted || api.ReqparamIsString {
		data, _ := json.Marshal(reqBody)
		log.Println(log.DebugLog, "http req body:", string(data))
		if api.IsEncrypted {
			toSendString := strconv.FormatInt(time.Now().UnixNano()/1e6, 10) + "|" + string(data)
			//log.Println(log.DebugLog, "send data:", toSendString, globals.AesKey)

			if data, dataerr := utils.AesEncryptBase64Key([]byte(toSendString), globals.AesKey); dataerr != nil {
				log.Println(log.DebugLog, dataerr.Error())
				return -1, "", dataerr
			} else {
				base64ToSendData := base64.StdEncoding.EncodeToString(data)
				//log.Println(log.DebugLog, "to encrypt base64:", base64ToSendData)
				this.SetReqBody(base64ToSendData)
			}
		} else {
			this.SetReqBody(string(data))
		}
	} else {
		this.SetReqBody(reqBody)
	}

	return this.PostBase(Url(url), resDetail, api.IsEncrypted)
}

func HttpPostAuto(reqbody interface{}, resDetail interface{}) (code int, detail string, err error) {
	var req HttpReq
	return req.PostAuto(reqbody, resDetail)
}

func HttpPostWithUrl(url string, reqBody interface{}, resDetail interface{}) (code int, detail string, err error) {
	var req HttpReq
	req.Init()
	req.SetReqBody(reqBody)
	return req.PostBase(url, resDetail, false)
}

func HttpTestAndCalTime(ctx context.Context) error {
	ch := make(chan error, 1)
	go func() (err error) {
		defer func() {
			ch <- err
		}()

		if server, err := ntp.CalTimeWithDefaultServers(); err == nil {
			log.Println(log.DebugLog, "Cal time success:", server, time.Now())
			return nil
		} else {
			log.Println(log.ErrorLog, "Cal time fail with ntp:", err)
		}

		var req HttpReqGetTimeStamp
		var rsp HttpRspGetTimeStamp
		if code, _, _ := HttpPostAuto(req, &rsp); code == 1000 {
			return nil
		} else {
			return errors.New("http req error!")
		}
	}()

	select {
	case er := <-ch:
		return er

	case <-ctx.Done():
		log.Println(log.ErrorLog, "cal time ctx done!")
		return errors.New("cal time ctx done!")
	}
}
