package main

import (
	"errors"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/go-resty/resty/v2"

	"lc/common/mqtt"
	"lc/common/onvif/profiles/media"
	"lc/common/protocol"
	"lc/common/util"
)

var (
	AppLive              = "live"
	UploadSnapshot       = "/camera/v1/snapshot"
	UploadFlv            = "/camera/v1/flv"
	UploadPresetSnapshot = "/camera/v1/preset/picture"
)

func (o *LcDevice) GetProfiles() error {
	XAddr, ok := o.endpoints["media"]
	if !ok {
		return errors.New("不支持media")
	}
	m := media.NewMedia(o.client, XAddr)
	reply, err := m.GetProfiles(&media.GetProfiles{})
	if err != nil {
		return err
	}
	for _, v := range reply.Profiles {
		vt := VideoToken{
			Token:         string(v.Token),
			Name:          string(v.Name),
			VideoEncoding: string(v.VideoEncoderConfiguration.Encoding),
			VideoResolution: Resolution{
				Width:  v.VideoEncoderConfiguration.Resolution.Width,
				Height: v.VideoEncoderConfiguration.Resolution.Height,
			},
			VideoFrameRateLimit: v.VideoEncoderConfiguration.RateControl.FrameRateLimit,
			VideoBitrateLimit:   v.VideoEncoderConfiguration.RateControl.BitrateLimit,
		}
		o.tokens[vt.Token] = &vt
	}
	return nil
}

func (o *LcDevice) GetAllStreamUri() error {
	XAddr, ok := o.endpoints["media"]
	if !ok {
		return errors.New("不支持media")
	}
	m := media.NewMedia(o.client, XAddr)
	for k, v := range o.tokens {
		for retry := 3; retry > 0; retry-- {
			reply, err := m.GetStreamUri(&media.GetStreamUri{ProfileToken: media.ReferenceToken(k)})
			if err != nil {
				time.Sleep(3 * time.Second)
			} else {
				v.VideoRtspUrl = strings.ReplaceAll(string(reply.MediaUri.Uri), "rtsp://", "rtsp://"+o.onvifDev.User+":"+o.onvifDev.Password+"@")
				break
			}
		}
	}
	return nil
}

func (o *LcDevice) GetAllSnapshotUri() error {
	XAddr, ok := o.endpoints["media"]
	if !ok {
		return errors.New("不支持media")
	}

	m := media.NewMedia(o.client, XAddr)
	for k, v := range o.tokens {
		for retry := 3; retry > 0; retry-- {
			reply, err := m.GetSnapshotUri(&media.GetSnapshotUri{ProfileToken: media.ReferenceToken(k)})
			if err != nil {
				//其他错误停3秒重试
				time.Sleep(5 * time.Second)
			} else {
				v.SnapshotUrl = string(reply.MediaUri.Uri)
				break
			}
		}
	}
	return nil
}

// SnapshotFromRtsp 通过RTSP截取一张图片
func (o *LcDevice) SnapshotFromRtsp(token string) (string, error) {
	RtspUrl := o.GetRtspUrl(token)
	if RtspUrl == "" {
		return "", errors.New("找不到RtspUrl")
	}
	wd, _ := os.Getwd()
	proc := &os.ProcAttr{
		Dir: wd,
		Env: os.Environ(),
		Files: []*os.File{
			os.Stdin,
			os.Stdout,
			os.Stderr,
		},
	}
	fileName := util.GetPath(4) + o.onvifDev.Code + "_" + strconv.FormatInt(util.MlNow().Unix(), 10) + ".jpg"
	args := []string{onvifDevConfig.Ffmpeg, "-i", RtspUrl, "-t", "0.001", "-y", "-f", "mjpeg", "-r", "1", fileName}
	process, err := os.StartProcess(onvifDevConfig.Ffmpeg, args, proc)
	if err != nil {
		return "", err
	} else {
		go func() {
			time.Sleep(6 * time.Second)
			if process != nil {
				process.Signal(os.Kill)
			}
		}()
		_, err = process.Wait()
		process = nil
	}
	//判断是否已经抓图成功
	if FileExist(fileName) {
		return fileName, nil
	}
	err = errors.New("snapshotFromRtsp:抓图失败")
	return "", err
}

func (o *LcDevice) GetRtspUrl(token string) string {
	rtspUrl := ""
	if v, ok := o.tokens[token]; !ok {
		max := int32(0)
		for _, v := range o.tokens { //查找分辨率最高的SnapshotUrl
			r := v.VideoResolution.Height * v.VideoResolution.Width
			if r > max {
				max = r
				rtspUrl = v.VideoRtspUrl
			}
		}
	} else {
		rtspUrl = v.VideoRtspUrl
	}
	return rtspUrl
}

func (o *LcDevice) GetSnapshotUrl(token string) string {
	snapshotUrl := ""
	max := int32(0)
	if v, ok := o.tokens[token]; !ok {
		for _, v := range o.tokens { //查找分辨率最高的SnapshotUrl
			r := v.VideoResolution.Height * v.VideoResolution.Width
			if r > max {
				max = r
				snapshotUrl = v.SnapshotUrl
			}
		}
	} else {
		snapshotUrl = v.SnapshotUrl
	}
	return snapshotUrl
}

func (o *LcDevice) GetProfileToken(token string) string {
	max := int32(0)
	if _, ok := o.tokens[token]; !ok {
		for _, v := range o.tokens { //查找分辨率最高的SnapshotUrl
			r := v.VideoResolution.Height * v.VideoResolution.Width
			if r > max {
				max = r
				token = v.Token
			}
		}
	}
	return token
}

func (o *LcDevice) Snapshot(token string) {
	snapshotUrl := o.GetSnapshotUrl(token)
	if snapshotUrl == "" {
		return
	}
	var fileName string
	client := resty.New()
	client.SetTimeout(6 * time.Second)
	client.SetBasicAuth(o.onvifDev.User, o.onvifDev.Password)
	resp, err := client.R().Get(snapshotUrl)
	if err != nil {
		fileName, err = o.SnapshotFromRtsp(token)
	} else {
		if resp.StatusCode() != http.StatusOK {
			fileName, err = o.SnapshotFromRtsp(token)
		} else if len(resp.Body()) == 0 {
			fileName, err = o.SnapshotFromRtsp(token)
		} else {
			fileName = util.GetPath(4) + o.onvifDev.Code + "_" + strconv.FormatInt(util.MlNow().Unix(), 10) + ".jpg"
			if err = ioutil.WriteFile(fileName, resp.Body(), os.ModePerm); err != nil {
				return
			}
		}
	}

	if fileName != "" && err == nil {
		client.R().SetFile("file", fileName).Post(o.onvifDev.WebServer + UploadSnapshot)
		os.Remove(fileName)
	}
}

func (o *LcDevice) Snapshot2(file string) error {
	snapshotUrl := o.GetSnapshotUrl("")
	if snapshotUrl == "" {
		return errors.New("找不到链接，截图错误")
	}
	var fileName string
	client := resty.New()
	client.SetTimeout(6 * time.Second)
	client.SetBasicAuth(o.onvifDev.User, o.onvifDev.Password)
	resp, err := client.R().Get(snapshotUrl)
	if err != nil {
		return err
	}
	if resp.StatusCode() != http.StatusOK {
		return errors.New("调用截图接口发生错误")
	}
	fileName = util.GetPath(4) + file
	err = ioutil.WriteFile(fileName, resp.Body(), os.ModePerm)
	if err != nil {
		return err
	}
	resp, err = client.R().SetFile("file", fileName).Post(o.onvifDev.WebServer + UploadPresetSnapshot)
	os.Remove(fileName)
	if err != nil {
		return err
	}
	if resp.StatusCode() != http.StatusOK {
		return errors.New("截图上传发生错误")
	}
	return nil
}

func (o *LcDevice) CreateProcess(rtspurl string) {
	dir, _ := os.Getwd()
	proc := &os.ProcAttr{
		Dir: dir,
		Env: os.Environ(),
		Files: []*os.File{
			os.Stdin,
			os.Stdout,
			os.Stderr,
		},
	}
	args := []string{onvifDevConfig.Ffmpeg, "-fflags", "nobuffer", "-i", rtspurl, "-c", "copy", "-f", "flv", o.onvifDev.RtmpServer + "/" + AppLive + "/" + o.onvifDev.Code}
	process, err := os.StartProcess(onvifDevConfig.Ffmpeg, args, proc)
	if err != nil {
		return
	}
	o.ffmpeg = process
	go o.WatchFfmpeg()
}

func (o *LcDevice) CleanupProcess() {
	if o.ffmpeg != nil {
		o.ffmpeg.Kill()
	}
}

func (o *LcDevice) WatchFfmpeg() {
	status := make(chan *os.ProcessState)
	died := make(chan error)
	go func() {
		state, err := o.ffmpeg.Wait()
		if err != nil {
			died <- err
			return
		}
		status <- state
	}()
	select {
	case _ = <-status:
	case _ = <-died:
	}
	o.ffmpeg = nil
}

func (o *LcDevice) RecordToFLV(token string, second int) {
	//最长录制60秒视频
	if second > 60 {
		second = 60
	}
	RtspUrl := o.GetRtspUrl(token)
	if RtspUrl == "" {
		return
	}
	dir, _ := os.Getwd()
	proc := &os.ProcAttr{
		Dir: dir,
		Env: os.Environ(),
		Files: []*os.File{
			os.Stdin,
			os.Stdout,
			os.Stderr,
		},
	}
	fileName := util.GetPath(4) + o.onvifDev.Code + "_" + strconv.FormatInt(util.MlNow().Unix(), 10) + ".flv"
	args := []string{onvifDevConfig.Ffmpeg, "-i", RtspUrl, "-c", "copy", "-t", strconv.Itoa(second), fileName}
	process, err := os.StartProcess(onvifDevConfig.Ffmpeg, args, proc)
	if err != nil {
		return
	} else if process != nil {
		go func() {
			time.Sleep(time.Duration(second+5) * time.Second)
			if process != nil {
				process.Signal(os.Kill)
			}
		}()
		_, err = process.Wait()
		//判断是否已经抓图成功
		if fileName != "" && err == nil {
			if FileExist(fileName) {
				client := resty.New()
				client.SetTimeout(6 * time.Second)
				client.R().SetFile("file", fileName).Post(o.onvifDev.WebServer + UploadFlv)
				os.Remove(fileName)
			}
		}
	}
}

// HandleTpOnvifSnapshot 截图
func (o *LcDevice) HandleTpOnvifSnapshot(m mqtt.Message) {
	var obj protocol.Pack_MediaCommonInfo
	if err := obj.DeCode(m.PayloadString()); err != nil {
		return
	}
	if o.onvifDev.Code != obj.Id {
		return
	}

	var ret protocol.Pack_IPCCommonACK
	if strRet, err := ret.EnCode(o.onvifDev.Code, appConfig.GID, "", obj.Seq, nil); err == nil {
		GetMQTTMgr().Publish(GetTopic(o.GetDevType(), o.onvifDev.Code, protocol.TP_ONVIF_SNAPSHOT_ACK), strRet, mqtt.AtMostOnce, ToAll)
	}

	go o.Snapshot(obj.Data.ProfileToken)
}

// HandleTpOnvifVideo 拉流推流，停止拉流推流
func (o *LcDevice) HandleTpOnvifVideo(m mqtt.Message) {
	var obj protocol.Pack_MediaCommonInfo
	if err := obj.DeCode(m.PayloadString()); err != nil {
		return
	}
	if o.onvifDev.Code != obj.Id {
		return
	}

	var ret protocol.Pack_IPCCommonACK
	if strRet, err := ret.EnCode(o.onvifDev.Code, appConfig.GID, "", obj.Seq, nil); err == nil {
		GetMQTTMgr().Publish(GetTopic(o.GetDevType(), o.onvifDev.Code, protocol.TP_ONVIF_VIDEO_ACK), strRet, mqtt.AtMostOnce, ToAll)
	}

	//如果已开启GB28181,则不用启动ffmpeg推流
	if o.onvifDev.Gb28181 {
		return
	}

	if obj.Data.Flag == 1 {
		o.observer++
		if o.ffmpeg != nil {
			return
		}
		if rtspUrl := o.GetRtspUrl(obj.Data.ProfileToken); rtspUrl == "" {
			return
		} else {
			o.CreateProcess(rtspUrl)
		}
	} else if obj.Data.Flag == 2 { //停止推流
		if o.observer > 0 {
			o.observer--
			if o.observer == 0 {
				o.CleanupProcess()
			}
		}
	}
}

func (o *LcDevice) HandleTpOnvifRecord(m mqtt.Message) {
	var obj protocol.Pack_MediaCommonInfo
	if err := obj.DeCode(m.PayloadString()); err != nil {
		return
	}
	if o.onvifDev.Code != obj.Id {
		return
	}
	var ret protocol.Pack_IPCCommonACK
	if strRet, err := ret.EnCode(o.onvifDev.Code, appConfig.GID, "", obj.Seq, nil); err == nil {
		GetMQTTMgr().Publish(GetTopic(o.GetDevType(), o.onvifDev.Code, protocol.TP_ONVIF_RECORD_ACK), strRet, mqtt.AtMostOnce, ToAll)
	}
	go o.RecordToFLV("", 30)
}
