package services

import (
	"encoding/json"
	"errors"
	"eta/eta_api/models"
	"eta/eta_api/models/data_manage/excel"
	"eta/eta_api/models/ppt_english"
	"eta/eta_api/models/system"
	"eta/eta_api/services/alarm_msg"
	"eta/eta_api/services/ppt2img"
	"eta/eta_api/utils"
	"fmt"
	"html"
	"sort"
	"strings"
	"time"
)

const (
	ElementsTypeText  = "text"
	ElementsTypeImage = "image"
	ElementsTypeChart = "chart"
	ElementsTypeSheet = "sheet"
)

type PPTContent struct {
	//Id       int                  `json:"id" description:"此处因目录改版类型有int也有string且并没有使用到该字段所以注释掉"`
	Key      int                  `json:"key"`
	ModelId  int                  `json:"modelId"`
	Title    string               `json:"title"`
	Elements []PPTContentElements `json:"elements"`
}

type PPTContentElements struct {
	Type        string `json:"type"`
	Position    int    `json:"position"`
	Content     string `json:"content"`
	RichContent string `json:"richContent"`
	ChartId     string `json:"chartId"`
	SheetId     string `json:"sheetId"`
	SheetHeight string `json:"sheetHeight"`
	Src         string `json:"src"`
	Uid         string `json:"uid"`
}

// SavePPTReport 保存PPT报告
func SavePPTReport(pptId, classifyId int, title string, adminInfo *system.Admin) (reportId int, reportCode, errMsg string, err error) {
	defer func() {
		if err != nil {
			utils.FileLog.Info("%s", err.Error())
			go alarm_msg.SendAlarmMsg("PPT转报告失败, SavePPTReport Msg: "+errMsg+", Err: "+err.Error(), 3)
		}
	}()
	if pptId == 0 {
		errMsg = "参数有误"
		err = errors.New("参数有误")
		return
	}
	item, e := models.GetPptV2ById(pptId)
	if e != nil {
		errMsg = "获取PPT失败"
		err = errors.New("获取PPT失败, Err: " + e.Error())
		return
	}
	// PPT内容转HTML
	htm, e := pptContent2Html(item.Content, false)
	if e != nil {
		errMsg = "转换失败"
		err = e
		return
	}

	// 2023-02-21 PPT可多次转为报告, 不做关联
	if classifyId == 0 {
		errMsg = "请选择报告类型"
		err = errors.New("请选择报告类型")
		return
	}
	if title == "" {
		errMsg = "标题不能为空"
		err = errors.New("标题不能为空")
		return
	}
	// 获取分类及父级分类
	classifyList, e := models.GetAllClassify()
	if e != nil {
		errMsg = "转换失败"
		err = errors.New("获取分类列表失败, Err: " + e.Error())
		return
	}
	classifyMap := make(map[int]*models.Classify, 0)
	for _, v := range classifyList {
		classifyMap[v.Id] = v
	}
	classifyIdFirst := 0
	classifyIdSecond := 0
	classifyIdThird := 0
	classifyNameFirst := ""
	classifyNameSecond := ""
	classifyNameThird := ""

	// 最小单元分类,第二级别的分类 ,最大的分类
	var baseClassify, twoClassify, threeClassify *models.Classify

	var hasTwo, hasThird bool
	baseClassify, ok := classifyMap[classifyId]
	if !ok {
		errMsg = "分类异常"
		err = errors.New("获取分类失败 ")
		return
	}
	twoClassify, hasTwo = classifyMap[baseClassify.ParentId]
	if hasTwo {
		threeClassify, hasThird = classifyMap[twoClassify.ParentId]
	}

	if hasThird { // 如果确实是有三级分类
		classifyIdFirst = threeClassify.Id
		classifyNameFirst = threeClassify.ClassifyName

		classifyIdSecond = twoClassify.Id
		classifyNameSecond = twoClassify.ClassifyName

		classifyIdThird = baseClassify.Id
		classifyNameThird = baseClassify.ClassifyName
	} else if hasTwo {
		classifyIdFirst = twoClassify.Id
		classifyNameFirst = twoClassify.ClassifyName

		classifyIdSecond = baseClassify.Id
		classifyNameSecond = baseClassify.ClassifyName

	} else {
		classifyIdFirst = baseClassify.Id
		classifyNameFirst = baseClassify.ClassifyName
	}

	// 新增报告
	nowTime := time.Now().Local()
	reportReq := &models.AddReq{
		AddType:            1,
		ClassifyIdFirst:    classifyIdFirst,
		ClassifyNameFirst:  classifyNameFirst,
		ClassifyIdSecond:   classifyIdSecond,
		ClassifyNameSecond: classifyNameSecond,
		ClassifyIdThird:    classifyIdThird,
		ClassifyNameThird:  classifyNameThird,
		Title:              title,
		Abstract:           "",
		Author:             "",
		Frequency:          utils.ReportFrequencyDefault,
		State:              1,
		Content:            htm,
		CreateTime:         nowTime.Format(utils.FormatDateTime),
		ReportVersion:      2,
		CollaborateType:    1, // 协作方式,1:个人,2:多人协作。默认:1
		ReportLayout:       1, // 报告布局,1:常规布局,2:智能布局。默认:1
		IsPublicPublish:    1, // 是否公开发布,1:是,2:否
	}

	// 如果PPT是公开的,则报告也公开发布
	if item.IsShare == 1 {
		reportReq.IsPublicPublish = 1
	}

	newReportInfo, _, e := CreateNewReport(*reportReq, adminInfo)
	if e != nil {
		errMsg = "转换失败"
		err = errors.New("新增报告失败, Err: " + e.Error())
		return
	}
	reportId = newReportInfo.Id
	reportCode = newReportInfo.ReportCode

	// ppt转报告后,将ppt的表格关系做处理
	handlerPptToReportTableReferenced(pptId, newReportInfo.Id)

	// 更新报告中的ppt图片
	go saveReportPptImg(pptId, reportId, item.PptxUrl)

	return
}

// PPT2内容转HTML
func pptContent2Html(content string, isEnglish bool) (htm string, err error) {
	contents := make([]PPTContent, 0)
	if e := json.Unmarshal([]byte(content), &contents); e != nil {
		err = errors.New("PPT内容转换失败")
		return
	}
	pageLen := len(contents)
	htmlContent := ``
	// iframe图表/表格域名

	// 获取基础配置, 若未配置则直接返回

	// 获取配置好的短信模版
	smsCond := ` AND conf_key = ? `
	smsPars := make([]interface{}, 0)
	smsPars = append(smsPars, "ChartViewUrl")
	conf := new(models.BusinessConf)
	conf, e := conf.GetItemByCondition(smsCond, smsPars)
	if e != nil {
		if utils.IsErrNoRow(e) {
			err = fmt.Errorf("请先配置公共图库的地址")
			return
		}
		err = fmt.Errorf("获取聚合短信配置信息失败, Err: %s", e.Error())
		return
	}
	if conf.ConfVal == "" {
		err = fmt.Errorf("请先配置公共图库的地址")
		return
	}

	chartRoot := conf.ConfVal
	if pageLen > 0 {
		htmlPrefix := `<p style="text-align: left; margin-top: 10px; font-size: 16px;">`
		htmlSuffix := `</p>`
		htmlBr := `<br>`
		for i := 0; i < pageLen; i++ {
			// 每页标题加粗居中
			title := contents[i].Title
			if title != "" {
				htmlContent += `<p style="font-size: 16px; text-align: left;"><strong>`
				htmlContent += title
				htmlContent += `</strong></p>`
			}
			ele := contents[i].Elements
			// 每页元素按照Position升序排序
			sort.Slice(ele, func(k, j int) bool {
				return ele[k].Position < ele[j].Position
			})
			for _, v := range ele {
				// 根据不同的Type拼接不同的内容
				htmlContent += htmlPrefix
				switch v.Type {
				case ElementsTypeText:
					htmlContent += v.RichContent
				case ElementsTypeImage:
					htmlContent += fmt.Sprint(`<img src="`, v.Src, `" class="fr-fic fr-dib fr-draggable">`)
				case ElementsTypeChart:
					if isEnglish {
						// 英文研报图表src多加一个fromPage=en, 表格暂时没有区分
						if strings.HasPrefix(v.ChartId, "isETAForumChart_") {
							chartIdInfo := strings.Split(v.ChartId, "_")
							if len(chartIdInfo) == 2 {
								v.ChartId = chartIdInfo[1]
							}
							htmlContent += fmt.Sprintf(`<iframe src="%s/chartshow?code=%s&fromPage=en&isETAForumChart=true&uid=%s" width="100%%" height="350" style="border-width:0px; min-height:350px;" class="iframe%s"></iframe>`, chartRoot, v.ChartId, v.Uid, v.Uid)
							break
						}
						htmlContent += fmt.Sprintf(`<iframe src="%s/chartshow?code=%s&fromPage=en&uid=%s" width="100%%" height="350" style="border-width:0px; min-height:350px;" class="iframe%s"></iframe>`, chartRoot, v.ChartId, v.Uid, v.Uid)
						break
					}
					if strings.HasPrefix(v.ChartId, "isETAForumChart_") {
						chartIdInfo := strings.Split(v.ChartId, "_")
						if len(chartIdInfo) == 2 {
							v.ChartId = chartIdInfo[1]
						}
						htmlContent += fmt.Sprintf(`<iframe src="%s/chartshow?code=%s&isETAForumChart=true&uid=%s" width="100%%" height="350" style="border-width:0px; min-height:350px;" class="iframe%s"></iframe>`, chartRoot, v.ChartId, v.Uid, v.Uid)
						break
					}
					htmlContent += fmt.Sprintf(`<iframe src="%s/chartshow?code=%s&uid=%s" width="100%%" height="350" style="border-width:0px; min-height:350px;" class="iframe%s"></iframe>`, chartRoot, v.ChartId, v.Uid, v.Uid)
				case ElementsTypeSheet:
					htmlContent += fmt.Sprintf(`<iframe src="%s/sheetshow?code=%s&uid=%s" class="iframe%s" width="100%%" height="%s" style="border-width:0px;"></iframe>`, chartRoot, v.SheetId, v.Uid, v.Uid, v.SheetHeight)
				}
				htmlContent += htmlSuffix
			}
			// 每页中间插入一个换行符, 最后一页不插入
			currentPage := i + 1
			if currentPage != pageLen {
				htmlContent += htmlPrefix + htmlBr + htmlSuffix
			}
		}
	}
	htm = htmlContent
	return
}

// ResetPPTReport 重置PPT关联的报告(如删除关联的报告后)
func ResetPPTReport(reportId int, isEnglish bool) (err error) {
	defer func() {
		if err != nil {
			utils.FileLog.Info("%s", err.Error())
			go alarm_msg.SendAlarmMsg("重置PPT关联报告失败, ResetPPTReport Err: "+err.Error(), 3)
		}
	}()

	// 英文报告
	if isEnglish {
		en, e := ppt_english.GetPptEnglishByReportId(reportId)
		if e != nil && !utils.IsErrNoRow(e) {
			err = errors.New("获取英文PPT失败, Err: " + e.Error())
			return
		}
		if en != nil && en.PptId > 0 {
			updateCols := []string{"ReportId", "ReportCode"}
			en.ReportId = 0
			en.ReportCode = ""
			if e = en.Update(updateCols); e != nil {
				err = errors.New("更新英文PPT关联报告失败, Err: " + e.Error())
				return
			}
		}
		return
	}

	// 中文报告
	item, e := models.GetPptV2ByReportId(reportId)
	if e != nil && !utils.IsErrNoRow(e) {
		err = errors.New("获取PPT失败, Err: " + e.Error())
		return
	}
	if item != nil && item.PptId > 0 {
		updateCols := []string{"ReportId", "ReportCode"}
		item.ReportId = 0
		item.ReportCode = ""
		if e = item.Update(updateCols); e != nil {
			err = errors.New("更新PPT关联报告失败, Err: " + e.Error())
			return
		}
	}
	return
}

// saveReportPptImg ppt转报告后,需要再次将ppt转图片
func saveReportPptImg(pptId, reportId int, pptUrl string) {
	var err error
	defer func() {
		if err != nil {
			utils.FileLog.Info(fmt.Sprintf("将ppt转图片失败, saveReportPptImg Err:%s", err.Error()))
			go alarm_msg.SendAlarmMsg("将ppt转图片失败, saveReportPptImg Err: "+err.Error(), 3)
		}
	}()

	// 更新报告内容
	report, e := models.GetReportByReportId(reportId)
	if e != nil && !utils.IsErrNoRow(e) {
		err = errors.New("获取报告失败, Err: " + e.Error())
		return
	}
	if report == nil {
		return
	}
	// 获取ppt转图片的结果
	list, err := ppt2img.Ppt2Img(pptUrl)
	if err != nil {
		return
	}

	reportPptImgList := make([]*models.ReportPptImg, 0)
	for _, v := range list {
		reportPptImg := &models.ReportPptImg{
			//ReportPptImgId:  0,
			PptId:           pptId,
			ReportId:        reportId,
			ReportChapterId: 0,
			ImgUrl:          v,
			CreateTime:      time.Now(),
		}
		reportPptImgList = append(reportPptImgList, reportPptImg)
	}

	//批量添加Ppt转报告的图片记录
	err = models.AddAndEditMultiReportPptImg(pptId, reportPptImgList)
	return
}

// SaveEnglishPPTReport 保存英文PPT报告
func SaveEnglishPPTReport(pptId, classifyIdFirst, classifyIdSecond int, title, abstract string, adminInfo *system.Admin) (reportId int, reportCode, errMsg string, err error) {
	defer func() {
		if err != nil {
			utils.FileLog.Info("%s", err.Error())
			go alarm_msg.SendAlarmMsg("PPT转报告失败, SavePPTReport Msg: "+errMsg+", Err: "+err.Error(), 3)
		}
	}()
	if pptId == 0 {
		errMsg = "参数有误"
		err = errors.New("参数有误")
		return
	}
	item, e := ppt_english.GetPptEnglishById(pptId)
	if e != nil {
		errMsg = "获取PPT失败"
		err = errors.New("获取PPT失败, Err: " + e.Error())
		return
	}
	// PPT内容转HTML
	htm, e := pptContent2Html(item.Content, true)
	if e != nil {
		errMsg = "转换失败"
		err = e
		return
	}

	// 2023-02-21 PPT可多次转为报告, 不做关联
	if title == "" {
		errMsg = "标题不能为空"
		err = errors.New("标题不能为空")
		return
	}
	if classifyIdFirst <= 0 {
		errMsg = "请选择报告分类"
		err = errors.New("报告分类不能为空")
		return
	}

	// 分类
	classifyList, e := models.GetAllEnglishClassify()
	if e != nil {
		errMsg = "转换失败"
		err = errors.New("获取分类列表失败, Err: " + e.Error())
		return
	}
	classifyMap := make(map[int]string, 0)
	for _, v := range classifyList {
		classifyMap[v.Id] = v.ClassifyName
	}
	classifyNameFirst := classifyMap[classifyIdFirst]
	classifyNameSecond := classifyMap[classifyIdSecond]

	// 新增报告
	nowTime := time.Now().Local()
	reportReq := &models.AddEnglishReportReq{
		AddType:            1,
		ClassifyIdFirst:    classifyIdFirst,
		ClassifyNameFirst:  classifyNameFirst,
		ClassifyIdSecond:   classifyIdSecond,
		ClassifyNameSecond: classifyNameSecond,
		Title:              title,
		Abstract:           abstract,
		Author:             "Horizon Insights FICC Team",
		Frequency:          utils.ReportFrequencyDefault,
		State:              1,
		Content:            htm,
		CreateTime:         nowTime.Format(utils.FormatDateTime),
	}
	newReportId, newCode, e := CreateNewEnglishReport(*reportReq, adminInfo)
	if e != nil {
		errMsg = "转换失败"
		err = errors.New("新增报告失败, Err: " + e.Error())
		return
	}
	reportId = int(newReportId)
	reportCode = newCode

	// 英文ppt转英文报告后,将英文报告的表格关系做处理
	handlerPptToEnReportTableReferenced(pptId, reportId)

	return
}

// UpdatePptEditing 更新PPT编辑状态
func UpdatePptEditing(pptId, status, userId int, userName string, isEn bool) (ret ppt_english.PPTEditingCache, err error) {
	if pptId <= 0 {
		return
	}
	cacheKey := ""
	if isEn {
		cacheKey = fmt.Sprint(utils.CACHE_EN_PPT_EDITING, pptId)
	} else {
		cacheKey = fmt.Sprint(utils.CACHE_PPT_EDITING, pptId)
	}

	// 完成编辑
	if status == 2 {
		_ = utils.Rc.Delete(cacheKey)
		return
	}

	// 读取缓存中的结果
	var editor ppt_english.PPTEditingCache
	strCache, _ := utils.Rc.RedisString(cacheKey)
	fmt.Println(strCache)
	if strCache != "" {
		e := json.Unmarshal([]byte(strCache), &editor)
		if e != nil {
			err = fmt.Errorf("解析缓存内容失败: %s", e.Error())
			return
		}
	}

	// 标记编辑中
	if status == 1 {
		// 无人编辑, 写入缓存
		if !editor.IsEditing {
			ret.IsEditing = true
			ret.AdminId = userId
			ret.Editor = userName
			ret.Tips = fmt.Sprintf("当前%s正在编辑PPT", userName)
			b, _ := json.Marshal(ret)
			utils.Rc.SetNX(cacheKey, string(b), utils.ReportPptEditingWait*time.Second)
			return
		}

		// 有人编辑
		if editor.IsEditing {
			// 编辑用户与当前用户不一致, 返回编辑用户, 一致则更新缓存
			if userId == editor.AdminId {
				b, _ := json.Marshal(editor)
				utils.Rc.Do("SETEX", cacheKey, int64(utils.ReportPptEditingWait), string(b))
			}
			ret = editor
			return
		}
	} else {
		// 默认查询
		ret = editor
	}
	return
}

// handlerPptToReportTableReferenced
// @Description: ppt转报告后,需要同时继承原来ppt中表格的拖动数据逻辑
// @author: Roc
// @datetime 2025-01-09 16:51:11
// @param pptId int
// @param reportId int
func handlerPptToReportTableReferenced(pptId, reportId int) {
	var err error
	defer func() {
		if err != nil {
			utils.FileLog.Error("ppt转报告后,报告与动态表格关联处理失败,PPT的ID:%d,新报告ID:%d,Err:%s", pptId, reportId, err.Error())
		}
	}()
	reportInfo, err := models.GetReportByReportId(reportId)
	if err != nil {
		return
	}

	newFromScene := utils.TableReferencedByReport
	addList, err := excel.CopyReferencedExcelConfigByReferencedIdAndFromScene(pptId, utils.TableReferencedByPPT, reportInfo.Id, newFromScene, reportInfo.AdminId, reportInfo.AdminRealName)
	if err != nil {
		return
	}

	if len(addList) > 0 {
		// 修改内容
		content := HandleReportContentTableAndScene(reportInfo.Id, newFromScene, html.UnescapeString(reportInfo.Content))
		reportInfo.Content = html.EscapeString(content)
		reportInfo.ContentStruct = HandleReportContentStructTableAndScene(reportInfo.Id, newFromScene, reportInfo.ContentStruct)
		err = reportInfo.Update([]string{"Content", "ContentStruct"})
		if err != nil {
			return
		}

		if reportInfo.HasChapter == 1 {
			chapterList, tmpErr := models.GetChapterListByReportId(reportInfo.Id)
			if tmpErr != nil {
				err = tmpErr
				return
			}
			for _, v := range chapterList {
				chapterContent := HandleReportContentTableAndScene(reportInfo.Id, newFromScene, html.UnescapeString(v.Content))
				v.Content = html.EscapeString(chapterContent)
				v.ContentStruct = HandleReportContentStructTableAndScene(reportInfo.Id, newFromScene, v.ContentStruct)
				err = v.Update([]string{"Content", "ContentStruct"})
				if err != nil {
					return
				}
			}
		}
	}

}

// HandlerPptToEnPptTableReferenced
// @Description: ppt转英文ppt后,需要同时继承原来ppt中表格的拖动数据逻辑
// @author: Roc
// @datetime 2025-01-09 16:51:03
// @param pptId int
// @param enPptId int
// @param sysUserId int
// @param sysUserName string
func HandlerPptToEnPptTableReferenced(pptId, enPptId, sysUserId int, sysUserName string) {
	var err error
	defer func() {
		if err != nil {
			utils.FileLog.Error("ppt转英文PPT后,英文PPT与动态表格关联处理失败,PPT的ID:%d,英文ppt的ID:%d,Err:%s", pptId, enPptId, err.Error())
		}
	}()

	_, err = excel.CopyReferencedExcelConfigByReferencedIdAndFromScene(pptId, utils.TableReferencedByPPT, enPptId, utils.TableReferencedByEnPPT, sysUserId, sysUserName)
	if err != nil {
		return
	}

}

// HandlerMergePptTableReferenced
// @Description: 多ppt合并生成新的ppt,需要同时继承原来所有ppt中表格的拖动数据逻辑
// @author: Roc
// @datetime 2025-01-09 16:51:03
// @param pptId int
// @param enPptId int
// @param sysUserId int
// @param sysUserName string
// @param fromScene int
func HandlerMergePptTableReferenced(oldPptIdList []int, pptId, sysUserId int, sysUserName string, fromScene int) {
	var err error
	defer func() {
		if err != nil {
			utils.FileLog.Error("ppt转英文PPT后,英文PPT与动态表格关联处理失败,待合并的PPT的ID:%v,英文ppt的ID:%d,Err:%s", oldPptIdList, pptId, err.Error())
		}
	}()

	_, err = excel.CopyReferencedExcelConfigByReferencedIdListAndFromScene(oldPptIdList, fromScene, pptId, fromScene, sysUserId, sysUserName)
	if err != nil {
		return
	}

}

// handlerPptToEnReportTableReferenced
// @Description: 英文ppt转报告后,需要同时继承原来英文ppt中表格的拖动数据逻辑
// @author: Roc
// @datetime 2025-01-09 17:04:49
// @param enPptId int
// @param enReportId int
func handlerPptToEnReportTableReferenced(enPptId, enReportId int) {
	var err error
	defer func() {
		if err != nil {
			utils.FileLog.Error("ppt转英文报告后,英文报告与动态表格关联处理失败,PPT的ID:%d,英文报告ID:%d,Err:%s", enPptId, enReportId, err.Error())
		}
	}()
	reportInfo, err := models.GetEnglishReportItemById(enReportId)
	if err != nil {
		return
	}

	newFromScene := utils.TableReferencedByEnReport
	addList, err := excel.CopyReferencedExcelConfigByReferencedIdAndFromScene(enPptId, utils.TableReferencedByEnPPT, enReportId, newFromScene, reportInfo.AdminId, reportInfo.AdminRealName)
	if err != nil {
		return
	}

	if len(addList) > 0 {
		// 修改内容
		content := HandleReportContentTableAndScene(reportInfo.Id, newFromScene, html.UnescapeString(reportInfo.Content))
		reportInfo.Content = html.EscapeString(content)
		err = reportInfo.Update([]string{"Content"})
		if err != nil {
			return
		}
	}

}