package logic

//处理websocket云渲染任务数据
import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"fusenapi/constants"
	websocket_data2 "fusenapi/server/websocket/internal/websocket_data"
	"fusenapi/service/repositories"
	"fusenapi/utils/curl"
	"fusenapi/utils/hash"
	"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添加
	TaskId               string     //map的key
	RenderId             string     // map的val(增加任务时候传)
	RenderNotifyImageUrl string     //渲染回调数据(删除任务时候传)
	TaskProperty         renderTask //渲染任务的属性
}

// 渲染任务属性
type renderTask struct {
	RenderId             string //渲染id(前端传的)
	CombineBeginTime     int64  //合图开始时间
	CombineEndTime       int64  //合图结束时间
	UnityRenderBeginTime int64  //发送给unity时间
	UnityRenderEndTime   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() {
	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_data2.RenderImageReqMsg
	if err := json.Unmarshal(data, &renderImageData); err != nil {
		w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_ERR_DATA_FORMAT, "invalid format of websocket render image message:"+string(data)))
		logx.Error("invalid format of websocket render image message", err)
		return
	}
	if renderImageData.RenderId == "" {
		w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_ERR_DATA_FORMAT, "invalid format of websocket render image message:render_id is empty"))
		logx.Error("invalid format of websocket render image message:render_id is empty")
		return
	}
	if renderImageData.RenderData.ProductId <= 0 {
		w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_ERR_DATA_FORMAT, "invalid format of websocket render image message:product_id "))
		logx.Error("invalid format of websocket render image message:product_id")
		return
	}
	if renderImageData.RenderData.TemplateTag == "" {
		w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_ERR_DATA_FORMAT, "invalid format of websocket render image message:template_tag "))
		logx.Error("invalid format of websocket render image message:template_tag")
		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.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_ERR_DATA_FORMAT, "failed to get user logo"))
			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.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_ERR_DATA_FORMAT, "default logo is not exists"))
				return
			}
			w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_ERR_DATA_FORMAT, "failed to get default logo"))
			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

	//生成任务id(需要把user_id,guest_id设为0)
	hashVal := renderImageData.RenderData
	hashVal.UserId = 0
	hashVal.GuestId = 0
	hashByte, _ := json.Marshal(hashVal)
	var hashData map[string]interface{}
	_ = json.Unmarshal(hashByte, &hashData)
	taskId := hash.JsonHashKey(hashData)
	//查询有没有缓存的资源,有就返回######################
	resource, err := w.logic.svcCtx.AllModels.FsResource.FindOneById(w.logic.ctx, taskId)
	if err != nil {
		if !errors.Is(err, gorm.ErrRecordNotFound) {
			w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE_ERR, fmt.Sprintf("获取云渲染缓存资源错误 task_id:%s ", taskId)))
			logx.Error("failed to find render resource:", err)
			return
		}
	} else {
		//返回给客户端
		b := w.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE, websocket_data2.RenderImageRspMsg{
			RenderId:             renderImageData.RenderId,
			Image:                *resource.ResourceUrl,
			CombineTakesTime:     "耗时0秒(缓存)",
			UnityRenderTakesTime: "耗时0秒(缓存)",
		})
		//发送数据到out chan
		w.sendToOutChan(b)
		return
	}
	//###########################################
	//把需要渲染的图片任务加进去
	w.createRenderTask(renderImageControlChanItem{
		Option:   1, //0删除 1添加 2修改耗时属性
		TaskId:   taskId,
		RenderId: renderImageData.RenderId,
	})
	//组装数据
	if err = w.assembleRenderData(taskId, renderImageData); err != nil {
		logx.Error("组装数据失败:", err)
		return
	}
}

// 组装数据发送给unity
func (w *wsConnectItem) assembleRenderData(taskId string, info websocket_data2.RenderImageReqMsg) error {
	//获取产品第一个尺寸
	productFirstSize, err := w.logic.svcCtx.AllModels.FsProductSize.GetProductFirstSize(w.logic.ctx, info.RenderData.ProductId)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE_ERR, fmt.Sprintf("没有尺寸 product_id:%d  ", info.RenderData.ProductId)))
			logx.Error("product first size  is not found")
			return err
		}
		w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE_ERR, fmt.Sprintf("获取尺寸错误 product_id:%d  ", info.RenderData.ProductId)))
		logx.Error("failed to get product first size:", err)
		return err
	}
	//获取模型(只是获取id)
	model3dInfo, err := w.logic.svcCtx.AllModels.FsProductModel3d.GetOneBySizeIdTag(w.logic.ctx, productFirstSize.Id, constants.TAG_MODEL, "id")
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE_ERR, fmt.Sprintf("没有模型 product_id:%d , size_id:%d ", info.RenderData.ProductId, productFirstSize.Id)))
			logx.Error("product model  is not found")
			return err
		}
		w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE_ERR, fmt.Sprintf("获取模型错误 product_id:%d , size_id:%d ", info.RenderData.ProductId, productFirstSize.Id)))
		logx.Error("failed to get product model:", err)
		return err
	}
	//获取模板
	productTemplate, err := w.logic.svcCtx.AllModels.FsProductTemplateV2.FindFirstOneByProductIdModelIdTemplateTag(w.logic.ctx, info.RenderData.ProductId, model3dInfo.Id, info.RenderData.TemplateTag)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE_ERR, fmt.Sprintf("模板未找到 product_id:%d , model_id:%d ,template_tag:%s", info.RenderData.ProductId, model3dInfo.Id, info.RenderData.TemplateTag)))
			logx.Error("template info is not found")
			return err
		}
		w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE_ERR, fmt.Sprintf("获取模板错误 product_id:%d , model_id:%d ,template_tag:%s", info.RenderData.ProductId, model3dInfo.Id, info.RenderData.TemplateTag)))
		logx.Error("failed to get template info:", err)
		return err
	}
	//记录刀版图合成开始时间
	w.modifyRenderTaskTimeConsuming(renderImageControlChanItem{
		Option: 2,
		TaskId: taskId,
		TaskProperty: renderTask{
			CombineBeginTime: time.Now().UTC().Unix(),
		},
	})
	//获取刀版图
	combineReq := repositories.LogoCombineReq{
		UserId:      info.RenderData.UserId,
		GuestId:     info.RenderData.GuestId,
		TemplateId:  productTemplate.Id,
		TemplateTag: info.RenderData.TemplateTag,
		Website:     info.RenderData.Website,
		Slogan:      info.RenderData.Slogan,
		Address:     info.RenderData.Address,
		Phone:       info.RenderData.Phone,
	}
	res, err := w.logic.svcCtx.Repositories.ImageHandle.LogoCombine(w.logic.ctx, &combineReq)
	if err != nil {
		w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE_ERR, fmt.Sprintf("接口合图错误,产品id:%d,模板id:%d", info.RenderData.ProductId, productTemplate.Id)))
		logx.Error("合成刀版图失败,合成请求数据:", combineReq, "错误信息:", err)
		return err
	}
	combineImage := "" //刀版图
	if res != nil && res.ResourceUrl != nil {
		combineImage = *res.ResourceUrl
	} else {
		w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE_ERR, fmt.Sprintf("接口合图错误,刀版图是空的,产品id:%d", info.RenderData.ProductId)))
		logx.Error("合成刀版图失败,合成的刀版图是空指针:", err)
		return err
	}
	//记录刀版图合成结束时间
	w.modifyRenderTaskTimeConsuming(renderImageControlChanItem{
		Option: 2,
		TaskId: taskId,
		TaskProperty: renderTask{
			CombineEndTime: time.Now().UTC().Unix(),
		},
	})
	logx.Info("合成刀版图成功,合成刀版图数据:", combineReq, ",logo图片:", info.RenderData.Logo, "  刀版图:", *res.ResourceUrl)
	//获取渲染设置信息
	element, err := w.logic.svcCtx.AllModels.FsProductTemplateElement.FindOneByModelId(w.logic.ctx, *productTemplate.ModelId)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE_ERR, fmt.Sprintf("无渲染设置信息,产品id:%d ,模板id:%d,模型id:%d", info.RenderData.ProductId, productTemplate.Id, *productTemplate.ModelId)))
			logx.Error("element info is not found,model_id = ", *productTemplate.ModelId)
			return err
		}
		logx.Error("failed to get element list,", err)
		return err
	}
	//组装数据
	refletion := -1
	if element.Refletion != nil && *element.Refletion != "" {
		refletion, err = strconv.Atoi(*element.Refletion)
		if err != nil {
			logx.Error("err refletion:set default -1")
		}
	}
	//组装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)
			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"
	t := time.Now().UTC()
	postData := map[string]interface{}{
		"group":       "unity3d",
		"source":      "home page",
		"priority":    1,
		"create_at":   t,
		"render_data": sendData,
	}
	postDataBytes, _ := json.Marshal(postData)
	_, err = curl.ApiCall(url, "POST", header, bytes.NewReader(postDataBytes), time.Second*10)
	if err != nil {
		w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE_ERR, fmt.Sprintf("请求unity接口错误,产品id:%d", info.RenderData.ProductId)))
		logx.Error("failed to send data to unity")
		return err
	}
	//记录发送到unity时间
	w.modifyRenderTaskTimeConsuming(renderImageControlChanItem{
		Option: 2,
		TaskId: taskId,
		TaskProperty: renderTask{
			UnityRenderBeginTime: time.Now().UTC().Unix(),
		},
	})
	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) modifyRenderTaskTimeConsuming(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 := ""
					UnityRenderTakesTime := ""
					if taskData.CombineBeginTime > 0 && taskData.CombineEndTime > 0 {
						CombineTakesTime = fmt.Sprintf("耗时%d秒", taskData.CombineEndTime-taskData.CombineBeginTime)
					}
					if taskData.UnityRenderBeginTime > 0 && taskData.UnityRenderEndTime > 0 {
						UnityRenderTakesTime = fmt.Sprintf("耗时%d秒", taskData.UnityRenderEndTime-taskData.UnityRenderBeginTime)
					}
					//发送到出口
					w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE, websocket_data2.RenderImageRspMsg{
						RenderId:             taskData.RenderId,
						Image:                data.RenderNotifyImageUrl,
						CombineTakesTime:     CombineTakesTime,
						UnityRenderTakesTime: UnityRenderTakesTime,
					}))
				}
				//删除任务
				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.CombineBeginTime != 0 {
						taskData.CombineBeginTime = data.TaskProperty.CombineBeginTime
					}
					if data.TaskProperty.CombineEndTime != 0 {
						taskData.CombineEndTime = data.TaskProperty.CombineEndTime
					}
					if data.TaskProperty.UnityRenderBeginTime != 0 {
						taskData.UnityRenderBeginTime = data.TaskProperty.UnityRenderBeginTime
					}
					if data.TaskProperty.UnityRenderEndTime != 0 {
						taskData.UnityRenderEndTime = data.TaskProperty.UnityRenderEndTime
					}
				}
			}
		}
	}
}