package logic

//处理websocket云渲染任务数据
import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"fusenapi/constants"
	"fusenapi/model/gmodel"
	"fusenapi/service/repositories"
	"fusenapi/utils/curl"
	"fusenapi/utils/hash"
	"fusenapi/utils/websocket_data"
	"github.com/zeromicro/go-zero/core/logx"
	"gorm.io/gorm"
	"strconv"
	"time"
)

// 云渲染属性
type extendRenderProperty struct {
	renderImageTask        map[string]*renderTask          //需要渲染的图片任务 key是taskId val 是renderId
	renderImageTaskCtlChan chan renderImageControlChanItem //渲染任务新增/回调结果移除任务/更新渲染耗时属性的控制通道(由于任务map无法读写并发)
	renderChan             chan []byte                     //渲染消息入口的缓冲队列
}

// 渲染任务新增移除的控制通道的数据
type renderImageControlChanItem struct {
	option               int        // 0删除 1添加 2修改耗时属性
	taskId               string     //map的key(必须传)
	renderId             string     // map的val(增加任务时候传)
	renderNotifyImageUrl string     //渲染回调数据(删除任务时候传)
	taskProperty         renderTask //渲染任务的属性
}

// 渲染任务属性
type renderTask struct {
	renderId                        string //渲染id(新增任务传)
	combineTakesTime                int64  //合刀版图耗时
	uploadCombineImageTakesTime     int64  //上传刀版图耗时
	unityRenderBeginTime            int64  //发送给unity时间
	unityRenderEndTime              int64  //unity回调结果时间
	uploadUnityRenderImageTakesTime int64  //上传unity渲染结果图时间
}

// 发送到渲染缓冲队列
func (w *wsConnectItem) sendToRenderChan(data []byte) {
	select {
	case <-w.closeChan: //已经关闭
		return
	case w.extendRenderProperty.renderChan <- data: //发入到缓冲队列
		return
	case <-time.After(time.Second * 3): //三秒没进入缓冲队列就丢弃
		return
	}
}

// 渲染发送到组装数据组装数据(缓冲队列)
func (w *wsConnectItem) renderImage() {
	defer func() {
		if err := recover(); err != nil {
			logx.Error("func renderImage err:", err)
		}
	}()
	for {
		select {
		case <-w.closeChan: //已关闭
			return
		case data := <-w.extendRenderProperty.renderChan:
			w.consumeRenderCache(data)
		}
	}

}

// 消费渲染缓冲数据
func (w *wsConnectItem) consumeRenderCache(data []byte) {
	logx.Info("消费渲染数据:", string(data))
	var renderImageData websocket_data.RenderImageReqMsg
	if err := json.Unmarshal(data, &renderImageData); err != nil {
		w.incomeDataFormatErrResponse("invalid format of render data:" + string(data))
		logx.Error("invalid format of websocket render image message", err)
		return
	}
	//获取上传最近的logo
	userMaterial, err := w.logic.svcCtx.AllModels.FsUserMaterial.FindLatestOne(w.logic.ctx, w.userId, w.guestId)
	if err != nil {
		if !errors.Is(err, gorm.ErrRecordNotFound) {
			w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, "", "failed to get user logo", w.userId, w.guestId, 0, 0, 0, 0)
			logx.Error("failed to get user logo")
			return
		}
		//使用默认logo(id=0)
		userMaterialDefault, err := w.logic.svcCtx.AllModels.FsUserMaterial.FindOneById(w.logic.ctx, 0)
		if err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, "", "default logo is not exists", w.userId, w.guestId, 0, 0, 0, 0)
				return
			}
			w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, "", "failed to get default logo", w.userId, w.guestId, 0, 0, 0, 0)
			logx.Error("default logo is not exists")
			return
		}
		renderImageData.RenderData.Logo = *userMaterialDefault.ResourceUrl
	} else {
		renderImageData.RenderData.Logo = *userMaterial.ResourceUrl
	}
	//用户id赋值
	renderImageData.RenderData.UserId = w.userId
	renderImageData.RenderData.GuestId = w.guestId
	var productSize *gmodel.FsProductSize
	//指定尺寸
	if renderImageData.RenderData.ProductSizeId > 0 {
		productSize, err = w.logic.svcCtx.AllModels.FsProductSize.FindOneByIdProductId(w.logic.ctx, renderImageData.RenderData.ProductSizeId, renderImageData.RenderData.ProductId)
	} else { //获取产品第一个尺寸
		productSize, err = w.logic.svcCtx.AllModels.FsProductSize.GetProductFirstSize(w.logic.ctx, renderImageData.RenderData.ProductId)
	}
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, "", "product first size is not exists", w.userId, w.guestId, 0, 0, renderImageData.RenderData.ProductSizeId, 0)
			logx.Error("product size  is not found")
			return
		}
		w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, "", "failed to get product first size", w.userId, w.guestId, 0, 0, renderImageData.RenderData.ProductSizeId, 0)
		logx.Error("failed to get product  size:", err)
		return
	}
	//获取模型(只是获取id)
	model3dInfo, err := w.logic.svcCtx.AllModels.FsProductModel3d.GetOneBySizeIdTag(w.logic.ctx, productSize.Id, constants.TAG_MODEL, "id")
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, "", "product model is not exists", w.userId, w.guestId, 0, 0, productSize.Id, 0)
			logx.Error("product model  is not found")
			return
		}
		w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, "", "failed to get product model", w.userId, w.guestId, 0, 0, productSize.Id, 0)
		logx.Error("failed to get product model:", err)
		return
	}
	//获取模板
	productTemplate, err := w.logic.svcCtx.AllModels.FsProductTemplateV2.FindFirstOneCloudRenderByProductIdModelIdTemplateTag(w.logic.ctx, renderImageData.RenderData.ProductId, model3dInfo.Id, renderImageData.RenderData.TemplateTag)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, "", "product template is not exists", w.userId, w.guestId, 0, model3dInfo.Id, productSize.Id, 0)
			logx.Error("template info is not found")
			return
		}
		w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, "", "failed to get product template", w.userId, w.guestId, 0, model3dInfo.Id, productSize.Id, 0)
		logx.Error("failed to get template info:", err)
		return
	}
	//获取渲染设置信息
	element, err := w.logic.svcCtx.AllModels.FsProductTemplateElement.FindOneByModelId(w.logic.ctx, *productTemplate.ElementModelId)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, "", "render element is not exists", w.userId, w.guestId, productTemplate.Id, model3dInfo.Id, productSize.Id, *productTemplate.ElementModelId)
			logx.Error("element info is not found,element_model_id = ", 0)
			return
		}
		w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, "", "failed to get render element", w.userId, w.guestId, productTemplate.Id, model3dInfo.Id, productSize.Id, *productTemplate.ElementModelId)
		logx.Error("failed to get element ,", err)
		return
	}
	//获取刀版图
	combineReq := repositories.LogoCombineReq{
		UserId:      renderImageData.RenderData.UserId,
		GuestId:     renderImageData.RenderData.GuestId,
		TemplateId:  productTemplate.Id,
		TemplateTag: renderImageData.RenderData.TemplateTag,
		Website:     renderImageData.RenderData.Website,
		Slogan:      renderImageData.RenderData.Slogan,
		Address:     renderImageData.RenderData.Address,
		Phone:       renderImageData.RenderData.Phone,
		Qrcode:      renderImageData.RenderData.Qrcode,
		LogoUrl:     renderImageData.RenderData.Logo,
	}
	res, err := w.logic.svcCtx.Repositories.ImageHandle.LogoCombine(w.logic.ctx, &combineReq)
	if err != nil {
		w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, "", "failed to combine image:"+err.Error(), w.userId, w.guestId, productTemplate.Id, model3dInfo.Id, productSize.Id, *productTemplate.ElementModelId)
		logx.Error("合成刀版图失败,合成请求数据:", combineReq, "错误信息:", err)
		return
	}
	combineImage := "" //刀版图
	if res != nil && res.ResourceUrl != nil {
		combineImage = *res.ResourceUrl
	} else {
		w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, "", "combine image is empty", w.userId, w.guestId, productTemplate.Id, model3dInfo.Id, productSize.Id, *productTemplate.ElementModelId)
		logx.Error("合成刀版图失败,合成的刀版图是空指针:", err)
		return
	}
	//发送合图结果消息
	w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_COMBINE_IMAGE, websocket_data.CombineImageRspMsg{
		RenderId:     renderImageData.RenderId,
		CombineImage: combineImage,
		CombineProcessTime: websocket_data.CombineProcessTime{
			CombineTakesTime:            fmt.Sprintf("%dms", res.DiffTimeLogoCombine),
			UploadCombineImageTakesTime: fmt.Sprintf("%dms", res.DiffTimeUploadFile),
		},
	}))
	//如果指定指定只返回刀版图
	if renderImageData.OnlyReturnCombineImage {
		logx.Info("云渲染传入size id则不走unity云渲染,只返回刀版图,render_id:", renderImageData.RenderId)
		return
	}
	//获取唯一id
	taskId := w.genRenderTaskId(renderImageData, model3dInfo, productTemplate, element)
	//查询有没有缓存的资源,有就返回######################
	resource, err := w.logic.svcCtx.AllModels.FsResource.FindOneById(w.logic.ctx, taskId)
	if err != nil {
		if !errors.Is(err, gorm.ErrRecordNotFound) {
			w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, taskId, "failed to get render cache", w.userId, w.guestId, productTemplate.Id, model3dInfo.Id, productSize.Id, *productTemplate.ElementModelId)
			logx.Error("failed to find render resource:", err)
			return
		}
	} else {
		//返回给客户端
		b := w.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE, websocket_data.RenderImageRspMsg{
			RenderId: renderImageData.RenderId,
			Image:    *resource.ResourceUrl,
			RenderProcessTime: websocket_data.RenderProcessTime{
				CombineTakesTime:                "cache",
				UnityRenderTakesTime:            "cache",
				UploadCombineImageTakesTime:     "cache",
				UploadUnityRenderImageTakesTime: "cache",
			},
		})
		//发送数据到out chan
		w.sendToOutChan(b)
		return
	}
	//###########################################
	//把需要渲染的图片任务加进去
	w.createRenderTask(renderImageControlChanItem{
		option:   1, //0删除 1添加 2修改耗时属性
		taskId:   taskId,
		renderId: renderImageData.RenderId,
	})
	//记录刀版图合成消耗时间跟上传刀版图时间以及刀版图
	w.modifyRenderTaskProperty(renderImageControlChanItem{
		option: 2,
		taskId: taskId,
		taskProperty: renderTask{
			combineTakesTime:            res.DiffTimeLogoCombine,
			uploadCombineImageTakesTime: res.DiffTimeUploadFile,
		},
	})
	//组装数据
	if err = w.assembleRenderData(taskId, combineImage, renderImageData, productTemplate, model3dInfo, element, productSize); err != nil {
		logx.Error("组装数据失败:", err)
		return
	}
}

// 组装渲染任务id
func (w *wsConnectItem) genRenderTaskId(renderImageData websocket_data.RenderImageReqMsg, model3dInfo *gmodel.FsProductModel3d, productTemplate *gmodel.FsProductTemplateV2, element *gmodel.FsProductTemplateElement) string {
	//生成任务id(需要把user_id,guest_id设为0)
	incomeHashParam := renderImageData.RenderData
	incomeHashParam.UserId = 0  //设为0(渲染跟用户id无关)
	incomeHashParam.GuestId = 0 //设为0(渲染跟用户id无关)
	incomeHashBytes, _ := json.Marshal(incomeHashParam)
	modelHashStr := ""
	templateHashStr := ""
	if model3dInfo.ModelInfo != nil {
		modelHashStr = *model3dInfo.ModelInfo
	}
	if productTemplate.TemplateInfo != nil {
		templateHashStr = *productTemplate.TemplateInfo
	}
	elementHashBytes, _ := json.Marshal(element)
	hashMap := map[string]interface{}{
		"income_param":   incomeHashBytes,
		"model_info":     modelHashStr,
		"template_info":  templateHashStr,
		"material_image": *productTemplate.MaterialImg,
		"render_element": elementHashBytes,
	}
	return hash.JsonHashKey(hashMap)
}

// 组装数据发送给unity
func (w *wsConnectItem) assembleRenderData(taskId string, combineImage string, info websocket_data.RenderImageReqMsg, productTemplate *gmodel.FsProductTemplateV2, model3dInfo *gmodel.FsProductModel3d, element *gmodel.FsProductTemplateElement, productSize *gmodel.FsProductSize) (err error) {
	//组装数据
	refletion := -1
	if element.Refletion != nil && *element.Refletion != "" {
		refletion, err = strconv.Atoi(*element.Refletion)
		if err != nil {
			logx.Error("err refletion:set default -1")
			w.renderErrResponse(info.RenderId, info.RenderData.TemplateTag, taskId, "parse element.Refletion from string to number err", w.userId, w.guestId, productTemplate.Id, model3dInfo.Id, productSize.Id, *productTemplate.ElementModelId)
			return err
		}
	}
	//组装data数据
	var mode map[string]interface{}
	if element.Mode != nil && *element.Mode != "" {
		if err = json.Unmarshal([]byte(*element.Mode), &mode); err != nil {
			logx.Error("faile to parse element mode json:", err)
			w.renderErrResponse(info.RenderId, info.RenderData.TemplateTag, taskId, "parse element.Mode err", w.userId, w.guestId, productTemplate.Id, model3dInfo.Id, productSize.Id, *productTemplate.ElementModelId)
			return err
		}
	}
	tempData := make([]map[string]interface{}, 0, 3)
	if element.Base != nil && *element.Base != "" {
		tempData = append(tempData, map[string]interface{}{
			"name":      "model",
			"data":      "0," + combineImage + "," + *element.Base,
			"type":      "other",
			"layer":     "0",
			"is_update": 1,
			"mode":      mode["model"],
		})
	}
	if element.Shadow != nil && *element.Shadow != "" {
		tempData = append(tempData, map[string]interface{}{
			"name":      "shadow",
			"data":      *element.Shadow,
			"type":      "other",
			"layer":     "0",
			"is_update": 0,
			"mode":      mode["shadow"],
		})
	}
	if element.ModelP != nil && *element.ModelP != "" {
		tempData = append(tempData, map[string]interface{}{
			"name":      "model_P",
			"data":      "0," + *element.ModelP,
			"type":      "other",
			"layer":     "0",
			"is_update": 0,
			"mode":      mode["model_P"],
		})
	}
	result := []interface{}{
		map[string]interface{}{
			"light":     *element.Light,
			"refletion": refletion,
			"scale":     *element.Scale,
			"sku_id":    info.RenderData.ProductId,
			"tid":       *element.Title,
			"rotation":  *element.Rotation,
			"filePath":  "", //todo 文件路径,针对千人千面
			"data":      tempData,
		},
	}
	sendData := map[string]interface{}{
		"id":               taskId,
		"order_id":         0,
		"user_id":          info.RenderData.UserId,
		"guest_id":         info.RenderData.GuestId,
		"sku_ids":          []int64{info.RenderData.ProductId},
		"tids":             []string{*element.Title},
		"data":             result,
		"is_thousand_face": 0,
		"folder":           "", //todo 千人千面需要使用
	}
	//请求unity接口
	url := w.logic.svcCtx.Config.Unity.Host + "/api/render/queue/push"
	header := make(map[string]string)
	header["content-type"] = "application/json"
	postData := map[string]interface{}{
		"group":       "unity3d",
		"source":      "home page",
		"priority":    1,
		"create_at":   time.Now().UTC(),
		"render_data": sendData,
	}
	postDataBytes, _ := json.Marshal(postData)
	unityRenderBeginTime := time.Now().UTC().UnixMilli()
	_, err = curl.ApiCall(url, "POST", header, bytes.NewReader(postDataBytes), time.Second*10)
	if err != nil {
		w.renderErrResponse(info.RenderId, info.RenderData.TemplateTag, taskId, "request unity api err", w.userId, w.guestId, productTemplate.Id, model3dInfo.Id, productSize.Id, *productTemplate.ElementModelId)
		logx.Error("failed to send data to unity")
		return err
	}
	//记录发送到unity时间
	w.modifyRenderTaskProperty(renderImageControlChanItem{
		option: 2,
		taskId: taskId,
		taskProperty: renderTask{
			unityRenderBeginTime: unityRenderBeginTime,
		},
	})
	logx.Info("发送到unity成功,刀版图:", combineImage, "  请求unity的数据:", string(postDataBytes))
	return nil
}

// 增加渲染任务
func (w *wsConnectItem) createRenderTask(data renderImageControlChanItem) {
	//强制设为增加
	data.option = 1
	select {
	case <-w.closeChan: //关闭
		return
	case w.extendRenderProperty.renderImageTaskCtlChan <- data:
		return
	case <-time.After(time.Second * 3):
		return
	}
}

// 渲染回调处理并删除渲染任务
func (w *wsConnectItem) deleteRenderTask(data renderImageControlChanItem) {
	//强制设为删除
	data.option = 0
	select {
	case <-w.closeChan: //关闭
		return
	case w.extendRenderProperty.renderImageTaskCtlChan <- data:
		return
	case <-time.After(time.Second * 3):
		return
	}
}

// 修改任务属性(只有耗时属性可以更新)
func (w *wsConnectItem) modifyRenderTaskProperty(data renderImageControlChanItem) {
	if data.taskId == "" {
		logx.Error("设置耗时属性需要的task_id不能为空")
		return
	}
	//强制设为修改耗时属性
	data.option = 2
	select {
	case <-w.closeChan: //关闭
		return
	case w.extendRenderProperty.renderImageTaskCtlChan <- data:
		return
	case <-time.After(time.Second * 3):
		return
	}
}

// 处理渲染任务的增加/删除/修改耗时属性(任务map不能读写并发,所以放在chan里面串行执行)
func (w *wsConnectItem) operationRenderTask() {
	defer func() {
		if err := recover(); err != nil {
			logx.Error("operation render task panic:", err)
		}
	}()
	for {
		select {
		case <-w.closeChan:
			return
		case data := <-w.extendRenderProperty.renderImageTaskCtlChan:
			switch data.option {
			case 0: //渲染结果回调,删除任务
				//存在任务,则发送渲染结果给前端
				if taskData, ok := w.extendRenderProperty.renderImageTask[data.taskId]; ok {
					CombineTakesTime := "0ms"
					UnityRenderTakesTime := "0ms"
					uploadCombineImageTakesTime := "0ms"
					uploadUnityRenderImageTakesTime := "0ms"
					//合图时间
					if taskData.combineTakesTime > 0 {
						CombineTakesTime = fmt.Sprintf("%dms", taskData.combineTakesTime)
					}
					//上传刀版图时间
					if taskData.uploadCombineImageTakesTime > 0 {
						uploadCombineImageTakesTime = fmt.Sprintf("%dms", taskData.uploadCombineImageTakesTime)
					}
					//unity渲染时间
					if taskData.unityRenderBeginTime > 0 && taskData.unityRenderEndTime > 0 {
						UnityRenderTakesTime = fmt.Sprintf("%dms", taskData.unityRenderEndTime-taskData.unityRenderBeginTime)
					}
					//上传unity渲染图耗时
					if taskData.uploadUnityRenderImageTakesTime > 0 {
						uploadUnityRenderImageTakesTime = fmt.Sprintf("%dms", taskData.uploadUnityRenderImageTakesTime)
					}
					//发送到出口
					w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE, websocket_data.RenderImageRspMsg{
						RenderId: taskData.renderId,
						Image:    data.renderNotifyImageUrl,
						RenderProcessTime: websocket_data.RenderProcessTime{
							CombineTakesTime:                CombineTakesTime,
							UnityRenderTakesTime:            UnityRenderTakesTime,
							UploadCombineImageTakesTime:     uploadCombineImageTakesTime,
							UploadUnityRenderImageTakesTime: uploadUnityRenderImageTakesTime,
						},
					}))
				}
				//删除任务
				delete(w.extendRenderProperty.renderImageTask, data.taskId)
			case 1: //新增任务
				w.extendRenderProperty.renderImageTask[data.taskId] = &renderTask{
					renderId: data.renderId,
				}

			case 2: //修改任务属性
				if taskData, ok := w.extendRenderProperty.renderImageTask[data.taskId]; ok {
					//合图耗时
					if data.taskProperty.combineTakesTime != 0 {
						taskData.combineTakesTime = data.taskProperty.combineTakesTime
					}
					//上传合图耗时
					if data.taskProperty.uploadCombineImageTakesTime != 0 {
						taskData.uploadCombineImageTakesTime = data.taskProperty.uploadCombineImageTakesTime
					}
					//上传渲染结果图耗时
					if data.taskProperty.uploadUnityRenderImageTakesTime != 0 {
						taskData.uploadUnityRenderImageTakesTime = data.taskProperty.uploadUnityRenderImageTakesTime
					}
					//发送unity时间
					if data.taskProperty.unityRenderBeginTime != 0 {
						taskData.unityRenderBeginTime = data.taskProperty.unityRenderBeginTime
					}
					//收到unity返回的时间
					if data.taskProperty.unityRenderEndTime != 0 {
						taskData.unityRenderEndTime = data.taskProperty.unityRenderEndTime
					}
				}
			}
		}
	}
}