package com.cmvr.test.flow.runtime.operator.llm;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.cmvr.common.exception.GlobalException;
import com.cmvr.llm.service.LLMAiAgentPlatformService;
import com.cmvr.test.enums.ActionEnum;
import com.cmvr.test.flow.runtime.message.TaskNodeExecuteMessage;
import com.cmvr.test.flow.runtime.message.TaskNodeExecuteResult;
import com.cmvr.test.model.vo.inspection.InspectionAlarmRuleVO;
import com.cmvr.test.model.vo.inspection.InspectionMeterRecognizeConfigVO;
import com.cmvr.test.service.ShangdaoFileService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 巡检仪表读数识别节点。
 *
 * <p>输入参数见{@link InspectionMeterRecognizeConfigVO}。节点先将MinIO图片上传商道，
 * 再调用固定智能体识别读数；识别成功后执行全部告警规则，命中多条规则时取最高级别。</p>
 *
 * <p>节点输出包含value、unit、resultStatus和alarmLevel，同时通过内部字段
 * {@code _inspectionResult}交给巡检事件监听器持久化。该内部字段不需要前端配置。</p>
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class InspectionMeterRecognizeOperateService implements LLMOperateService
{
    private static final String API_KEY = "d9gqfkd4shheenomcfcg";
    private static final String RESULT_MARKER = "_inspectionResult";
    private static final Pattern NUMBER_PATTERN =
            Pattern.compile("[-+]?(?:\\d+(?:\\.\\d+)?|\\.\\d+)");

    private final ShangdaoFileService shangdaoFileService;
    private final LLMAiAgentPlatformService llmAiAgentPlatformService;

    @Override
    public boolean supports(ActionEnum action)
    {
        return ActionEnum.INSPECTION_METER_RECOGNIZE.equals(action);
    }

    @Override
    public TaskNodeExecuteResult execute(TaskNodeExecuteMessage message)
    {
        JSONObject input = message.getInputParams() == null ? new JSONObject() : message.getInputParams();
        InspectionMeterRecognizeConfigVO config = input.to(InspectionMeterRecognizeConfigVO.class);
        String imageUrl = StringUtils.trimToNull(config.getImageUrl());
        String resultName = StringUtils.defaultIfBlank(config.getResultName(), "仪表读数");
        String configuredUnit = StringUtils.trimToNull(config.getUnit());
        JSONArray ruleArray = input.getJSONArray("alarmRules");
        List<AlarmRule> rules = parseRules(config.getAlarmRules());

        // 商道文件地址有效期较短，仅用于本次识别；长期证据仍保存原始MinIO地址。
        String shangdaoUrl = shangdaoFileService.uploadFromMinio(imageUrl);
        JSONObject response = llmAiAgentPlatformService.query(
                ActionEnum.INSPECTION_METER_RECOGNIZE.getAction(), shangdaoUrl,
                API_KEY, Boolean.FALSE, new JSONObject());
        String rawResult = response == null ? null : response.getString("result");

        Reading reading = parseReading(rawResult, configuredUnit);
        JSONObject output = new JSONObject();
        output.put("imageUrl", imageUrl);
        output.put("rawResult", rawResult);
        output.put("resultName", resultName);

        JSONObject inspectionResult = new JSONObject();
        inspectionResult.put("resultType", "METER");
        inspectionResult.put("resultName", resultName);
        inspectionResult.put("evidenceUrl", imageUrl);
        inspectionResult.put("evidenceType", "IMAGE");
        inspectionResult.put("rawResult", rawResult);
        inspectionResult.put("ruleSnapshot", ruleArray == null ? new JSONArray() : ruleArray);

        // 无法提取数值时保留原始响应并进入人工复核，不执行阈值规则，避免误报警。
        if (reading == null)
        {
            output.put("resultStatus", "RECOGNIZE_FAILED");
            inspectionResult.put("resultStatus", "RECOGNIZE_FAILED");
            inspectionResult.put("valueText", rawResult);
            inspectionResult.put("alarmMessage", "商道返回结果无法解析为仪表读数");
            output.put(RESULT_MARKER, inspectionResult);
            return TaskNodeExecuteResult.success(output);
        }

        AlarmRule matchedRule = matchHighestRule(reading.value, rules);
        String resultStatus = matchedRule == null ? "NORMAL" : "ABNORMAL";
        String unit = StringUtils.defaultIfBlank(configuredUnit, reading.unit);
        output.put("value", reading.value);
        output.put("unit", unit);
        output.put("resultStatus", resultStatus);
        output.put("alarmLevel", matchedRule == null ? null : matchedRule.level);

        inspectionResult.put("resultStatus", resultStatus);
        inspectionResult.put("valueNumber", reading.value);
        inspectionResult.put("valueText", reading.value.toPlainString());
        inspectionResult.put("unit", unit);
        if (matchedRule != null)
        {
            inspectionResult.put("alarmLevel", matchedRule.level);
            inspectionResult.put("alarmMessage", matchedRule.message);
            inspectionResult.put("matchedRule", matchedRule.source);
        }
        output.put(RESULT_MARKER, inspectionResult);
        return TaskNodeExecuteResult.success(output);
    }

    /**
     * 校验并转换前端配置的多级告警规则。
     */
    private List<AlarmRule> parseRules(List<InspectionAlarmRuleVO> sourceRules)
    {
        List<AlarmRule> result = new ArrayList<>();
        if (sourceRules == null)
        {
            return result;
        }
        for (int i = 0; i < sourceRules.size(); i++)
        {
            InspectionAlarmRuleVO source = sourceRules.get(i);
            if (source == null)
            {
                throw new GlobalException("alarmRules[" + i + "]格式错误");
            }
            Integer level = source.getLevel();
            if (level == null || level < 1 || level > 3)
            {
                throw new GlobalException("alarmRules[" + i + "].level只能为1、2、3");
            }
            String operator = StringUtils.upperCase(StringUtils.trimToEmpty(source.getOperator()));
            validateOperator(operator, i);

            AlarmRule rule = new AlarmRule();
            rule.level = level;
            rule.operator = operator;
            rule.threshold = source.getThreshold();
            rule.minValue = source.getMinValue();
            rule.maxValue = source.getMaxValue();
            rule.includeMin = source.getIncludeMin() == null || source.getIncludeMin();
            rule.includeMax = source.getIncludeMax() == null || source.getIncludeMax();
            rule.message = StringUtils.defaultIfBlank(source.getMessage(), "仪表读数触发告警");
            rule.source = JSON.parseObject(JSON.toJSONString(source));
            validateThreshold(rule, i);
            result.add(rule);
        }
        return result;
    }

    private void validateOperator(String operator, int index)
    {
        if (!("GT".equals(operator) || "GTE".equals(operator) || "LT".equals(operator)
                || "LTE".equals(operator) || "EQ".equals(operator) || "BETWEEN".equals(operator)
                || "NOT_BETWEEN".equals(operator)))
        {
            throw new GlobalException("alarmRules[" + index + "].operator不支持: " + operator);
        }
    }

    private void validateThreshold(AlarmRule rule, int index)
    {
        boolean between = "BETWEEN".equals(rule.operator) || "NOT_BETWEEN".equals(rule.operator);
        if (between)
        {
            if (rule.minValue == null || rule.maxValue == null || rule.minValue.compareTo(rule.maxValue) > 0)
            {
                throw new GlobalException("alarmRules[" + index + "]区间上下界配置错误");
            }
        }
        else if (rule.threshold == null)
        {
            throw new GlobalException("alarmRules[" + index + "].threshold不能为空");
        }
    }

    /** 命中多条重叠规则时按level取最高严重级别，与前端数组顺序无关。 */
    private AlarmRule matchHighestRule(BigDecimal value, List<AlarmRule> rules)
    {
        return rules.stream()
                .filter(rule -> rule.matches(value))
                .max(Comparator.comparingInt(rule -> rule.level))
                .orElse(null);
    }

    /**
     * 解析商道结果：优先读取标准JSON字段，兼容纯文本数字返回。
     */
    private Reading parseReading(String rawResult, String configuredUnit)
    {
        String text = StringUtils.trimToNull(rawResult);
        if (text == null)
        {
            return null;
        }
        String cleaned = text.replace("```json", "").replace("```", "").trim();
        try
        {
            Object parsed = JSON.parse(cleaned);
            if (parsed instanceof JSONObject)
            {
                JSONObject object = (JSONObject) parsed;
                BigDecimal value = firstDecimal(object, "value", "reading", "meterValue", "result");
                if (value != null)
                {
                    return new Reading(value,
                            StringUtils.defaultIfBlank(object.getString("unit"), configuredUnit));
                }
            }
        }
        catch (Exception ignored)
        {
            log.debug("商道仪表结果不是标准JSON，尝试提取数字，rawResult={}", text);
        }

        Matcher matcher = NUMBER_PATTERN.matcher(cleaned);
        if (!matcher.find())
        {
            return null;
        }
        try
        {
            return new Reading(new BigDecimal(matcher.group()), configuredUnit);
        }
        catch (NumberFormatException ex)
        {
            return null;
        }
    }

    private BigDecimal firstDecimal(JSONObject object, String... keys)
    {
        for (String key : keys)
        {
            Object value = object.get(key);
            if (value instanceof Number || value instanceof String)
            {
                try
                {
                    return new BigDecimal(String.valueOf(value));
                }
                catch (NumberFormatException ignored)
                {
                    // 继续尝试下一个标准字段。
                }
            }
        }
        return null;
    }

    private static final class Reading
    {
        private final BigDecimal value;
        private final String unit;

        private Reading(BigDecimal value, String unit)
        {
            this.value = value;
            this.unit = unit;
        }
    }

    private static final class AlarmRule
    {
        private int level;
        private String operator;
        private BigDecimal threshold;
        private BigDecimal minValue;
        private BigDecimal maxValue;
        private boolean includeMin;
        private boolean includeMax;
        private String message;
        private JSONObject source;

        private boolean matches(BigDecimal value)
        {
            int thresholdCompare = threshold == null ? 0 : value.compareTo(threshold);
            switch (operator.toUpperCase(Locale.ROOT))
            {
                case "GT": return thresholdCompare > 0;
                case "GTE": return thresholdCompare >= 0;
                case "LT": return thresholdCompare < 0;
                case "LTE": return thresholdCompare <= 0;
                case "EQ": return thresholdCompare == 0;
                case "BETWEEN": return within(value);
                case "NOT_BETWEEN": return !within(value);
                default: return false;
            }
        }

        private boolean within(BigDecimal value)
        {
            int minCompare = value.compareTo(minValue);
            int maxCompare = value.compareTo(maxValue);
            boolean lowerMatched = includeMin ? minCompare >= 0 : minCompare > 0;
            boolean upperMatched = includeMax ? maxCompare <= 0 : maxCompare < 0;
            return lowerMatched && upperMatched;
        }
    }
}
