package vqdtask

import (
	"bufio"
	"context"
	"easyvqd/internal/conf"
	"easyvqd/internal/core/host"
	"easyvqd/internal/core/vqd"
	"easyvqd/pkg/vqdcms"
	"fmt"
	"log/slog"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
)

type Core struct {
	HostCore    *host.Core
	VqdTaskCore *vqd.Core
	Cfg         *conf.Bootstrap
	ResultCb    vqdcms.VQDResultCB
}

var (
	VqdTaskMap = vqdcms.VqdTaskMap{M: make(map[string]*vqdcms.VQDHandle)}
)

func NewCore(HostCore *host.Core, VqdTaskCore *vqd.Core, Cfg *conf.Bootstrap) *Core {
	core := &Core{
		HostCore:    HostCore,
		VqdTaskCore: VqdTaskCore,
		Cfg:         Cfg,
	}
	core.ResultCb = func(v vqdcms.AbnormalModel) {

		in := &vqd.AddVqdAlarmInput{
			ChannelName:      v.ChannelName,
			TaskTemplateName: v.TemplateName,
			TaskName:         v.TaskName,
			PlanName:         v.PlanName,
			TaskID:           int64(v.ID),
			TaskTemplateID:   int64(v.TemplateID),
			PlanID:           int64(v.PlanID),
			IsDeep:           v.IsDeep,
			FilePath:         v.FilePath,
		}
		var Abnormals vqd.Abnormals
		if len(v.Abnormals) > 0 {
			for _, abnormal := range v.Abnormals {
				item := vqd.Abnormal{
					Value: abnormal.Value,
					Name:  abnormal.Name,
				}
				Abnormals = append(Abnormals, item)
			}
		}
		in.Abnormals = Abnormals

		var DefaultValues vqd.DefaultValues
		if len(v.DefaultValues) > 0 {
			for _, defaultValue := range v.DefaultValues {
				item := vqd.DefaultValue{
					Thr1:  defaultValue.Thr1,
					Name1: defaultValue.Name1,
					Thr2:  defaultValue.Thr2,
					Name2: defaultValue.Name2,
					Ratio: defaultValue.Ratio,
				}
				DefaultValues = append(DefaultValues, item)
			}
		}
		in.DefaultValues = DefaultValues
		_, err := core.VqdTaskCore.AddVqdAlarm(context.TODO(), in)
		if err != nil {
			slog.Error("add alarm", "err", err.Error())
		}
	}
	core.HostCore.CbIFrame = func(s string, data []byte, codes int) {
		if codes == VIDEO_CODEC_H264 {
			v, ok := VqdTaskMap.LoadTaskMap(s)
			if ok {
				v.SendData(data, VIDEO_CODEC_H264)
			}
		} else {
			v, ok := VqdTaskMap.LoadTaskMap(s)
			if ok {
				v.SendData(data, VIDEO_CODEC_H265)
			}
		}

		//slog.Debug("cb IFrame", "name", s, "codes", codes)

	}
	time.AfterFunc(time.Duration(5)*time.Second, func() {
		// 启用诊断分析
		core.InitVqdTask()
	})
	// 启用定时清理任务
	go core.scheduleCleanTask()

	// 启用任务管理器
	return core
}

func (c *Core) InitVqdTask() {
	err := vqdcms.VQDInit()
	if err != nil {
		slog.Error("vqd cms open", "err", err.Error())
		return
	}

	all, _, err := c.VqdTaskCore.FindVqdTaskAll()
	if err == nil {
		for _, vqdTask := range all {
			errs := c.AddTaskVqd(vqdTask.ID)
			if errs != nil {
				slog.Error("vqd init add task", "err", errs.Error())
			}
			time.Sleep(200 * time.Millisecond)
		}
	}

	return
}
func (c *Core) UnVqdTask() {
	VqdTaskMap.DeleteTaskMapAll()
	vqdcms.VQDUnInit()
	return
}
func (c *Core) AddTaskVqd(taskId int) error {

	task, err := c.VqdTaskCore.GetVqdTask(context.TODO(), taskId)
	if err != nil {
		slog.Error("vqd add task find", "err", err.Error())
		return err
	}
	taskTemplate, err := c.VqdTaskCore.GetIDVqdTaskTemplate(context.TODO(), task.TaskTemplateID)
	if err != nil {
		slog.Error("vqd add task find template", "err", err.Error())
		return err
	}
	taskPlan, err := c.VqdTaskCore.GetVqdTimeTemplate(context.TODO(), int(task.TimeTemplateID))
	if err != nil {
		slog.Error("vqd add task find plan", "err", err.Error())
		return err
	}
	chnId := task.ChannelID

	para := vqdcms.NewVQDPara(taskTemplate)
	info := vqdcms.VQDHandleInfo{
		ChannelID:    chnId,
		ChannelName:  task.ChannelName,
		TaskID:       task.ID,
		TaskName:     task.Name,
		TemplateID:   taskTemplate.ID,
		TemplateName: taskTemplate.Name,
		PlanID:       taskPlan.ID,
		PlanName:     taskPlan.Name,
		Plans:        taskPlan.Plans,
	}
	v := vqdcms.NewVQDHandle(c.ResultCb, c.HostCore, info).Create(para, taskPlan.Plans)

	VqdTaskMap.StoreChildMap(fmt.Sprintf("%s", chnId), v)
	return nil
}
func (c *Core) UpdateTaskVqd(taskId int) error {
	task, err := c.VqdTaskCore.GetVqdTask(context.TODO(), taskId)
	if err != nil {
		slog.Error("vqd update task find", "err", err.Error())
		return err
	}
	v, ok := VqdTaskMap.LoadTaskMap(task.ChannelID)
	if ok {
		taskTemplate, err := c.VqdTaskCore.GetIDVqdTaskTemplate(context.TODO(), task.TaskTemplateID)
		if err != nil {
			slog.Error("vqd update task find template", "err", err.Error())
			return err
		}
		taskPlan, err := c.VqdTaskCore.GetVqdTimeTemplate(context.TODO(), int(task.TimeTemplateID))
		if err != nil {
			slog.Error("vqd add task find plan", "err", err.Error())
			return err
		}
		para := vqdcms.NewVQDPara(taskTemplate)
		info := vqdcms.VQDHandleInfo{
			ChannelID:    task.ChannelID,
			ChannelName:  task.ChannelName,
			TaskID:       task.ID,
			TaskName:     task.Name,
			TemplateID:   taskTemplate.ID,
			TemplateName: taskTemplate.Name,
			PlanID:       taskPlan.ID,
			PlanName:     taskPlan.Name,
			Plans:        taskPlan.Plans,
		}
		errs := v.SetVQDConfig(para, info)
		if errs != nil {
			slog.Error("vqd update set config err", "err", errs.Error())
			return err
		}
		VqdTaskMap.StoreChildMap(fmt.Sprintf("%s", task.ChannelID), v)
	}
	return nil

}
func (c *Core) DelTaskVqd(taskId int, chnId string) {
	v, ok := VqdTaskMap.LoadTaskMap(chnId)
	if ok {
		v.Destroy()
	}
	VqdTaskMap.DeleteTaskMap(chnId)
}

// 测试i帧数据是否可以转为图片文件
func CheckIFramesToJpg() {
	dir, _ := os.Getwd()

	h264Path := filepath.Join(dir, "check_tip.h264") // 你的H.264裸流文件路径
	//1. 检查FFmpeg
	ok, err := CheckFFmpeg()
	if !ok {
		fmt.Println("错误:", err)
		os.Exit(1)
	}

	// 2. 配置参数（根据你的H.264文件调整）

	imageFormat := "jpg" // 输出图片格式
	videoWidth := 0      // 分辨率自动探测（如需指定则改为实际值，如1920）
	videoHeight := 0     // 分辨率自动探测（如需指定则改为实际值，如1080）

	// 3. 提取H.264关键帧并转图片
	if err := ExtractH264KeyFrames(h264Path, dir, imageFormat, videoWidth, videoHeight); err != nil {
		fmt.Println("提取H.264关键帧失败:", err)
		os.Exit(1)
	}
}

// CheckFFmpeg 检查系统是否安装了FFmpeg
func CheckFFmpeg() (bool, error) {
	cmd := exec.Command("ffmpeg", "-version")
	err := cmd.Run()
	if err != nil {
		return false, fmt.Errorf("FFmpeg 未安装或未添加到系统PATH: %v", err)
	}
	return true, nil
}

// ExtractH264KeyFrames 将H.264裸流的关键帧转为图片
// h264Path: H.264裸流文件路径（.264/.h264）
// outputDir: 图片输出目录
// format: 输出图片格式 (jpg/png)
// width/height: 视频分辨率（若不指定，FFmpeg会自动探测）
func ExtractH264KeyFrames(h264Path, outputDir, format string, width, height int) error {
	// 验证输入文件
	if _, err := os.Stat(h264Path); os.IsNotExist(err) {
		return fmt.Errorf("H.264文件不存在: %s", h264Path)
	}

	// 创建输出目录
	if err := os.MkdirAll(outputDir, 0755); err != nil {
		return fmt.Errorf("创建输出目录失败: %v", err)
	}

	// 构造FFmpeg命令（适配H.264裸流）
	outputPattern := filepath.Join(outputDir, "h264_keyframe_%04d."+format)
	args := []string{
		"-f", "h264", // 明确输入格式为H.264裸流
		"-i", h264Path, // 输入H.264裸流文件
		"-skip_frame", "nokey", // 只处理关键帧（I帧）
		"-vsync", "0", // 禁用帧同步，保证每个I帧都输出
		"-q:v", "2", // 图片质量（1-31，值越小质量越高）
	}

	// 可选：指定分辨率（如果FFmpeg自动探测失败时使用）
	if width > 0 && height > 0 {
		args = append(args, "-s", fmt.Sprintf("%dx%d", width, height))
	}

	// 补全输出参数
	args = append(args,
		"-f", "image2", // 输出图片序列格式
		outputPattern, // 输出文件模板
		"-y",          // 覆盖已存在的文件
	)

	// 创建命令并捕获输出
	cmd := exec.Command("ffmpeg", args...)
	_, err := cmd.StdoutPipe()
	if err != nil {
		return fmt.Errorf("创建标准输出管道失败: %v", err)
	}
	stderr, err := cmd.StderrPipe()
	if err != nil {
		return fmt.Errorf("创建标准错误管道失败: %v", err)
	}

	// 启动命令
	if err := cmd.Start(); err != nil {
		return fmt.Errorf("启动FFmpeg失败: %v", err)
	}

	// 实时打印FFmpeg日志（便于调试H.264解析问题）
	scanner := bufio.NewScanner(stderr)
	for scanner.Scan() {
		line := scanner.Text()
		fmt.Println("FFmpeg日志:", line)
		// 捕获关键错误信息
		if strings.Contains(line, "error") && strings.Contains(line, "H.264") {
			fmt.Println("⚠️ H.264解析警告:", line)
		}
	}

	// 等待命令执行完成
	if err := cmd.Wait(); err != nil {
		return fmt.Errorf("FFmpeg执行失败: %v", err)
	}

	// 统计输出的关键帧图片数量
	files, err := filepath.Glob(filepath.Join(outputDir, "h264_keyframe_*."+format))
	if err != nil {
		return fmt.Errorf("统计输出图片失败: %v", err)
	}

	fmt.Printf("成功从H.264裸流提取 %d 个关键帧，保存至: %s\n", len(files), outputDir)
	return nil
}
