package biz

import (
	"ai_scheduler/internal/biz/do"
	"ai_scheduler/internal/biz/handle/dingtalk"
	"ai_scheduler/internal/biz/handle/qywx"
	"ai_scheduler/internal/config"
	"ai_scheduler/internal/data/constants"
	"ai_scheduler/internal/data/impl"
	"ai_scheduler/internal/data/model"
	"ai_scheduler/internal/domain/tools/common/knowledge_base"
	"ai_scheduler/internal/entitys"
	"ai_scheduler/internal/pkg"
	"ai_scheduler/internal/tools"
	"ai_scheduler/internal/tools/bbxt"
	"ai_scheduler/tmpl/dataTemp"
	"ai_scheduler/utils"
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"

	"gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go/card"
	"gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go/chatbot"

	dingtalkPkg "ai_scheduler/internal/pkg/dingtalk"
	"ai_scheduler/internal/pkg/util"

	"github.com/alibabacloud-go/dingtalk/card_1_0"
	"github.com/alibabacloud-go/tea/tea"
	"github.com/duke-git/lancet/v2/slice"
	"github.com/gofiber/fiber/v2/log"
	"github.com/redis/go-redis/v9"
	"xorm.io/builder"
)

// AiRouterBiz 智能路由服务
type DingTalkBotBiz struct {
	do                   *do.Do
	handle               *do.Handle
	botConfigImpl        *impl.BotConfigImpl
	replier              *chatbot.ChatbotReplier
	log                  log.Logger
	dingTalkUser         *dingtalk.User
	botGroupImpl         *impl.BotGroupImpl
	botGroupConfigImpl   *impl.BotGroupConfigImpl
	botGroupQywxImpl     *impl.BotGroupQywxImpl
	toolManager          *tools.Manager
	chatHis              *impl.BotChatHisImpl
	botUserImpl          *impl.BotUserImpl
	conf                 *config.Config
	cardSend             *dingtalk.SendCardClient
	qywxGroupHandle      *qywx.Group
	groupConfigBiz       *GroupConfigBiz
	reportDailyCacheImpl *impl.ReportDailyCacheImpl
	macro                *do.Macro
	dingtalkOauth2Client *dingtalkPkg.Oauth2Client
	dingTalkOld          *dingtalkPkg.OldClient
	dingtalkCardClient   *dingtalkPkg.CardClient
	redisCli             *redis.Client
	issueImpl            *impl.IssueImpl
	sysImpl              *impl.SysImpl
}

// NewDingTalkBotBiz
func NewDingTalkBotBiz(
	do *do.Do,
	handle *do.Handle,
	botConfigImpl *impl.BotConfigImpl,
	botGroupImpl *impl.BotGroupImpl,
	botGroupConfigImpl *impl.BotGroupConfigImpl,
	dingTalkUser *dingtalk.User,
	chatHis *impl.BotChatHisImpl,
	botUserImpl *impl.BotUserImpl,
	reportDailyCacheImpl *impl.ReportDailyCacheImpl,
	toolManager *tools.Manager,
	conf *config.Config,
	cardSend *dingtalk.SendCardClient,
	groupConfigBiz *GroupConfigBiz,
	macro *do.Macro,
	dingtalkOauth2Client *dingtalkPkg.Oauth2Client,
	dingTalkOld *dingtalkPkg.OldClient,
	dingtalkCardClient *dingtalkPkg.CardClient,
	rdb *utils.Rdb,
	issueImpl *impl.IssueImpl,
	sysImpl *impl.SysImpl,
) *DingTalkBotBiz {
	return &DingTalkBotBiz{
		do:                   do,
		handle:               handle,
		botConfigImpl:        botConfigImpl,
		replier:              chatbot.NewChatbotReplier(),
		dingTalkUser:         dingTalkUser,
		groupConfigBiz:       groupConfigBiz,
		botGroupImpl:         botGroupImpl,
		botGroupConfigImpl:   botGroupConfigImpl,
		toolManager:          toolManager,
		chatHis:              chatHis,
		botUserImpl:          botUserImpl,
		conf:                 conf,
		cardSend:             cardSend,
		reportDailyCacheImpl: reportDailyCacheImpl,
		macro:                macro,
		dingtalkOauth2Client: dingtalkOauth2Client,
		dingTalkOld:          dingTalkOld,
		dingtalkCardClient:   dingtalkCardClient,
		redisCli:             rdb.Rdb,
		issueImpl:            issueImpl,
		sysImpl:              sysImpl,
	}
}

func (d *DingTalkBotBiz) GetDingTalkBotCfgList() (dingBotList []entitys.DingTalkBot, err error) {
	botConfig := make([]model.AiBotConfig, 0)

	cond := builder.NewCond()
	cond = cond.And(builder.Eq{"status": constants.Enable})
	cond = cond.And(builder.Eq{"bot_type": constants.BotTypeDingTalk})
	err = d.botConfigImpl.GetRangeToMapStruct(&cond, &botConfig)
	for _, v := range botConfig {
		var config entitys.DingTalkBot
		err = json.Unmarshal([]byte(v.BotConfig), &config)
		if err != nil {
			d.log.Info("初始化“%s”失败:%s", v.BotName, err.Error())
		}
		config.BotIndex = v.RobotCode
		dingBotList = append(dingBotList, config)
	}
	return

}

func (d *DingTalkBotBiz) InitRequire(ctx context.Context, data *chatbot.BotCallbackDataModel) (requireData *entitys.RequireDataDingTalkBot, err error) {
	requireData = &entitys.RequireDataDingTalkBot{
		Req: data,
		Ch:  make(chan entitys.Response, 2),
	}

	return
}

func (d *DingTalkBotBiz) Do(ctx context.Context, requireData *entitys.RequireDataDingTalkBot) (err error) {
	//entitys.ResLoading(requireData.Ch, "", "收到消息，正在处理中，请稍等")
	//defer close(requireData.Ch)
	switch constants.ConversationType(requireData.Req.ConversationType) {
	case constants.ConversationTypeSingle:
		err = d.handleSingleChat(ctx, requireData)
	case constants.ConversationTypeGroup:
		err = d.handleGroupChat(ctx, requireData)
	default:
		err = errors.New("未知的聊天类型：" + requireData.Req.ConversationType)
	}
	if err != nil {
		entitys.ResText(requireData.Ch, "", err.Error())
	}
	return
}

// handleSingleChat 单聊处理
// 先不接意图识别-仅提供问题处理
func (d *DingTalkBotBiz) handleSingleChat(ctx context.Context, requireData *entitys.RequireDataDingTalkBot) error {
	// 1. 获取用户信息
	user, err := d.botUserImpl.GetByStaffId(requireData.Req.SenderStaffId)
	if err != nil {
		return err
	}
	requireData.ID = int32(user.UserID)
	requireData.UserInfo = &entitys.DingTalkUserInfo{
		UserId:  int(user.UserID),
		StaffId: user.StaffID,
		Name:    user.Name,
	}

	// 2. 获取历史记录 (最近6轮用户输入)
	userHist, err := d.getRecentUserHistory(ctx, constants.ConversationTypeSingle, requireData.ID, d.conf.Sys.SessionLen)
	if err != nil {
		return err
	}

	// 3. 系统&问题分类（意图识别阶段）
	resolveResult, err := d.resolveSystemAndIssueType(ctx, requireData, userHist)
	if err != nil {
		return err
	}
	log.Debugf("系统&分类结果: %s - %s，原因：%s", resolveResult.Sys.SysName, resolveResult.IssueType.Name, resolveResult.Classification.Reason)

	// 4. 分类处理（后续考虑接各自的工作流/agent）
	switch resolveResult.IssueType.Code {
	case constants.IssueTypeKnowledgeQA:
		// 知识库问答
		return d.handleKnowledgeQA(ctx, requireData, userHist, resolveResult)
	default: // 其他问题类型
		// 系统为空，再次询问
		if resolveResult.Sys.SysID == 0 {
			entitys.ResText(requireData.Ch, "", "\n抱歉，我无法确定您咨询的是哪个系统。请告诉我具体系统名称（如：直连天下系统、货易通系统），以便我为您准确解答或安排对应的技术支持。")
			return nil
		}
		return d.fallbackToGroupCreation(ctx, requireData, resolveResult)
	}
}

// 知识库问答
func (d *DingTalkBotBiz) handleKnowledgeQA(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, userHist []model.AiBotChatHi, resolveResult *resolveSystemAndIssueTypeResult) error {
	// 获取租户ID
	tenantId := constants.KnowledgeTenantIdDefault
	if resolveResult.Sys.KnowlegeTenantKey != "" {
		tenantId = resolveResult.Sys.KnowlegeTenantKey
	}

	// 改写 Query (Query Rewriting)
	rewrittenQuery, err := d.handle.RewriteQuery(ctx, userHist, requireData.Req.Text.Content)
	var queryText = requireData.Req.Text.Content
	if err == nil && rewrittenQuery != "" {
		queryText = rewrittenQuery
	}
	log.Debugf("改写前后的Query: %s -> %s", requireData.Req.Text.Content, queryText)

	// 获取知识库结果
	isRetrieved, responseContent, err := d.getKnowledgeAnswer(ctx, requireData.Ch, tenantId, queryText)
	if err != nil {
		return err
	}
	if isRetrieved {
		// 过一遍 LLM 判断是否真的命中知识库
		isRetrieved, err = d.handle.IsAnswerRelevant(ctx, queryText, responseContent)
		if err != nil {
			return err
		}
	}

	// 未匹配&全局 -> 明确具体系统
	if !isRetrieved && resolveResult.Sys.SysID == 0 {
		entitys.ResText(requireData.Ch, "", "\n抱歉，知识库未命中，无法回答您的问题。\n若您的问题是某一具体系统的，请告诉我具体系统名称（如：直连天下系统、货易通系统），以便我为您准确解答。")
		return nil
	}
	// 未匹配&指定系统 -> 拉群卡片
	if !isRetrieved && resolveResult.Sys.SysID != 0 {
		entitys.ResText(requireData.Ch, "", fmt.Sprintf("\n抱歉，%s知识库未命中，无法回答您的问题。即将为您创建群聊解答。", resolveResult.Sys.SysName))
		return d.fallbackToGroupCreation(ctx, requireData, resolveResult)
	}

	return nil
}

// 获取知识库问答结果
func (d *DingTalkBotBiz) getKnowledgeAnswer(ctx context.Context, ch chan entitys.Response, tenantId string, queryText string) (bool, string, error) {
	// 请求知识库工具
	knowledgeBase := knowledge_base.New(d.conf.KnowledgeConfig)
	knowledgeResp, err := knowledgeBase.Query(&knowledge_base.QueryRequest{
		TenantID: tenantId, // 后续动态接参
		Query:    queryText,
		Mode:     constants.KnowledgeModeMix,
		Stream:   true,
		Think:    false,
		OnlyRAG:  true,
	})
	if err != nil {
		return false, "", fmt.Errorf("请求知识库工具失败，err: %v", err)
	}

	// 读取知识库SSE数据
	return d.groupConfigBiz.readKnowledgeSSE(knowledgeResp, ch, true)
}

type resolveSystemAndIssueTypeResult struct {
	Sys            model.AiSy
	IssueType      model.AiIssueType
	Classification *do.IssueClassification
}

// 解析系统和问题类型
func (d *DingTalkBotBiz) resolveSystemAndIssueType(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, userHist []model.AiBotChatHi) (*resolveSystemAndIssueTypeResult, error) {
	// 1. 获取所有系统和问题类型用于分类
	allSys, err := d.sysImpl.FindAll()
	if err != nil {
		return nil, err
	}
	sysNames := slice.Map(allSys, func(_ int, sys model.AiSy) string {
		return sys.SysName
	})
	allIssueTypes, err := d.issueImpl.IssueType.FindAll()
	if err != nil {
		return nil, err
	}
	issueTypeNames := slice.Map(allIssueTypes, func(_ int, it model.AiIssueType) string {
		return it.Name
	})

	// 2. LLM 分类
	// 系统名称
	classificationSys, err := d.handle.ClassifyIssueSystem(ctx, sysNames, requireData.Req.Text.Content, userHist)
	if err != nil {
		return nil, err
	}
	// 问题类型
	classificationIssueType, err := d.handle.ClassifyIssueType(ctx, issueTypeNames, sysNames, requireData.Req.Text.Content, userHist)
	if err != nil {
		return nil, err
	}
	// 合并
	classification := &do.IssueClassification{
		SysName:       classificationSys.SysName,
		IssueTypeName: classificationIssueType.IssueTypeName,
		Summary:       classificationIssueType.Summary,
		Reason:        fmt.Sprintf("系统名称推断理由：%s\n问题类型推断理由：%s", classificationSys.Reason, classificationIssueType.Reason),
	}

	// 3. 匹配系统
	var sys model.AiSy
	for _, s := range allSys {
		if s.SysName == classification.SysName {
			sys = s
			break
		}
	}

	// 4. 匹配问题类型
	var issueType model.AiIssueType
	for _, it := range allIssueTypes {
		if it.Name == classification.IssueTypeName {
			issueType = it
			break
		}
	}

	return &resolveSystemAndIssueTypeResult{
		Sys:            sys,
		IssueType:      issueType,
		Classification: classification,
	}, nil
}

// handleWithSpecificSys 处理用户明确指定的系统
// func (d *DingTalkBotBiz) handleWithSpecificSys(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, sysName string) error {
// 	// 1. 查找系统
// 	var sys model.AiSy
// 	cond := builder.NewCond().And(builder.Eq{"sys_name": sysName})
// 	err := d.sysImpl.GetOneBySearchToStrut(&cond, &sys)
// 	if err != nil {
// 		if errors.Is(err, sql.ErrNoRows) {
// 			entitys.ResText(requireData.Ch, "", "抱歉，我还是没有找到名为“"+sysName+"”的系统。请联系管理员确认系统名称。")
// 			return nil
// 		}
// 		return err
// 	}

// 	// 2. 既然已经明确了系统，直接尝试拉群（这里假设问题类型为“其他”或由LLM再次分析）
// 	// 为简化，这里再次调用分类逻辑，但带上已确定的系统
// 	return d.fallbackToGroupCreationWithSys(ctx, requireData, &sys)
// }

// getRecentUserHistory 获取最近的用户输入历史
func (d *DingTalkBotBiz) getRecentUserHistory(ctx context.Context, conversationType constants.ConversationType, id int32, limit int) ([]model.AiBotChatHi, error) {
	var his []model.AiBotChatHi
	cond := builder.NewCond().
		And(builder.Eq{"his_type": conversationType}).
		And(builder.Eq{"id": id}).
		And(builder.Eq{"role": "user"})

	_, err := d.chatHis.GetListToStruct(&cond, &dataTemp.ReqPageBo{Limit: limit}, &his, "his_id desc")
	if err != nil {
		return nil, err
	}
	return his, nil
}

// 在已知系统&问题类型的情况下进行分类并拉群
func (d *DingTalkBotBiz) fallbackToGroupCreation(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, resolveResult *resolveSystemAndIssueTypeResult) error {
	entitys.ResText(requireData.Ch, "", fmt.Sprintf("\n检测到您想咨询 %s-%s 问题。", resolveResult.Sys.SysName, resolveResult.IssueType.Name))

	// 查找分配规则
	rule, found, _ := d.issueImpl.IssueAssignRule.FindOne(
		d.issueImpl.WithSysID(resolveResult.Sys.SysID),
		d.issueImpl.WithIssueTypeID(resolveResult.IssueType.ID),
		d.issueImpl.WithStatus(1),
	)
	if !found {
		entitys.ResText(requireData.Ch, "", fmt.Sprintf("\n抱歉，当前系统未配置路由规则 %s-%s，请联系管理员配置。", resolveResult.Sys.SysName, resolveResult.IssueType.Name))
		return nil
	}

	var groupMember, groupMemberName []string
	if rule.ID != 0 {
		// 获取配置的用户
		assignUsers, err := d.issueImpl.IssueAssignUser.FindAll(d.issueImpl.WithRuleID(rule.ID))
		if len(assignUsers) == 0 {
			log.Errorf("assign user not found for rule %d; err: %v", rule.ID, err)
			return fmt.Errorf("分配用户 %d 不存在", rule.ID)
		}
		userIds := slice.Map(assignUsers, func(_ int, au model.AiIssueAssignUser) int32 {
			return au.UserID
		})
		// 获取有效用户
		botUsers, err := d.botUserImpl.GetByUserIds(userIds)
		if err != nil {
			return err
		}

		// 仅获取有效用户的 staff_id
		for _, au := range assignUsers {
			botUser, found := slice.Find(botUsers, func(_ int, bu model.AiBotUser) bool {
				return bu.UserID == au.UserID
			})
			if found && botUser.StaffID != "" {
				groupMember = append(groupMember, botUser.StaffID)
				groupMemberName = append(groupMemberName, "@"+botUser.Name)
			}
		}
	}

	// 兜底处理人
	if len(groupMember) == 0 {
		groupMember = []string{"17415698414368678"}
	}

	// 合并提问者
	groupMember = append([]string{requireData.Req.SenderStaffId}, groupMember...)
	groupMember = slice.Unique(groupMember)

	// 先回复用户
	entitys.ResText(requireData.Ch, "", fmt.Sprintf("\n已检索到处理人\n%s\n是否创建群聊？", strings.Join(groupMemberName, "、")))

	// 发送确认卡片
	groupName := fmt.Sprintf("[%s]-%s", resolveResult.IssueType.Name, resolveResult.Classification.Summary)
	return d.SendGroupCreationConfirmCard(ctx, &SendGroupCreationConfirmCardParams{
		RobotCode:      requireData.Req.RobotCode,
		ConversationId: requireData.Req.ConversationId,
		SenderStaffId:  requireData.Req.SenderStaffId,
		UserIds:        groupMember,
		GroupName:      groupName,
		Summary:        resolveResult.Classification.Summary,
	})
}

// createDefaultGroup 兜底发送确认卡片
func (d *DingTalkBotBiz) createDefaultGroup(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, reason string) error {
	userIds := []string{requireData.Req.SenderStaffId, "17415698414368678"}
	groupName := fmt.Sprintf("[未知]-%s", reason)
	return d.SendGroupCreationConfirmCard(ctx, &SendGroupCreationConfirmCardParams{
		RobotCode:      requireData.Req.RobotCode,
		ConversationId: requireData.Req.ConversationId,
		SenderStaffId:  requireData.Req.SenderStaffId,
		UserIds:        userIds,
		GroupName:      groupName,
		Summary:        reason,
	})
}

func (d *DingTalkBotBiz) handleGroupChat(ctx context.Context, requireData *entitys.RequireDataDingTalkBot) (err error) {
	group, err := d.initGroup(ctx, requireData.Req.ConversationId, requireData.Req.ConversationTitle, requireData.Req.RobotCode)
	if err != nil {
		return
	}
	groupConfig, err := d.groupConfigBiz.GetGroupConfig(ctx, group.ConfigID)
	if err != nil {
		return
	}
	//宏
	sucMsg, err, isFinal := d.macro.Router(ctx, requireData.Req.Text.Content, groupConfig)
	if err != nil {
		entitys.ResText(requireData.Ch, "", err.Error())
		return
	}
	if len(sucMsg) > 0 {
		entitys.ResText(requireData.Ch, "", sucMsg)
	}
	if isFinal {
		return
	}
	requireData.ID = group.GroupID
	groupTools, err := d.groupConfigBiz.getGroupTools(ctx, groupConfig)
	if err != nil {
		return
	}
	rec, err := d.recognize(ctx, requireData, groupTools)
	if err != nil {
		return
	}

	return d.groupConfigBiz.handleMatch(ctx, rec, groupConfig, requireData.Req)
}

func (d *DingTalkBotBiz) initGroup(ctx context.Context, conversationId string, conversationTitle string, robotCode string) (group *model.AiBotGroup, err error) {
	group, err = d.botGroupImpl.GetByConversationIdAndRobotCode(conversationId, robotCode)
	if err != nil {
		if !errors.Is(err, sql.ErrNoRows) {

			return
		}
	}

	if group.GroupID == 0 {
		group = &model.AiBotGroup{
			ConversationID: conversationId,
			Title:          conversationTitle,
			RobotCode:      robotCode,
		}
		//如果不存在则创建
		_, err = d.botGroupImpl.Add(group)
	}
	return
}

func (d *DingTalkBotBiz) recognize(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, tools []model.AiBotTool) (rec *entitys.Recognize, err error) {

	userContent, err := d.getUserContent(requireData.Req.Msgtype, requireData.Req.Text.Content)
	if err != nil {
		return
	}
	rec = &entitys.Recognize{
		Ch:           requireData.Ch,
		SystemPrompt: d.defaultPrompt(),
		UserContent:  userContent,
	}
	//历史记录
	rec.ChatHis, err = d.getHis(ctx, constants.ConversationType(requireData.Req.ConversationType), requireData.ID)
	if err != nil {
		return
	}
	//工具注册
	if len(tools) > 0 {
		rec.Tasks = make([]entitys.RegistrationTask, 0, len(tools))
		for _, task := range tools {
			taskConfig := entitys.TaskConfigDetail{}
			if err = json.Unmarshal([]byte(task.Config), &taskConfig); err != nil {
				log.Errorf("解析任务配置失败: %s, 任务ID: %s", err.Error(), task.Index)
				continue // 解析失败时跳过该任务，而不是直接返回错误
			}

			rec.Tasks = append(rec.Tasks, entitys.RegistrationTask{
				Name:             task.Index,
				Desc:             task.TempPrompt,
				TaskConfigDetail: taskConfig, // 直接使用解析后的配置，避免重复构建
			})
		}
	}

	rec.Ext = pkg.JsonByteIgonErr(&entitys.TaskExt{
		UserName: requireData.Req.SenderNick,
	})

	err = d.handle.Recognize(ctx, rec, &do.WithDingTalkBot{})
	return
}

func (d *DingTalkBotBiz) getHis(ctx context.Context, conversationType constants.ConversationType, Id int32) (content entitys.ChatHis, err error) {

	var (
		his []model.AiBotChatHi
	)
	cond := builder.NewCond()
	cond = cond.And(builder.Eq{"his_type": conversationType})
	cond = cond.And(builder.Eq{"id": Id})
	_, err = d.chatHis.GetListToStruct(&cond, &dataTemp.ReqPageBo{Limit: d.conf.Sys.SessionLen}, &his, "his_id desc")
	if err != nil {
		return
	}
	messages := make([]entitys.HisMessage, 0, len(his))
	for _, v := range his {
		if v.Role != "user" {
			continue
		}
		messages = append(messages, entitys.HisMessage{
			Role:      constants.Caller(v.Role), // 用户角色
			Content:   v.Content,                // 用户输入内容
			Timestamp: v.CreateAt.Format(time.DateTime),
		})
	}
	return entitys.ChatHis{
		SessionId: fmt.Sprintf("%s_%d", conversationType, Id),
		Messages:  messages,
		Context: entitys.HisContext{
			UserLanguage: constants.LangZhCN,                   // 默认中文
			SystemMode:   constants.SystemModeTechnicalSupport, // 默认技术支持模式
		},
	}, nil
}

func (d *DingTalkBotBiz) getUserContent(msgType string, msgContent interface{}) (content *entitys.RecognizeUserContent, err error) {
	switch constants.BotMsgType(msgType) {
	case constants.BotMsgTypeText:
		content = &entitys.RecognizeUserContent{
			Text: msgContent.(string),
		}
	default:
		return nil, errors.New("未知的消息类型：" + msgType)
	}
	return
}

func (d *DingTalkBotBiz) HandleStreamRes(ctx context.Context, data *chatbot.BotCallbackDataModel, content chan string) (err error) {
	err = d.cardSend.NewCard(ctx, &dingtalk.CardSend{
		RobotCode:        data.RobotCode,
		ConversationType: constants.ConversationType(data.ConversationType),
		Template:         constants.CardTempDefault,
		ContentChannel:   content, // 指定内容通道
		ConversationId:   data.ConversationId,
		SenderStaffId:    data.SenderStaffId,
		Title:            data.Text.Content,
	})
	return
}

func (d *DingTalkBotBiz) SendReport(ctx context.Context, groupInfo *model.AiBotGroup, report *bbxt.ReportRes) (err error) {
	if report == nil {
		return errors.New("report is nil")
	}
	reportChan := make(chan string, 10)
	defer close(reportChan)
	reportChan <- report.Title
	reportChan <- fmt.Sprintf("![图片](%s)", report.Url)
	err = d.HandleStreamRes(ctx, &chatbot.BotCallbackDataModel{
		RobotCode:        groupInfo.RobotCode,
		ConversationType: constants.ConversationTypeGroup,
		ConversationId:   groupInfo.ConversationID,
		Text: chatbot.BotCallbackDataTextModel{
			Content: report.ReportName,
		},
	}, reportChan)
	return
}

// SendReports 发送多个报告
func (d *DingTalkBotBiz) SendReports(ctx context.Context, groupInfo *model.AiBotGroup, reports []*bbxt.ReportRes) (err error) {
	if len(reports) == 0 {
		return errors.New("report is empty")
	}

	title := fmt.Sprintf("截止%s日报", time.Now().Format("1月2日15点"))
	reportChan := make(chan string, len(reports)*2)
	writeCount := 0
	for _, v := range reports {
		if v == nil {
			continue
		}
		reportChan <- fmt.Sprintf("**%s**", v.Title)
		reportChan <- fmt.Sprintf("![图片](%s)", v.Url)
		writeCount += 2
	}
	close(reportChan)
	if writeCount == 0 {
		return errors.New("report is empty")
	}
	err = d.HandleStreamRes(ctx, &chatbot.BotCallbackDataModel{
		RobotCode:        groupInfo.RobotCode,
		ConversationType: constants.ConversationTypeGroup,
		ConversationId:   groupInfo.ConversationID,
		Text: chatbot.BotCallbackDataTextModel{
			Content: title,
		},
	}, reportChan)
	return
}

func (d *DingTalkBotBiz) GetGroupInfo(ctx context.Context, groupId int) (group model.AiBotGroup, err error) {

	cond := builder.NewCond()
	cond = cond.And(builder.Eq{"group_id": groupId})
	cond = cond.And(builder.Eq{"status": constants.Enable})
	err = d.botGroupImpl.GetOneBySearchToStrut(&cond, &group)

	return
}

func (d *DingTalkBotBiz) ReplyText(ctx context.Context, SessionWebhook string, content string, arg ...string) error {
	msg := content
	if len(arg) > 0 {
		msg = fmt.Sprintf(content, arg)
	}
	return d.replier.SimpleReplyText(ctx, SessionWebhook, []byte(msg))
}

func (d *DingTalkBotBiz) replyImg(ctx context.Context, SessionWebhook string, content string, arg ...string) error {
	msg := content
	if len(arg) > 0 {
		msg = fmt.Sprintf(content, arg)
	}
	return d.replier.SimpleReplyText(ctx, SessionWebhook, []byte(msg))
}

func (d *DingTalkBotBiz) replyFile(ctx context.Context, SessionWebhook string, content string, arg ...string) error {
	msg := content
	if len(arg) > 0 {
		msg = fmt.Sprintf(content, arg)
	}
	return d.replier.SimpleReplyText(ctx, SessionWebhook, []byte(msg))
}

func (d *DingTalkBotBiz) replyMarkdown(ctx context.Context, SessionWebhook string, content string, arg ...string) error {
	msg := content
	if len(arg) > 0 {
		msg = fmt.Sprintf(content, arg)
	}
	return d.replier.SimpleReplyText(ctx, SessionWebhook, []byte(msg))
}

func (d *DingTalkBotBiz) replyActionCard(ctx context.Context, SessionWebhook string, content string, arg ...string) error {
	msg := content
	if len(arg) > 0 {
		msg = fmt.Sprintf(content, arg)
	}
	return d.replier.SimpleReplyText(ctx, SessionWebhook, []byte(msg))
}

func (d *DingTalkBotBiz) SaveHis(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, chat []string) (err error) {
	if len(chat) == 0 {
		return
	}
	his := []*model.AiBotChatHi{
		{
			HisType: requireData.Req.ConversationType,
			ID:      requireData.ID,
			Role:    "user",
			Content: requireData.Req.Text.Content,
		},
		{
			HisType: requireData.Req.ConversationType,
			ID:      requireData.ID,
			Role:    "system",
			Content: strings.Join(chat, "\n"),
		},
	}
	_, err = d.chatHis.Add(his)
	return err
}

func (d *DingTalkBotBiz) defaultPrompt() string {
	now := time.Now().Format(time.DateTime)
	return `[system] 你是一个智能路由系统，核心职责是 **精准解析用户意图并路由至对应任务模块**。请严格遵循以下规则：
[rule]
1. **返回格式**：
仅输出以下 **严格格式化的 JSON 字符串**（禁用 Markdown）：  
{ "index": "工具索引index", "confidence": 0.0-1.0,"reasoning": "判断理由","parameters":"jsonstring |提取参数","is_match":true||false,"chat": "追问内容"}
关键规则（按优先级排序）：

2. **工具匹配**：

- 若匹配到工具，使用工具的 parameters 作为模板做参数匹配
- 注意区分 parameters 中的 必须参数（required） 和 可选参数（optional），按下述参数提取规则处理。
- 若**完全无法匹配**，立即设置 is_match: false，并在 chat 中已第一人称的角度提醒用户需要适用何种工具（例："请问您是要查询订单还是商品呢"）。

3. **参数提取**：

- 根据 parameters 字段列出的参数名，从用户输入中提取对应值。
- **仅提取**明确提及的参数，忽略未列出的内容。
- 必须参数仅使用用户直接提及的参数，不允许从上下文推断。
- 若必须参数缺失，立即设置 is_match: false，并在 chat 中已第一人称的角度提醒用户提供缺少的参数追问（例："需要您补充XX信息"）。

4. 格式强制要求：
-所有字段值必须是**字符串**（包括 confidence）。
-parameters 必须是 **转义后的 JSON 字符串**（如 "{\"product_name\": \"京东月卡\"}"）。
当前时间：` + now + `，所有的时间识别精确到秒`
}

// CreateIssueHandlingGroupAndInit 创建问题处理群聊并初始化
func (d *DingTalkBotBiz) CreateIssueHandlingGroupAndInit(ctx context.Context, data *card.CardRequest) (resp *card.CardResponse, err error) {

	// 解析 OutTrackId 以获取 SpaceId 和 BotId
	spaceId, botId := constants.ParseCardOutTrackId(data.OutTrackId)

	// 获取操作状态
	status := data.CardActionData.CardPrivateData.Params["status"]
	if status == "confirm" {
		// 获取新群聊人员 (从卡片参数中统一解析)
		targetUserIdsStr := data.CardActionData.CardPrivateData.Params["target_user_ids"].(string)
		var userIds []string
		if targetUserIdsStr != "" {
			userIds = strings.Split(targetUserIdsStr, ",")
		}

		if len(userIds) == 0 {
			return nil, errors.New("target_user_ids 参数不能为空")
		}

		// 创建群聊及群初始化（异步响应）
		util.SafeGo("CreateIssueHandlingGroupAndInit", func() {
			err := d.createIssueHandlingGroupAndInit(ctx, data.CardActionData.CardPrivateData.Params, spaceId, botId, userIds)
			if err != nil {
				log.Errorf("创建群聊及群初始化失败: %v", err)
			}
		})
	}

	// 构建关闭创建群组卡片按钮的响应
	return d.buildCreateGroupCardResp(), nil
}

type SendGroupCreationConfirmCardParams struct {
	RobotCode      string
	ConversationId string
	SenderStaffId  string
	UserIds        []string
	GroupName      string
	Summary        string
	IsGroupChat    bool
}

// SendGroupCreationConfirmCard 发送创建群聊确认卡片
func (d *DingTalkBotBiz) SendGroupCreationConfirmCard(ctx context.Context, params *SendGroupCreationConfirmCardParams) error {
	// 获取人员姓名用于展示
	var userNames []string
	for _, uid := range params.UserIds {
		if uid == params.SenderStaffId {
			continue
		}
		user, err := d.botUserImpl.GetByStaffId(uid)
		if err == nil && user != nil {
			userNames = append(userNames, "@"+user.Name)
		} else {
			userNames = append(userNames, "@"+uid)
		}
	}
	issueOwnerStr := strings.Join(userNames, "、")

	// 获取应用配置
	appKey, err := d.botConfigImpl.GetRobotAppKey(params.RobotCode)
	if err != nil {
		return err
	}

	// 构建卡片 OutTrackId
	outTrackId := constants.BuildCardOutTrackId(params.SenderStaffId, params.RobotCode)

	// 准备可见人员列表
	var recipients []*string
	if params.IsGroupChat {
		// 群聊：提问者 + 负责人可见
		for _, uid := range params.UserIds {
			recipients = append(recipients, tea.String(uid))
		}
		// 确保提问者也在可见列表中
		foundSender := false
		for _, uid := range params.UserIds {
			if uid == params.SenderStaffId {
				foundSender = true
				break
			}
		}
		if !foundSender {
			recipients = append(recipients, tea.String(params.SenderStaffId))
		}
	} else {
		// 单聊：仅提问者可见
		recipients = append(recipients, tea.String(params.SenderStaffId))
	}

	// 发送钉钉卡片
	_, err = d.dingtalkCardClient.CreateAndDeliver(
		appKey,
		&card_1_0.CreateAndDeliverRequest{
			CardTemplateId: tea.String(d.conf.Dingtalk.Card.Template.CreateGroupApprove),
			OutTrackId:     tea.String(outTrackId),
			CallbackType:   tea.String("STREAM"),
			CardData: &card_1_0.CreateAndDeliverRequestCardData{
				CardParamMap: map[string]*string{
					"title":           tea.String("创建群聊提醒"),
					"content":         tea.String(fmt.Sprintf("**确认创建群聊？**\n\n将邀请以下成员加入群聊：\n\n%s", issueOwnerStr)),
					"remark":          tea.String("注：如若无需，忽略即可"),
					"button_left":     tea.String("创建群聊"),
					"button_right":    tea.String("忽略"),
					"action_id":       tea.String("create_group"),
					"button_display":  tea.String("true"),
					"group_scope":     tea.String(params.Summary),
					"target_user_ids": tea.String(strings.Join(params.UserIds, ",")),
					"group_name":      tea.String(params.GroupName),
				},
			},
			OpenSpaceId: tea.String("dtv1.card//IM_ROBOT." + params.SenderStaffId),
			ImRobotOpenDeliverModel: &card_1_0.CreateAndDeliverRequestImRobotOpenDeliverModel{
				SpaceType: tea.String("IM_ROBOT"),
				RobotCode: tea.String(params.RobotCode),
			},
			ImRobotOpenSpaceModel: &card_1_0.CreateAndDeliverRequestImRobotOpenSpaceModel{
				SupportForward: tea.Bool(false),
			},
		})
	return err
}

// buildNewGroupUserIds 构建新群聊人员列表
func (d *DingTalkBotBiz) buildNewGroupUserIds(spaceId, botId, groupOwner string) ([]string, error) {
	// 群id+机器人id确认一个群配置
	botGroup, err := d.botGroupImpl.GetByConversationIdAndRobotCode(spaceId, botId)
	if err != nil {
		return nil, err
	}

	// 获取群配置
	var groupConfig model.AiBotGroupConfig
	cond := builder.NewCond().And(builder.Eq{"config_id": botGroup.ConfigID})
	err = d.botGroupConfigImpl.GetOneBySearchToStrut(&cond, &groupConfig)
	if err != nil {
		return nil, err
	}

	// 获取处理人列表
	issueOwnerJson := groupConfig.IssueOwner
	type issueOwnerType struct {
		UserId string `json:"userid"`
		Name   string `json:"name"`
	}
	var issueOwner []issueOwnerType
	if err = json.Unmarshal([]byte(issueOwnerJson), &issueOwner); err != nil {
		return nil, err
	}

	// 合并所有userid
	userIds := []string{groupOwner} // 当前用户为群主
	for _, owner := range issueOwner {
		userIds = append(userIds, owner.UserId)
	}

	return userIds, nil
}

// createIssueHandlingGroupAndInit 创建问题处理群聊及群初始化
func (d *DingTalkBotBiz) createIssueHandlingGroupAndInit(ctx context.Context, callbackParams map[string]any, spaceId, botId string, userIds []string) error {
	// 获取应用配置
	appKey, err := d.botConfigImpl.GetRobotAppKey(botId)
	if err != nil {
		return err
	}

	// 获取 access_token
	accessToken, err := d.dingtalkOauth2Client.GetAccessToken(appKey)
	if err != nil {
		return err
	}
	appKey.AccessToken = accessToken

	// 创建群聊
	var groupName string
	if s, ok := callbackParams["group_name"].(string); ok {
		groupName = s
	}
	_, openConversationId, err := d.createIssueHandlingGroup(ctx, accessToken, groupName, userIds)
	if err != nil {
		return err
	}

	// 添加当前机器人到新群 - SDK 有问题，后续再考虑使用
	// _, err = d.dingtalkImClient.AddRobotToConversation(
	// 	appKey,
	// 	&im_1_0.AddRobotToConversationRequest{
	// 		OpenConversationId: tea.String(openConversationId),
	// 		RobotCode:          tea.String(botId),
	// 	})
	// if err != nil {
	// 	fmt.Printf("添加机器人到会话失败: %v", err)
	// }

	// 返回新群分享链接，直接进群 - SDK 有问题，后续再考虑使用
	// newGroupShareLink, err = d.dingTalkOld.GetJoinGroupQrcode(ctx, chatId, data.UserId)
	// if err != nil {
	// 	fmt.Printf("获取入群二维码失败: %v", err)
	// }

	// 初始化群聊
	time.Sleep(time.Millisecond * 200)                   // 等待机器人加入群聊
	groupScope := callbackParams["group_scope"].(string) // 群主题
	d.initIssueHandlingGroup(appKey, openConversationId, groupScope)

	return nil
}

// createIssueHandlingGroup 创建问题处理群聊会话
func (d *DingTalkBotBiz) createIssueHandlingGroup(ctx context.Context, accessToken string, groupName string, userIds []string) (chatId, openConversationId string, err error) {
	// 是否使用模板群开关
	var useTemplateGroup bool = true

	if groupName == "" {
		groupName = "问题处理群"
	}

	// 创建内部群会话
	if !useTemplateGroup {
		return d.dingTalkOld.CreateInternalGroupConversation(ctx, accessToken, groupName, userIds)
	}

	// 根据群模板ID创建群
	if useTemplateGroup {
		return d.dingTalkOld.CreateSceneGroupConversation(ctx, accessToken, groupName, userIds, d.conf.Dingtalk.SceneGroup.GroupTemplateIDIssueHandling)
	}

	return
}

// initIssueHandlingGroup 初始化问题处理群聊
func (d *DingTalkBotBiz) initIssueHandlingGroup(appKey dingtalkPkg.AppKey, openConversationId, groupScope string) error {
	// 1.开场白
	outTrackId := constants.BuildCardOutTrackId(openConversationId, d.conf.Dingtalk.SceneGroup.GroupTemplateRobotIDIssueHandling)
	_, err := d.dingtalkCardClient.CreateAndDeliver(
		appKey,
		&card_1_0.CreateAndDeliverRequest{
			CardTemplateId: tea.String(d.conf.Dingtalk.Card.Template.BaseMsg),
			OutTrackId:     tea.String(outTrackId),
			CallbackType:   tea.String("HTTP"),
			CardData: &card_1_0.CreateAndDeliverRequestCardData{
				CardParamMap: map[string]*string{
					"title":    tea.String("当前会话主题"),
					"markdown": tea.String("问题：" + groupScope),
				},
			},
			ImGroupOpenSpaceModel: &card_1_0.CreateAndDeliverRequestImGroupOpenSpaceModel{
				SupportForward: tea.Bool(false),
			},
			OpenSpaceId: tea.String("dtv1.card//im_group." + openConversationId),
			ImGroupOpenDeliverModel: &card_1_0.CreateAndDeliverRequestImGroupOpenDeliverModel{
				RobotCode: tea.String(d.conf.Dingtalk.SceneGroup.GroupTemplateRobotIDIssueHandling),
				AtUserIds: map[string]*string{
					"@ALL": tea.String("@ALL"),
				},
			},
		},
	)
	if err != nil {
		return err
	}

	// 2. 机器人能力
	// 构建卡片 OutTrackId
	outTrackId = constants.BuildCardOutTrackId(openConversationId, d.conf.Dingtalk.SceneGroup.GroupTemplateRobotIDIssueHandling)
	_, err = d.dingtalkCardClient.CreateAndDeliver(
		appKey,
		&card_1_0.CreateAndDeliverRequest{
			CardTemplateId: tea.String(d.conf.Dingtalk.Card.Template.BaseMsg),
			OutTrackId:     tea.String(outTrackId),
			CallbackType:   tea.String("HTTP"),
			CardData: &card_1_0.CreateAndDeliverRequestCardData{
				CardParamMap: map[string]*string{
					"title":    tea.String("当前机器人能力"),
					"markdown": tea.String("- 聊天内容提取（@机器人 [内容提取]{聊天记录/问答描述}） \n - QA知识收集（卡片信息收集） \n - QA知识问答（@机器人 [知识库查询]{问题描述}）"),
				},
			},
			ImGroupOpenSpaceModel: &card_1_0.CreateAndDeliverRequestImGroupOpenSpaceModel{
				SupportForward: tea.Bool(false),
			},
			OpenSpaceId: tea.String("dtv1.card//im_group." + openConversationId),
			ImGroupOpenDeliverModel: &card_1_0.CreateAndDeliverRequestImGroupOpenDeliverModel{
				RobotCode: tea.String(d.conf.Dingtalk.SceneGroup.GroupTemplateRobotIDIssueHandling),
				AtUserIds: map[string]*string{
					"@ALL": tea.String("@ALL"),
				},
			},
		},
	)
	if err != nil {
		return err
	}

	return nil
}

// buildCreateGroupCardResp 构建关闭创建群组卡片按钮
func (d *DingTalkBotBiz) buildCreateGroupCardResp() *card.CardResponse {
	return &card.CardResponse{
		CardData: &card.CardDataDto{
			CardParamMap: map[string]string{
				"button_display": "false",
			},
		},
		CardUpdateOptions: &card.CardUpdateOptions{
			UpdateCardDataByKey: true,
		},
	}
}
