package com.infoepoch.pms.agent.domain.itworkbench.understanding;

import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.streaming.OutputType;
import com.alibaba.cloud.ai.graph.streaming.StreamingOutput;
import com.infoepoch.pms.agent.common.utils.LogHelper;
import com.infoepoch.pms.agent.config.JsonUtils;
import com.infoepoch.pms.agent.domain.care.log.CareTraceLogSupport;
import com.infoepoch.pms.agent.domain.itworkbench.model.WorkbenchConversationState;
import com.infoepoch.pms.agent.domain.itworkbench.model.kpi.AnnualKpiDto;
import com.infoepoch.pms.agent.domain.itworkbench.model.minutes.DepartmentMinutesDto;
import com.infoepoch.pms.agent.domain.itworkbench.model.department.DepartmentWorkDto;
import com.infoepoch.pms.agent.observability.ai.AiComponentType;
import com.infoepoch.pms.agent.observability.ai.AiModelInvoker;
import com.infoepoch.pms.agent.observability.ai.AiObservationContext;
import com.infoepoch.pms.agent.observability.ai.AiObservationSupport;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import org.springframework.ai.chat.messages.AbstractMessage;

import java.time.LocalDate;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 工作台智能体 - 独立查询解析
 * 解析用户一句话 → 生成对应 DTO
 */
@Service
public class WorkbenchQueryUnderstandingService {

    private final ChatModel chatModel;
    private final ReactAgent careAgent;
    private final AiModelInvoker aiModelInvoker;
    private final AiObservationSupport observationSupport;

    public WorkbenchQueryUnderstandingService(
            @Qualifier("SiliconFlowChatModel") ChatModel chatModel,
            AiModelInvoker aiModelInvoker,
            @Qualifier("WorkbenchCareAgent") ReactAgent careAgent,
            AiObservationSupport observationSupport) {
        this.chatModel = chatModel;
        this.careAgent = careAgent;
        this.aiModelInvoker = aiModelInvoker;
        this.observationSupport = observationSupport;
    }

    public DepartmentWorkDto understandWork(String traceId, String sessionId, String message, WorkbenchConversationState state) {
        LocalDate now = LocalDate.now();
        int currentYear = now.getYear();
        int currentMonth = now.getMonthValue();
        int lastMonthYear = now.minusMonths(1).getYear();
        int lastMonth = now.minusMonths(1).getMonthValue();

        String prompt = """
            你是员工看板重点工作解析器，只输出纯净 JSON，禁止输出任何多余内容！

            【严格遵守以下规则，不准脑补、不准乱赋值】
            1. year 必须有值，不能为 null
               - 未提时间 → 默认当前年 %d，当前月 %d
               - 本月 → %d年%d月
               - 上月/上个月 → %d年%d月
               - 只提月份没提年份 →  年份默认当前年 %d 

            2. levelType 严格规则：
               - 明确说 A → 1
               - 明确说 B → 2
               - 明确说 C → 3
               - 明确说 H → 4
               - 明确说 其他 → -1
               - 什么都没提 → null

            3. categoryContain：
               只有用户明确说【类型为 XXX】才赋值，否则必须为 null

            4. workGroupContain：
               只有用户明确提到【XX 室】才赋值（如：运营管理室、规划建设室）
               否则必须为 null

            5. managerUser：
               只有明确提到具体人名才赋值，否则 null

            6. isAnnual：
               提到年度工作 → true
               提到非年度/月度 → false
               未提到 → null

            7. isFill：
               提到已填报 → true
               提到未填报 → false
               未提到 → null

            8. finish：
               提到已完成/完成情况 → "已完成"
               提到未完成/未完成情况 → "未完成"
               提到滞后 → "滞后"
               未提到 → null

            9. 未提到的字段必须为 null，绝对不能自动填充！
            10. 禁止生成不存在的字段，如 type

            用户输入：%s
            """.formatted(
                currentYear, currentMonth,
                currentYear, currentMonth,
                lastMonthYear, lastMonth,
                currentYear, message
        );

        AiObservationContext ctx = buildCtx(traceId, sessionId, "重点工作解析");
        String json = aiModelInvoker.call(ctx, prompt, chatModel);
        String clean = sanitizeJson(json);
        DepartmentWorkDto dto = JsonUtils.jsonToObject(clean, DepartmentWorkDto.class);
        if (dto == null) dto = new DepartmentWorkDto();

        return dto;
    }


    public AnnualKpiDto understandKpi(String traceId, String sessionId, String message, WorkbenchConversationState state) {
        LocalDate now = LocalDate.now();
        int currentYear = now.getYear();
        int currentMonth = now.getMonthValue();
        int lastMonthYear = now.minusMonths(1).getYear();
        int lastMonth = now.minusMonths(1).getMonthValue();

        String prompt = """
            你是员工看板 Kpi 解析器，只输出纯净 JSON，禁止输出任何多余内容！

            【严格遵守以下规则，不准脑补、不准乱赋值】
            1. year必须有值，不能为 null
               - 未提时间 → 默认当前年 %d，当前月 %d
               - 本月 → %d年%d月
               - 上月/上个月 → %d年%d月
               - 只提月份没提年份 →  年份默认当前年 %d 

            2. levelType 严格规则：
               - 明确说 A → 1
               - 明确说 B → 2
               - 明确说 C → 3
               - 明确说 其他 → -1
               - 什么都没提 → null

            3. categoryContain：
               只有用户明确说【类型为 XXX】才赋值，否则必须为 null

            4. workGroupContain：
               只有用户明确提到【XX 室】才赋值（如：运营管理室、规划建设室）
               否则必须为 null

            5. managerUser：
               只有明确提到具体人名才赋值，否则 null


            6. isFill：
               提到已填报 → true
               提到未填报 → false
               未提到 → null

            7. isFinish：
               提到已完成 → true
               提到未完成 → false
               未提到 → null

            8. 未提到的字段必须为 null，绝对不能自动填充！
            9. 禁止生成不存在的字段，如 type

            用户输入：%s
            """.formatted(
                currentYear, currentMonth,
                currentYear, currentMonth,
                lastMonthYear, lastMonth,
                currentYear,
                message
        );

        AiObservationContext ctx = buildCtx(traceId, sessionId, "KPI 解析");
        String json = aiModelInvoker.call(ctx, prompt, chatModel);
        String clean = sanitizeJson(json);
        AnnualKpiDto dto = JsonUtils.jsonToObject(clean, AnnualKpiDto.class);
        if (dto == null) dto = new AnnualKpiDto();

        return dto;
    }


    public DepartmentMinutesDto understandMinutes(String traceId, String sessionId, String message, WorkbenchConversationState state) {
        LocalDate now = LocalDate.now();
        int currentYear = now.getYear();
        int currentMonth = now.getMonthValue();
        int lastMonthYear = now.minusMonths(1).getYear();
        int lastMonth = now.minusMonths(1).getMonthValue();

        String prompt = """
                你是员工看板会议纪要解析器，只输出纯净 JSON，禁止输出任何多余内容！

                【严格遵守以下规则，不准脑补、不准乱赋值】
                1. currentYear必须有值，不能为 null
                   currentYear -> 年  month -> 月
                   - 未提时间 → 默认当前年 %d，当前月 %d
                   - 本月 → %d年%d月
                   - 只提月份没提年份 →  年份默认当前年 %d 
                   - 上月/上个月 → %d年%d月

                2. categoryContain：
                   只有用户明确说【会议名称为 XXX】才赋值，否则必须为 null

                3. documentNumber：
                   只有用户明确说【文号为 XXX】才赋值，否则必须为 null

                4. workGroup：
                   只有用户明确提到【XX 室】才赋值（如：运营管理室、规划建设室）
                   否则必须为 null

                5. managerName：
                   只有明确提到具体人名才赋值，否则 null


                6. isFill：
                   提到已填报 → true
                   提到未填报 → false
                   未提到 → null

                7. latestResult：
                   提到已完成 → "已完成"
                   提到推进中 → "推进中"
                   提到滞后 → "滞后"
                   未提到 → null
                    
                8.【要求完成时间 规则】
                    用户提到“要求完成时间”“完成时间范围”“完成时间在 XX 到 XX"：
                    - 开始时间 → startRequireCompletionTime
                    - 结束时间 → endRequireCompletionTime
                    时间格式统一为：YYYY-MM-DD
                    没提到范围 → 这两个字段都为 null 

                9. 未提到的字段必须为 null，绝对不能自动填充！
                10. 禁止生成不存在的字段，如 type

                用户输入：%s
                """.formatted(
                currentYear, currentMonth,
                currentYear, currentMonth,
                currentYear,
                lastMonthYear, lastMonth,
                message
        );

        AiObservationContext ctx = buildCtx(traceId, sessionId, "会议纪要解析");
        String json = aiModelInvoker.call(ctx, prompt, chatModel);
        String clean = sanitizeJson(json);
        DepartmentMinutesDto dto = JsonUtils.jsonToObject(clean, DepartmentMinutesDto.class);
        if (dto == null) dto = new DepartmentMinutesDto();

        return dto;
    }

    public Flux<String> generateAnswer(String traceId, String sessionId, String data) {
        try {
            RunnableConfig runnableConfig = buildRunnableConfig(traceId, sessionId, "员工看板数据查询");
            AtomicInteger chunkIndex = new AtomicInteger(0);
            return careAgent.stream(data, runnableConfig)
                    .filter(StreamingOutput.class::isInstance)
                    .map(StreamingOutput.class::cast)
                    .filter(output -> output.getOutputType() == OutputType.AGENT_MODEL_STREAMING)
                    .map(StreamingOutput::message)
                    .filter(AssistantMessage.class::isInstance)
                    .map(AssistantMessage.class::cast)
                    .map(AbstractMessage::getText)
                    .filter(StringUtils::hasText)
                    .doOnNext(chunk -> chunkIndex.incrementAndGet())
                    .switchIfEmpty(Flux.defer(() -> {
                        LogHelper.info(this, CareTraceLogSupport.format(
                                traceId, sessionId, "workbench_fallback", "模型未返回内容"
                        ));
                        return Flux.just("数据查询完成，但未生成有效回答。");
                    }))
                    .doOnComplete(() -> {
                        LogHelper.info(this, CareTraceLogSupport.format(
                                traceId, sessionId, "workbench_complete", "流式输出完成，chunk 数=" + chunkIndex.get()
                        ));
                    })
                    .doOnError(error -> LogHelper.error(this, CareTraceLogSupport.format(
                            traceId, sessionId, "workbench_error",
                            "流式异常：" + error.getMessage() + "，chunk 数=" + chunkIndex.get()
                    ), error));
        } catch (Exception e) {
            LogHelper.error(this, CareTraceLogSupport.format(
                    traceId,
                    sessionId,
                    "流式请求异常",
                    "通用对话立即失败，异常=" + CareTraceLogSupport.safeText(e.getMessage())
            ), e);
            return Flux.error(new IllegalArgumentException("调用模型失败"));
        }
    }

    /**
     * 基于上一轮历史数据进行多轮筛选（不查库）
     * @param traceId 追踪 ID
     * @param sessionId 会话 ID
     * @param historyDataJson 上一轮查询的原始数据 JSON
     * @param dataType 数据类型（重点工作/部门 KPI/会议跟踪）
     * @param newQuestion 用户新的筛选问题
     * @return 流式响应
     */
    public Flux<String> generateAnswerWithHistory(String traceId, String sessionId, String historyDataJson, String dataType, String newQuestion) {
        try {
            String prompt = """
                    你是员工看板数据助手。用户之前查询了【%s】，现在基于原有数据提出新的筛选要求：【%s】
                    
                    原始数据如下：
                    %s
                    
                    请根据用户的新要求，从原始数据中筛选出符合条件的内容，并以自然语言展示。
                    如果无法筛选出结果，请友好地告知用户。
                    
                    只输出结果，不要输出思考过程。
                    """.formatted(dataType, newQuestion, historyDataJson);

            RunnableConfig runnableConfig = buildRunnableConfig(traceId, sessionId, "员工看板多轮筛选");
            AtomicInteger chunkIndex = new AtomicInteger(0);
            return careAgent.stream(prompt, runnableConfig)
                    .filter(StreamingOutput.class::isInstance)
                    .map(StreamingOutput.class::cast)
                    .filter(output -> output.getOutputType() == OutputType.AGENT_MODEL_STREAMING)
                    .map(StreamingOutput::message)
                    .filter(AssistantMessage.class::isInstance)
                    .map(AssistantMessage.class::cast)
                    .map(AbstractMessage::getText)
                    .filter(StringUtils::hasText)
                    .doOnNext(chunk -> chunkIndex.incrementAndGet())
                    .switchIfEmpty(Flux.defer(() -> {
                        LogHelper.info(this, CareTraceLogSupport.format(
                                traceId, sessionId, "workbench_multi_turn_fallback", "模型未返回内容"
                        ));
                        return Flux.just("筛选完成，但未生成有效回答。");
                    }))
                    .doOnComplete(() -> {
                        LogHelper.info(this, CareTraceLogSupport.format(
                                traceId, sessionId, "workbench_multi_turn_complete", "多轮筛选完成，chunk 数=" + chunkIndex.get()
                        ));
                    })
                    .doOnError(error -> LogHelper.error(this, CareTraceLogSupport.format(
                            traceId, sessionId, "workbench_multi_turn_error",
                            "多轮筛选异常：" + error.getMessage() + "，chunk 数=" + chunkIndex.get()
                    ), error));
        } catch (Exception e) {
            LogHelper.error(this, CareTraceLogSupport.format(
                    traceId,
                    sessionId,
                    "多轮筛选异常",
                    "多轮筛选失败，异常=" + CareTraceLogSupport.safeText(e.getMessage())
            ), e);
            return Flux.error(new IllegalArgumentException("多轮筛选失败"));
        }
    }

    private RunnableConfig buildRunnableConfig(String traceId, String sessionId, String scene) {
        AiObservationContext currentContext = observationSupport.currentContext();
        String requestId = StringUtils.hasText(currentContext.requestId()) ? currentContext.requestId() : traceId;
        String flowId = StringUtils.hasText(currentContext.flowId()) ? currentContext.flowId() : requestId;
        AiObservationContext context = currentContext.toBuilder()
                .traceId(traceId)
                .requestId(requestId)
                .sessionId(sessionId)
                .flowId(flowId)
                .scene(scene)
                .componentType(AiComponentType.AGENT)
                .componentName("workbench.understanding")
                .agentName("WorkbenchCareAgent")
                .build();
        return observationSupport.buildRunnableConfig(sessionId, context);
    }

    private AiObservationContext buildCtx(String traceId, String sessionId, String scene) {
        return observationSupport.currentContext().toBuilder()
                .traceId(traceId)
                .sessionId(sessionId)
                .scene(scene)
                .componentType(AiComponentType.MODEL)
                .componentName("workbench.understanding")
                .modelProvider("siliconflow")
                .build();
    }

    private String sanitizeJson(String text) {
        if (text == null) return "";
        String s = text.replace("```json", "").replace("```", "").trim();
        int l = s.indexOf('{');
        int r = s.lastIndexOf('}');
        if (l >= 0 && r > l) return s.substring(l, r + 1);
        return "";
    }
}
