package services

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/PuerkitoBio/goquery"
	"hongze/hongze_yb/global"
	"hongze/hongze_yb/models/tables/yb_poster_config"
	"hongze/hongze_yb/models/tables/yb_poster_resource"
	"hongze/hongze_yb/models/tables/yb_resource"
	"hongze/hongze_yb/models/tables/yb_suncode_pars"
	"hongze/hongze_yb/services/alarm_msg"
	"hongze/hongze_yb/services/wx_app"
	"hongze/hongze_yb/utils"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"strings"
	"time"
)

var (
	ServerUrl                    = "http://127.0.0.1:5008/"
	VoiceBroadcastShareImgSource = "voice_broadcast_share_img"
)

// SharePosterReq 分享海报请求体
type SharePosterReq struct {
	CodePage  string `json:"code_page" description:"太阳码page"`
	CodeScene string `json:"code_scene" description:"太阳码scene"`
	Source    string `json:"source" description:"来源"`
	Version   string `json:"version" description:"海报版本号" `
	Pars      string `json:"pars" description:"海报动态信息"`
}

type Html2ImgResp struct {
	Code int    `json:"code"`
	Msg  string `json:"msg"`
	Data string `json:"data"`
}

// postHtml2Img 请求htm2img接口
func postHtml2Img(param map[string]interface{}) (resp *Html2ImgResp, err error) {
	// 目前仅此处调用该接口,暂不加授权、校验等
	postUrl := ServerUrl + "htm2img"
	postData, err := json.Marshal(param)
	if err != nil {
		return
	}
	result, err := Html2ImgHttpPost(postUrl, string(postData), "application/json")
	if err != nil {
		return
	}
	if err = json.Unmarshal(result, &resp); err != nil {
		return
	}
	return resp, nil
}

// Html2ImgHttpPost post请求
func Html2ImgHttpPost(url, postData string, params ...string) ([]byte, error) {
	body := ioutil.NopCloser(strings.NewReader(postData))
	client := &http.Client{}
	req, err := http.NewRequest("POST", url, body)
	if err != nil {
		return nil, err
	}
	contentType := "application/x-www-form-urlencoded;charset=utf-8"
	if len(params) > 0 && params[0] != "" {
		contentType = params[0]
	}
	req.Header.Set("Content-Type", contentType)
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	fmt.Println("HttpPost:" + string(b))
	return b, err
}

// CreateAndUploadSunCode 生成太阳码并上传OSS
func CreateAndUploadSunCode(page, scene, version string) (imgUrl string, err error) {
	if page == "" {
		err = errors.New("page不能为空")
		return
	}
	path := fmt.Sprint(page, "?", scene)
	exist, err := yb_poster_resource.GetPosterByCondition(path, "qrcode", version)
	if err != nil && err != utils.ErrNoRow {
		return
	}
	if exist != nil && exist.ImgURL != "" {
		return exist.ImgURL, nil
	}
	// scene超过32位会生成失败,md5处理至32位
	sceneMD5 := "a=1"
	if scene != "" {
		sceneMD5 = utils.MD5(scene)
	}
	picByte, err := wx_app.GetSunCode(page, sceneMD5)
	if err != nil {
		return
	}
	// 生成图片
	localPath := "./static/img"
	fileName := utils.GetRandStringNoSpecialChar(28) + ".png"
	fpath := fmt.Sprint(localPath, "/", fileName)
	f, err := os.Create(fpath)
	if err != nil {
		return
	}
	if _, err = f.Write(picByte); err != nil {
		return
	}
	defer func() {
		f.Close()
		os.Remove(fpath)
	}()
	// 上传OSS
	fileDir := "images/yb/suncode/"
	imgUrl, err = UploadAliyunToDir(fileName, fpath, fileDir)
	if err != nil {
		return
	}
	// 记录二维码信息
	newPoster := &yb_poster_resource.YbPosterResource{
		Path:       path,
		ImgURL:     imgUrl,
		Type:       "qrcode",
		Version:    version,
		CreateTime: time.Now(),
	}
	err = newPoster.Create()
	if err != nil {
		return
	}
	// 记录参数md5
	if scene != "" {
		newPars := &yb_suncode_pars.YbSuncodePars{
			Scene:      scene,
			SceneKey:   sceneMD5,
			CreateTime: time.Now(),
		}
		err = newPars.Create()
	}

	// 记录文件
	go func() {
		re := new(yb_resource.YbResource)
		re.ResourceUrl = imgUrl
		re.ResourceType = yb_resource.ResourceTypeImg
		re.CreateTime = time.Now().Local()
		if e := re.Create(); e != nil {
			return
		}
	}()
	return
}

// CreatePosterFromSourceV2 生成分享海报(通过配置获取相关信息)
func CreatePosterFromSourceV2(codePage, codeScene, source, version, pars string) (imgUrl string, err error) {
	var errMsg string
	defer func() {
		if err != nil {
			global.LOG.Critical(fmt.Sprintf("CreatePosterFromSource: source=%s, pars:%s, errMsg:%s", source, pars, errMsg))
			reqSlice := make([]string, 0)
			reqSlice = append(reqSlice, fmt.Sprint("CodePage:", codePage, "\n"))
			reqSlice = append(reqSlice, fmt.Sprint("CodeScene:", codeScene, "\n"))
			reqSlice = append(reqSlice, fmt.Sprint("Source:", source, "\n"))
			reqSlice = append(reqSlice, fmt.Sprint("Version:", version, "\n"))
			reqSlice = append(reqSlice, fmt.Sprint("Pars:", pars, "\n"))
			go alarm_msg.SendAlarmMsg("CreatePosterFromSource生成分享海报失败, Msg:"+errMsg+";Err:"+err.Error()+"\n;Req:\n"+strings.Join(reqSlice, ";"), 3)
		}
	}()
	if codePage == "" || source == "" || pars == "" {
		errMsg = "参数有误"
		err = errors.New(errMsg)
		return
	}
	path := fmt.Sprint(codePage, "?", codeScene)
	// 非列表来源获取历史图片,无则生成
	if !strings.Contains(source, "list") && source != "price_driven" {
		poster, tmpErr := yb_poster_resource.GetPosterByCondition(path, "poster", version)
		if tmpErr != nil && tmpErr != utils.ErrNoRow {
			err = tmpErr
			return
		}
		if poster != nil && poster.ImgURL != "" {
			imgUrl = poster.ImgURL
			return
		}
	}
	ybPosterConfig, err := yb_poster_config.GetBySource(source)
	if err != nil {
		return
	}

	width := ybPosterConfig.Width
	height := ybPosterConfig.Hight
	//生成太阳码
	sunCodeUrl, err := CreateAndUploadSunCode(codePage, codeScene, version)
	if err != nil {
		return
	}
	//sunCodeUrl := ``
	// 填充html内容
	contentStr, newHeight, err := fillContent2HtmlV2(source, pars, sunCodeUrl, height, *ybPosterConfig)
	if err != nil {
		errMsg = "html内容有误"
		return
	}
	global.LOG.Critical(contentStr)
	//return
	// 请求python服务htm2img
	htm2ImgReq := make(map[string]interface{})
	htm2ImgReq["html_content"] = contentStr
	htm2ImgReq["width"] = width
	htm2ImgReq["height"] = newHeight
	res, err := postHtml2Img(htm2ImgReq)
	if err != nil || res == nil {
		errMsg = "html转图片请求失败"
		return
	}
	if res.Code != 200 {
		errMsg = "html转图片请求失败"
		err = errors.New("html转图片失败: " + res.Msg)
		return
	}
	imgUrl = res.Data
	// 记录海报信息
	newPoster := &yb_poster_resource.YbPosterResource{
		Path:       path,
		ImgURL:     imgUrl,
		Type:       "poster",
		Version:    version,
		CreateTime: time.Now(),
	}
	err = newPoster.Create()
	return
}

// HtmlReplaceConfig html替换配置
type HtmlReplaceConfig struct {
	TemplateStr string `json:"template_str"`
	ReplaceStr  string `json:"replace_str"`
}

// DefaultValueConfig 默认值的配置
type DefaultValueConfig struct {
	Key          string `json:"key"`
	UseOtherKey  string `json:"use_other_key"`
	Value        string `json:"value"`
	ConditionKey string `json:"condition_key"`
}

// fillContent2Html 填充HTML动态内容
func fillContent2HtmlV2(source, pars, sunCodeUrl string, height float64, ybPosterConfig yb_poster_config.YbPosterConfig) (contentStr string, newHeight float64, err error) {
	paramsMap := make(map[string]string)
	if err = json.Unmarshal([]byte(pars), &paramsMap); err != nil {
		return
	}

	//fmt.Println(paramsMap)

	//html替换规则
	htmlReplaceConfigList := make([]HtmlReplaceConfig, 0)
	if err = json.Unmarshal([]byte(ybPosterConfig.HTMLReplaceConfig), &htmlReplaceConfigList); err != nil {
		return
	}

	newHeight = height
	contentStr = ybPosterConfig.HTMLTemplate

	// 默认数据替换
	defaultValueConfigMap := make([]DefaultValueConfig, 0)
	if ybPosterConfig.DefaultValueConfig != `` {
		if err = json.Unmarshal([]byte(ybPosterConfig.DefaultValueConfig), &defaultValueConfigMap); err != nil {
			return
		}
	}
	// 列表的动态内容不完整的用默认内容的填充
	//var emptyTime1, emptyTime2 bool
	conditionKeyValMap := make(map[string]string)
	for _, v := range defaultValueConfigMap {
		if v.ConditionKey == `` {
			continue
		}
		conditionKeyVal, ok := conditionKeyValMap[v.ConditionKey]
		if !ok {
			conditionKeyVal = paramsMap[v.ConditionKey]
			conditionKeyValMap[v.ConditionKey] = conditionKeyVal
		}
		if conditionKeyVal == `` {
			paramsMap[v.Key] = v.Value
			if v.UseOtherKey != `` {
				if tmpVal, ok := paramsMap[v.UseOtherKey]; ok {
					paramsMap[v.Key] = tmpVal
				}
			}

		}
	}

	// 填充指定内容
	switch source {
	case "report_detail": //需要将简介处理下
		reportAbstract := paramsMap["report_abstract"]
		doc, tmpErr := goquery.NewDocumentFromReader(strings.NewReader(reportAbstract))
		if tmpErr != nil {
			err = tmpErr
			return
		}
		abstract := ""
		doc.Find("p").Each(func(i int, s *goquery.Selection) {
			phtml, tmpErr := s.Html()
			if tmpErr != nil {
				err = tmpErr
				return
			}
			st := s.Text()
			if st != "" && st != "<br>" && st != "<br style=\"max-width: 100%;\">" && !strings.Contains(phtml, "iframe") {
				abstract = abstract + "<p>" + phtml + "</p>"
			}
		})
		paramsMap["report_abstract"] = abstract
	case "activity_list":
		bgColorMap := map[string]string{
			"未开始": "#E3B377",
			"进行中": "#3385FF",
			"已结束": "#A2A2A2",
		}
		statusItemMap := map[string]string{
			"未开始": "block",
			"进行中": "none",
			"已结束": "none",
		}
		offlineMap := map[string]string{
			"线上会议": "none",
			"线下沙龙": "block",
		}
		onlineMap := map[string]string{
			"线上会议": "block",
			"线下沙龙": "none",
		}

		listTitle := paramsMap["list_title"]
		status1 := paramsMap["status_1"]
		if status1 != "未开始" {
			newHeight = 1715
		}
		status2 := paramsMap["status_2"]
		paramsMap["list_title"] = "弘则FICC周度电话会安排"
		paramsMap["bg_color_1"] = bgColorMap[status1]
		paramsMap["show_item_1"] = statusItemMap[status1]
		paramsMap["show_offline_1"] = offlineMap[listTitle]
		paramsMap["show_online_1"] = onlineMap[listTitle]

		paramsMap["bg_color_2"] = bgColorMap[status2]
		paramsMap["show_item_2"] = statusItemMap[status2]
		paramsMap["show_offline_2"] = offlineMap[listTitle]
		paramsMap["show_online_2"] = onlineMap[listTitle]

		// 用默认内容填充的活动时间字体颜色调至看不见
		color1 := "#999"
		color2 := "#999"

		if paramsMap["empty_time_1"] == "true" {
			color1 = "#fff"
		}
		if paramsMap["empty_time_2"] == "true" {
			color2 = "#fff"
		}
		paramsMap["time_color_1"] = color1
		paramsMap["time_color_2"] = color2
	}

	contentStr = strings.Replace(contentStr, "{{SUN_CODE}}", sunCodeUrl, 1)

	for _, v := range htmlReplaceConfigList {
		tmpVal, ok := paramsMap[v.ReplaceStr]
		if !ok {
			tmpVal = ``
		}
		contentStr = strings.Replace(contentStr, v.TemplateStr, tmpVal, 1)
	}
	return
}

// GetDynamicShareImg 生成动态分享图
func GetDynamicShareImg(source, pars string) (imgUrl string, err error) {
	if source == "" {
		err = errors.New("图片来源有误")
		return
	}
	imgConfig, e := yb_poster_config.GetBySource(source)
	if e != nil {
		err = errors.New("获取图片配置失败")
		return
	}
	// 填充html内容
	content, newHeight, e := fillContent2HtmlV2(source, pars, "", imgConfig.Hight, *imgConfig)
	if e != nil {
		err = errors.New("html内容有误")
		return
	}
	htm2ImgReq := make(map[string]interface{})
	htm2ImgReq["html_content"] = content
	htm2ImgReq["width"] = imgConfig.Width
	htm2ImgReq["height"] = newHeight
	res, e := postHtml2Img(htm2ImgReq)
	if e != nil || res == nil {
		err = errors.New("html转图片请求失败")
		return
	}
	if res.Code != 200 {
		err = errors.New("html转图片请求失败: " + res.Msg)
		return
	}
	imgUrl = res.Data
	return
}

func ReportToJpeg(reportUrl, filePath string) (err error) {
	pyCode := `
import asyncio
from pyppeteer import launch, errors

async def main():
    try:
        # 启动浏览器
        browser = await launch({
            'executablePath': '%s',
            'headless': True,
            'args': ['--disable-infobars', '--no-sandbox']
        })
        
        # 新建页面
        page = await browser.newPage()
        
        # 设置视口大小
        await page.setViewport({
            'width': 750,
            'height': 1080
        })
        
        # 导航到页面
        await page.goto('%s', {
            'waitUntil': 'networkidle0',
            'timeout': 1000000  # 设置超时时间为 100 秒
        })
        await page.screenshot({
            'path': "%s",
            'fullPage': True,
			'quality':100
        })
        
    except errors.BrowserError as e:
        print('Browser closed unexpectedly:', e)
    except Exception as e:
        print('An error occurred:', e)
    finally:
        # 确保浏览器关闭
        if browser is not None:
            await browser.close()

# 获取当前事件循环
loop = asyncio.get_event_loop()

# 运行事件循环直到main协程完成
try:
    loop.run_until_complete(main())
except Exception as e:
    print('Error during event loop execution:', e)
finally:
    # 关闭事件循环
    loop.close()
`

	pyCode = fmt.Sprintf(pyCode, global.CONFIG.System.ChromePath, reportUrl, filePath)
	global.LOG.Info("jpeg pyCode: \n" + pyCode)
	cmd := exec.Command("python3", "-c", pyCode)

	output, e := cmd.CombinedOutput()
	if e != nil {
		err = e
		global.LOG.Info("ReportToJpeg failed: , error: \n" + err.Error())
		global.LOG.Info("Output: %s\n", string(output))
		go alarm_msg.SendAlarmMsg("ReportToJpeg failed:"+err.Error(), 3)
		go alarm_msg.SendAlarmMsg("Output :"+string(output), 3)
	}
	defer func() {
		cmd.Process.Kill()
	}()
	return
}