package com.sino.mci.inbound.intent;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.sino.mci.admin.entity.Tenant;
import com.sino.mci.admin.repository.TenantMapper;
import com.sino.mci.shared.util.JsonUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 规则 + LLM 意图识别服务。
 *
 * <p>优先使用租户自定义规则（配置在 {@code mci_tenant.ext_info.intent_rules}），
 * 其次使用默认规则；规则未命中时调用 LLM；LLM 未配置时返回 {@code unknown}。</p>
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class IntentRecognitionService {

    private static final double RULE_CONFIDENCE_THRESHOLD = 0.9;

    private static final List<IntentRule> DEFAULT_RULES = List.of(
            new IntentRule(List.of("下单", "我要下单"), "create_order", 1.0,
                    List.of("customer_name", "address", "phone")),
            new IntentRule(List.of("查状态", "状态"), "query_status", 1.0, List.of())
    );

    private static final Pattern CUSTOMER_NAME_PATTERN = Pattern.compile("客户\\s*[:：]?\\s*([^\\s,，]+)");
    private static final Pattern ADDRESS_PATTERN = Pattern.compile("(?:地址|在)\\s*[:：]?\\s*([^\\s,，]+)");
    private static final Pattern PHONE_PATTERN = Pattern.compile("(1[3-9]\\d{9})");

    private final TenantMapper tenantMapper;

    @Autowired(required = false)
    private ChatClient.Builder chatClientBuilder;

    @Value("${spring.ai.openai.api-key:}")
    private String apiKey;

    /**
     * 识别用户消息的意图。
     *
     * @param tenantCode     租户编码
     * @param text           用户消息文本
     * @param businessContext 业务上下文
     * @return 意图识别结果
     */
    public IntentResult recognize(String tenantCode, String text, Map<String, Object> businessContext) {
        if (text == null || text.isBlank()) {
            return unknownResult();
        }

        List<IntentRule> rules = resolveRules(tenantCode);
        for (IntentRule rule : rules) {
            if (matches(rule, text)) {
                Map<String, String> slots = extractSlots(text);
                List<String> missingSlots = resolveMissingSlots(rule, slots);
                return new IntentResult(rule.intent(), rule.confidence(), slots,
                        missingSlots, !missingSlots.isEmpty());
            }
        }

        if (!isLlmConfigured()) {
            log.debug("No rule matched and LLM is not configured, returning unknown intent");
            return unknownResult();
        }

        return callLlm(text, businessContext);
    }

    private List<IntentRule> resolveRules(String tenantCode) {
        List<IntentRule> rules = new ArrayList<>(loadTenantRules(tenantCode));
        rules.addAll(DEFAULT_RULES);
        return rules;
    }

    private List<IntentRule> loadTenantRules(String tenantCode) {
        Tenant tenant;
        try {
            tenant = tenantMapper.selectOne(
                    new LambdaQueryWrapper<Tenant>().eq(Tenant::getTenantCode, tenantCode));
        } catch (Exception e) {
            log.warn("Failed to load tenant rules for {}", tenantCode, e);
            return List.of();
        }
        if (tenant == null || tenant.getExtInfo() == null || tenant.getExtInfo().isBlank()) {
            return List.of();
        }

        Map<String, Object> ext = JsonUtils.fromJson(tenant.getExtInfo(), Map.class);
        if (ext == null) {
            return List.of();
        }

        Object rulesObj = ext.get("intent_rules");
        if (!(rulesObj instanceof List<?>)) {
            return List.of();
        }

        List<IntentRule> rules = new ArrayList<>();
        for (Object item : (List<?>) rulesObj) {
            if (!(item instanceof Map<?, ?> map)) {
                continue;
            }
            List<String> keywords = toStringList(map.get("keywords"));
            String intent = toString(map.get("intent"));
            Double confidence = toDouble(map.get("confidence"));
            List<String> expectedSlots = toStringList(map.get("expectedSlots"));
            if (intent != null && !intent.isBlank() && !keywords.isEmpty()) {
                rules.add(new IntentRule(keywords, intent,
                        confidence != null ? confidence : 1.0,
                        expectedSlots));
            }
        }
        return rules;
    }

    private boolean matches(IntentRule rule, String text) {
        for (String keyword : rule.keywords()) {
            if (text.contains(keyword)) {
                return true;
            }
        }
        return false;
    }

    private Map<String, String> extractSlots(String text) {
        Map<String, String> slots = new LinkedHashMap<>();

        Matcher customerMatcher = CUSTOMER_NAME_PATTERN.matcher(text);
        if (customerMatcher.find()) {
            slots.put("customer_name", customerMatcher.group(1).trim());
        }

        Matcher addressMatcher = ADDRESS_PATTERN.matcher(text);
        if (addressMatcher.find()) {
            slots.put("address", addressMatcher.group(1).trim());
        }

        Matcher phoneMatcher = PHONE_PATTERN.matcher(text);
        if (phoneMatcher.find()) {
            slots.put("phone", phoneMatcher.group(1).trim());
        }

        return slots;
    }

    private List<String> resolveMissingSlots(IntentRule rule, Map<String, String> slots) {
        List<String> missing = new ArrayList<>();
        for (String slot : rule.expectedSlots()) {
            if (!slots.containsKey(slot) || slots.get(slot) == null || slots.get(slot).isBlank()) {
                missing.add(slot);
            }
        }
        return missing;
    }

    private boolean isLlmConfigured() {
        return chatClientBuilder != null && apiKey != null && !apiKey.isBlank();
    }

    private IntentResult callLlm(String text, Map<String, Object> businessContext) {
        String prompt = buildPrompt(text, businessContext);
        try {
            String content = chatClientBuilder.build()
                    .prompt()
                    .user(prompt)
                    .call()
                    .content();
            return parseLlmResponse(content);
        } catch (Exception e) {
            log.warn("LLM intent recognition failed: {}", e.getMessage());
            return unknownResult();
        }
    }

    private String buildPrompt(String text, Map<String, Object> businessContext) {
        return "You are an intent classifier. Given the user message and context, output ONLY a JSON object "
                + "with fields: intent, confidence (0-1), slots (object), missingSlots (array), "
                + "requiresConfirmation (boolean).\n\n"
                + "User message: " + text + "\n"
                + "Context: " + JsonUtils.toJson(businessContext);
    }

    @SuppressWarnings("unchecked")
    private IntentResult parseLlmResponse(String content) {
        if (content == null || content.isBlank()) {
            return unknownResult();
        }
        String json = stripMarkdownCodeBlock(content);
        Map<String, Object> map = JsonUtils.fromJson(json, Map.class);
        if (map == null) {
            return unknownResult();
        }

        String intent = toString(map.get("intent"));
        if (intent == null || intent.isBlank()) {
            intent = "unknown";
        }
        double confidence = toDouble(map.get("confidence"));
        Map<String, String> slots = toStringMap(map.get("slots"));
        List<String> missingSlots = toStringList(map.get("missingSlots"));
        boolean requiresConfirmation = Boolean.TRUE.equals(map.get("requiresConfirmation"));

        return new IntentResult(intent, confidence, slots, missingSlots, requiresConfirmation);
    }

    private String stripMarkdownCodeBlock(String content) {
        String trimmed = content.trim();
        if (trimmed.startsWith("```")) {
            int firstNewline = trimmed.indexOf('\n');
            int lastFence = trimmed.lastIndexOf("```");
            if (firstNewline > 0 && lastFence > firstNewline) {
                return trimmed.substring(firstNewline + 1, lastFence).trim();
            }
        }
        return trimmed;
    }

    private IntentResult unknownResult() {
        return new IntentResult("unknown", 0.0, Map.of(), List.of(), false);
    }

    private static String toString(Object value) {
        return value != null ? value.toString() : null;
    }

    private static Double toDouble(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Number number) {
            return number.doubleValue();
        }
        try {
            return Double.parseDouble(value.toString());
        } catch (NumberFormatException e) {
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    private static List<String> toStringList(Object value) {
        if (!(value instanceof Collection<?> collection)) {
            return List.of();
        }
        List<String> result = new ArrayList<>();
        for (Object item : collection) {
            if (item != null) {
                result.add(item.toString());
            }
        }
        return result;
    }

    @SuppressWarnings("unchecked")
    private static Map<String, String> toStringMap(Object value) {
        if (!(value instanceof Map<?, ?> map)) {
            return new HashMap<>();
        }
        Map<String, String> result = new LinkedHashMap<>();
        for (Map.Entry<?, ?> entry : map.entrySet()) {
            if (entry.getValue() != null) {
                result.put(entry.getKey().toString(), entry.getValue().toString());
            }
        }
        return result;
    }

    private record IntentRule(List<String> keywords, String intent, double confidence,
                              List<String> expectedSlots) {
    }
}
