package logic

//处理websocket云渲染任务数据
import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"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"
)

var (
	//每个websocket渲染任务缓冲队列长度默认值
	renderChanLen = 500
	//每个websocket渲染并发数
	renderChanConcurrency = 100
)

// 渲染处理器
type renderProcessor struct {
}

// 云渲染属性
type extendRenderProperty struct {
	renderChan          chan websocket_data.RenderImageReqMsg //渲染消息入口的缓冲队列
	renderCtx           context.Context                       //渲染控制上下文(用于切换模板标签/颜色/logo取消之前发送的不相同的任务)
	renderCtxCancelFunc context.CancelFunc                    //渲染控制上下文取消方法
	selectColorIndex    int                                   //选择的颜色索引(用于标记连接当前连接选择的颜色)
	templateTag         string                                //模板标签 (用于标记连接当前连接选择的模板标签)
	Logo                string                                //logo地址 (用于标记连接当前连接选择的logo)
}

// 处理分发到这里的数据
func (r *renderProcessor) allocationMessage(w *wsConnectItem, data []byte) {
	//logx.Info("收到渲染任务消息")
	var renderImageData websocket_data.RenderImageReqMsg
	if err := json.Unmarshal(data, &renderImageData); err != nil {
		w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "数据格式错误", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0)
		logx.Error("invalid format of websocket render image message", err)
		return
	}
	//颜色/模板标签/logo变更
	ifCancelOldCtx := false
	if renderImageData.RenderData.TemplateTag != w.extendRenderProperty.templateTag {
		ifCancelOldCtx = true
	}
	if renderImageData.RenderData.TemplateTagColor.SelectedColorIndex != w.extendRenderProperty.selectColorIndex {
		ifCancelOldCtx = true
	}
	if renderImageData.RenderData.Logo != w.extendRenderProperty.Logo {
		ifCancelOldCtx = true
	}
	if ifCancelOldCtx {
		//赋值新的
		w.extendRenderProperty.templateTag = renderImageData.RenderData.TemplateTag
		w.extendRenderProperty.selectColorIndex = renderImageData.RenderData.TemplateTagColor.SelectedColorIndex
		w.extendRenderProperty.Logo = renderImageData.RenderData.Logo
		//让之前的失效
		w.extendRenderProperty.renderCtxCancelFunc()
		//重新赋值
		w.extendRenderProperty.renderCtx, w.extendRenderProperty.renderCtxCancelFunc = context.WithCancel(w.logic.ctx)
	}
	select {
	case <-w.closeChan: //已经关闭
		return
	case w.extendRenderProperty.renderChan <- renderImageData: //发入到缓冲队列
		return
	case <-time.After(time.Millisecond * 50): //超过50毫秒丢弃
		w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "渲染队列溢出,请稍后再发", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0)
		return
	}
}

// 消费渲染缓冲队列数据
func (w *wsConnectItem) consumeRenderImageData() {
	defer func() {
		if err := recover(); err != nil {
			logx.Error("func consumeRenderImageData panic:", err)
			//如果是上下文取消渲染的异常
			if isCancelRenderPanic(err) {
				//通知unity取消任务
				sendCancelRenderMsgToUnity(w.wid, time.Now().UTC().Unix())
			}
		}
	}()
	//限制并发
	limitChan := make(chan struct{}, renderChanConcurrency)
	defer close(limitChan)
	for {
		select {
		case <-w.closeChan: //已关闭
			return
		case data := <-w.extendRenderProperty.renderChan: //消费数据
			limitChan <- struct{}{}
			go func(d websocket_data.RenderImageReqMsg) {
				defer func() {
					if err := recover(); err != nil {
						logx.Error("func renderImage main panic:", err)
					}
				}()
				//临时chan用select io多路复用去判断携程退出
				tmpChan := make(chan struct{}, 1)
				defer close(tmpChan)
				defer func() {
					<-limitChan
				}()
				//如果不是无视上下文切换取消的(后面再开启)
				/*if !d.IgnoreContextCancel {
					go func() {
						defer func() {
							if err := recover(); err != nil {
								logx.Error("func renderImage panic:", err)
							}
						}()
						select {
						case <-w.extendRenderProperty.renderCtx.Done():
							//抛出取消渲染异常
							cancelRenderPanic()
						case <-tmpChan:
							return
						}
					}()
				}*/
				w.renderImage(d)
			}(data)
		}
	}
}

// 执行渲染任务
func (w *wsConnectItem) renderImage(renderImageData websocket_data.RenderImageReqMsg) {
	if renderImageData.RenderData.Logo == "" {
		w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "请传入logo", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0)
		return
	}
	//没传分辨率
	if renderImageData.RenderData.Resolution == "" {
		w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "请传入合图分辨率", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0)
		return
	}
	//分辨率校验
	resolution, err := strconv.Atoi(renderImageData.RenderData.Resolution)
	if err != nil {
		w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "请传入正确的合图分辨率格式", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0)
		return
	}
	if resolution < 100 || resolution > 800 {
		w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "请传入正确的合图分辨率范围值(100~800)", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0)
		return
	}
	lenColor := len(renderImageData.RenderData.TemplateTagColor.Colors)
	if lenColor == 0 {
		w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "请传入模板标签选择的颜色", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0)
		return
	}
	if renderImageData.RenderData.TemplateTagColor.SelectedColorIndex >= lenColor || renderImageData.RenderData.TemplateTagColor.SelectedColorIndex < 0 {
		w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "选择的模板标签颜色索引越界", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0)
		return
	}
	//获取产品信息(部分字段)
	productInfo, err := w.logic.svcCtx.AllModels.FsProduct.FindOne(w.logic.ctx, renderImageData.RenderData.ProductId, "id,is_customization")
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "该产品不存在", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0)
			return
		}
		w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "获取产品失败", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0)
		logx.Error(err)
		return
	}
	//不可定制
	if *productInfo.IsCustomization == 0 {
		w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "该产品不可定制", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0)
		return
	}
	//用户id赋值
	renderImageData.RenderData.UserId = w.userId
	renderImageData.RenderData.GuestId = w.guestId
	//获取信息
	productSize, productTemplate, model3dInfo, err := w.getProductRelationInfo(&renderImageData)
	if err != nil {
		logx.Error(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.RequestId, renderImageData.RenderData.TemplateTag, "", "云渲染设置不存在", renderImageData.RenderData.ProductId, w.userId, w.guestId, productTemplate.Id, model3dInfo.Id, productSize.Id, *productTemplate.ElementModelId)
			logx.Error("element info is not found,element_model_id = ", *productTemplate.ElementModelId)
			return
		}
		w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "获取云渲染设置失败", renderImageData.RenderData.ProductId, w.userId, w.guestId, productTemplate.Id, model3dInfo.Id, productSize.Id, *productTemplate.ElementModelId)
		logx.Error("failed to get element ,", err)
		return
	}
	//累增websocket请求合图数
	increaseCombineRequestCount(w.userId, w.guestId)
	//请求完要释放
	defer decreaseCombineRequestCount(w.userId, w.guestId)
	//获取刀版图
	combineReq := repositories.LogoCombineReq{
		UserId:                   renderImageData.RenderData.UserId,
		GuestId:                  renderImageData.RenderData.GuestId,
		ProductTemplateV2Info:    productTemplate,
		ProductTemplateTagGroups: renderImageData.RenderData.TemplateTagGroups,
		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,
		TemplateTagColor: repositories.TemplateTagColor{
			Color: renderImageData.RenderData.TemplateTagColor.Colors,
			Index: renderImageData.RenderData.TemplateTagColor.SelectedColorIndex,
		},
		Resolution: renderImageData.RenderData.Resolution,
		Debug:      w.debug,
	}
	res, err := w.logic.svcCtx.Repositories.ImageHandle.LogoCombine(w.logic.ctx, &combineReq)
	if err != nil {
		w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "合成刀版图失败:"+err.Error(), renderImageData.RenderData.ProductId, w.userId, w.guestId, productTemplate.Id, model3dInfo.Id, productSize.Id, *productTemplate.ElementModelId)
		//统计合图失败数
		increaseCombineRequestErrorCount(w.userId, w.guestId)
		logx.Error("合成刀版图失败,合成请求数据:", combineReq, "错误信息:", err)
		return
	}
	combineImage := "" //刀版图
	if res != nil && res.ResourceUrl != nil {
		combineImage = *res.ResourceUrl
	} else {
		w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "合成的刀版图是空的地址", renderImageData.RenderData.ProductId, w.userId, w.guestId, productTemplate.Id, model3dInfo.Id, productSize.Id, *productTemplate.ElementModelId)
		logx.Error("合成刀版图失败,合成的刀版图是空指针:", err)
		return
	}
	//发送合图完毕阶段消息
	w.sendCombineImageStepResponseMessage(renderImageData.RequestId, combineImage, productSize.Id, model3dInfo.Id, productTemplate.Id, res.DebugData)
	//获取唯一id
	taskId := w.genRenderTaskId(combineImage, 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.RequestId, renderImageData.RenderData.TemplateTag, taskId, "获取unity云渲染缓存失败", renderImageData.RenderData.ProductId, w.userId, w.guestId, productTemplate.Id, model3dInfo.Id, productSize.Id, *productTemplate.ElementModelId)
			logx.Error("failed to find render resource:", err)
			return
		}
		//无缓存
		logx.Info("无缓存的渲染图,需要unity")
	} else { //有缓存
		//如果没有debug或者debug模式下开启了缓存则返回缓存
		if w.debug == nil || w.debug.IsCache == 1 {
			//返回给客户端
			w.sendRenderResultData(websocket_data.RenderImageRspMsg{
				RequestId: renderImageData.RequestId,
				Image:     *resource.ResourceUrl,
				RenderProcessTime: &websocket_data.RenderProcessTime{
					UnityRenderTakesTime:            "cache",
					UploadUnityRenderImageTakesTime: "cache",
				},
			})
			return
		}
		//否则继续去unity
	}
	//组装数据
	if err = w.assembleRenderDataToUnity(taskId, resolution, combineImage, renderImageData, productTemplate, model3dInfo, element, productSize); err != nil {
		logx.Error("组装数据失败:", err)
		return
	}
}

// 获取模板相关信息
func (w *wsConnectItem) getProductRelationInfo(renderImageData *websocket_data.RenderImageReqMsg) (productSize *gmodel.FsProductSize, productTemplate *gmodel.FsProductTemplateV2, model3d *gmodel.FsProductModel3d, err error) {
	//获取模板
	productTemplate, err = w.logic.svcCtx.AllModels.FsProductTemplateV2.FindOneCloudRenderByProductIdTemplateTag(w.logic.ctx, renderImageData.RenderData.ProductId, renderImageData.RenderData.TemplateTag, "sort ASC")
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "找不到对应开启云渲染模板", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0)
			return nil, nil, nil, errors.New("找不到对应开启云渲染模板")
		}
		logx.Error(err)
		w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "获取对应开启云渲染模板失败", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0)
		return nil, nil, nil, errors.New("获取对应开启云渲染模板失败")
	}
	//判断设计信息是否为空
	if productTemplate.TemplateInfo == nil || *productTemplate.TemplateInfo == "" {
		w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "模板设计信息是空的", renderImageData.RenderData.ProductId, w.userId, w.guestId, productTemplate.Id, 0, 0, 0)
		return nil, nil, nil, errors.New("模板设计信息是空的")
	}
	//根据模板找到模型
	model3d, err = w.logic.svcCtx.AllModels.FsProductModel3d.FindOne(w.logic.ctx, *productTemplate.ModelId)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "找不到对应模型", renderImageData.RenderData.ProductId, w.userId, w.guestId, productTemplate.Id, 0, 0, 0)
			return nil, nil, nil, errors.New("找不到对应模型")
		}
		logx.Error(err)
		w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "获取对应模型失败", renderImageData.RenderData.ProductId, w.userId, w.guestId, productTemplate.Id, 0, 0, 0)
		return nil, nil, nil, errors.New("获取对应模型失败")
	}
	//判断设计信息是否为空
	if model3d.ModelInfo == nil || *model3d.ModelInfo == "" {
		w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "模型设计信息是空的", renderImageData.RenderData.ProductId, w.userId, w.guestId, productTemplate.Id, model3d.Id, 0, 0)
		return nil, nil, nil, errors.New("模型设计信息是空的")
	}
	//根据模型id获取尺寸信息
	productSize, err = w.logic.svcCtx.AllModels.FsProductSize.FindOne(w.logic.ctx, *model3d.SizeId)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "找不到对应尺寸", renderImageData.RenderData.ProductId, w.userId, w.guestId, productTemplate.Id, model3d.Id, 0, 0)
			return nil, nil, nil, errors.New("找不到对应尺寸")
		}
		logx.Error(err)
		w.renderErrResponse(renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "获取对应尺寸失败", renderImageData.RenderData.ProductId, w.userId, w.guestId, productTemplate.Id, model3d.Id, 0, 0)
		return nil, nil, nil, errors.New("获取对应尺寸失败")
	}
	return
}

// 组装数据发送给unity
func (w *wsConnectItem) assembleRenderDataToUnity(taskId string, resolution int, 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")
			w.renderErrResponse(info.RequestId, info.RenderData.TemplateTag, taskId, "解析云渲染设置,把Refletion字段值从字符串转数字失败", info.RenderData.ProductId, 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.RequestId, info.RenderData.TemplateTag, taskId, "解析云渲染设置字段 mode 为map对象失败", info.RenderData.ProductId, 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,
		},
	}
	//发送运行阶段消息(组装数据)
	w.sendAssembleRenderDataStepResponseMessage(info.RequestId)
	temId := websocket_data.ToUnityIdStruct{
		TaskId:          taskId,
		Wid:             w.wid,
		RequestId:       info.RequestId,
		RenderBeginTime: time.Now().UTC().UnixMilli(),
		TemplateTag:     info.RenderData.TemplateTag,
		UserId:          w.userId,
		GuestId:         w.guestId,
	}
	temIdBytes, _ := json.Marshal(temId)
	sendData := map[string]interface{}{
		"id":               string(temIdBytes),
		"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":      "product list",
		"priority":    1,
		"create_at":   time.Now().UTC(),
		"resolution":  resolution,
		"render_data": sendData,
	}
	postDataBytes, _ := json.Marshal(postData)
	beginPostTime := time.Now().UTC().UnixMilli()
	_, err = curl.ApiCall(url, "POST", header, bytes.NewReader(postDataBytes), time.Second*10)
	if err != nil {
		w.renderErrResponse(info.RequestId, info.RenderData.TemplateTag, taskId, "请求unity接口失败", info.RenderData.ProductId, w.userId, w.guestId, productTemplate.Id, model3dInfo.Id, productSize.Id, *productTemplate.ElementModelId)
		logx.Error("failed to send data to unity")
		return err
	}
	logx.Info(fmt.Sprintf("发送unity post数据耗时:%dms", time.Now().UTC().UnixMilli()-beginPostTime))
	//发送运行阶段消息
	w.sendRenderDataToUnityStepResponseMessage(info.RequestId)
	logx.Info("发送到unity成功,刀版图:", combineImage /*, "  请求unity的数据:", string(postDataBytes)*/)
	return nil
}

// 组装渲染任务id
func (w *wsConnectItem) genRenderTaskId(combineImage string, 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,
		"combine_image":  combineImage,
	}
	return hash.JsonHashKey(hashMap)
}