package com.zioinfo.esn.ai.service;

import com.zioinfo.esn.common.ai.ClaudeTextClient;
import com.zioinfo.esn.common.ai.TextAiClient;
import com.zioinfo.esn.common.ai.TextAiClient.GenResult;
import com.zioinfo.esn.config.OllamaClient;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * AI provider 선택 라우팅(런타임). 평문 텍스트 생성 진입점(알람분석·POS분류·채팅 등). [ZIOINFO-ESN]
 * {@link AiConfigService#provider()} 를 읽어 Claude ↔ Ollama(qwen3/deepseek/glm/기존소형) 를 선택한다.
 *
 * <p><b>추론 폴백 체인</b>(AI_PLATFORM_SPEC §3):
 * <ul>
 *   <li>claude(키·활성) → 실패 시 Ollama {@code qwen3:1.7b} → 실패 시 {@code llama3.2:1b} → degraded.</li>
 *   <li>qwen3/deepseek/glm → 해당 Ollama 모델 → 실패 시 {@code llama3.2:1b} → degraded.</li>
 *   <li>ollama → 설정 모델(기본 llama3.2:1b) → degraded.</li>
 * </ul>
 * Claude 실패는 예외가 아니라 degraded → 다음 단계 폴백(서비스 중단 없음). 모든 시도는 DuckDB
 * {@link LearningStore#logInference} 로 provider/model/latency/degraded 를 기록한다(fail-safe).
 *
 * <p><b>레퍼런스</b>: guardia-ocr {@code ai.service.AiTextRouter} 미러(Ollama 텍스트 경로·2차 폴백·학습로그 치환).
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AiTextRouter implements TextAiClient {

    private static final String FALLBACK_QWEN = "qwen3:1.7b";
    private static final String FALLBACK_SMALL = "llama3.2:1b";

    private final AiConfigService aiConfig;
    private final ClaudeTextClient claudeClient;
    private final OllamaClient ollamaClient;
    private final LearningStore learningStore;

    /**
     * 선택된 provider 로 텍스트를 생성한다. Claude 선택·실패 시 Ollama 폴백. 예외를 던지지 않는다.
     */
    @Override
    public GenResult generate(String prompt) {
        if (aiConfig.isClaudeActive()) {
            String model = aiConfig.claudeModel();
            long start = System.currentTimeMillis();
            GenResult r = claudeClient.generate(prompt, model);
            boolean ok = !r.degraded() && r.text() != null && !r.text().isBlank();
            learningStore.logInference("claude", model, System.currentTimeMillis() - start, !ok);
            if (ok) {
                return r;
            }
            // claude 실패 → 온프레미스 Ollama 자동 폴백(qwen3 → 소형).
            log.warn("Claude path degraded -> Ollama fallback");
            GenResult q = ollamaTry(prompt, FALLBACK_QWEN);
            if (!q.degraded()) {
                return q;
            }
            return ollamaTry(prompt, FALLBACK_SMALL);
        }
        // provider = ollama / qwen3 / deepseek / glm → 해당 Ollama 모델, 실패 시 소형 2차 폴백.
        String model = aiConfig.ollamaTextModel();
        GenResult r = ollamaTry(prompt, model);
        if (!r.degraded()) {
            return r;
        }
        if (!FALLBACK_SMALL.equals(model)) {
            return ollamaTry(prompt, FALLBACK_SMALL);
        }
        return r;
    }

    /** Ollama 평문 generate + 학습로그(실패 시 degraded). */
    private GenResult ollamaTry(String prompt, String model) {
        long start = System.currentTimeMillis();
        String txt = ollamaClient.generate(prompt, model);
        boolean degraded = (txt == null || txt.isBlank());
        learningStore.logInference("ollama", model, System.currentTimeMillis() - start, degraded);
        return degraded ? new GenResult(null, true) : new GenResult(txt, false);
    }
}
