package com.zioinfo.cms.ai.service;

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

/**
 * AI provider 선택 라우팅(런타임). 평문 텍스트 생성 진입점(콘텐츠초안·SEO·번역·리뷰요약·검색 등). [GUARDiA-CMS]
 * {@link AiConfigService#provider()} 를 읽어 Claude ↔ Ollama(qwen3/deepseek/glm/기존소형) 를 선택한다.
 *
 * <p><b>선택/폴백 정책</b>:
 * <ul>
 *   <li>provider=claude · 키 설정됨 · AI 활성 → {@link ClaudeTextClient}. 실패(degraded)면 <b>Ollama 자동 폴백</b>(선택모델).</li>
 *   <li>provider=qwen3/deepseek/glm/ollama → 해당 Ollama 소형 텍스트 모델로 generate.</li>
 *   <li>provider=claude 인데 키 미설정 → 곧장 Ollama(폴백모델).</li>
 * </ul>
 * 두 경로 모두 실패 시 {@code GenResult.degraded=true·text=null} → 호출자가 기존 규칙기반 폴백 유지(무회귀).
 *
 * <p>비전(llava/moondream) 경로는 {@link OllamaClient#vision} 이 담당한다(이 텍스트 라우터를 거치지
 * 않음 — Claude 비전 전환은 본 트랙 범위 밖).
 *
 * <p><b>레퍼런스</b>: guardia-ocr {@code ai.service.AiTextRouter} 미러(OllamaClient 텍스트 경로 치환).
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AiTextRouter implements TextAiClient {

    private final AiConfigService aiConfig;
    private final ClaudeTextClient claudeClient;
    private final OllamaClient ollamaClient;   // 온프레미스 평문 generate
    private final LearningStore learningStore;  // 로컬 DuckDB 추론 로그(best-effort)

    /**
     * 선택된 provider 로 텍스트를 생성한다. Claude 선택·실패 시 Ollama 폴백. 예외를 던지지 않는다.
     * 추론 지연/degraded 를 로컬 DuckDB(ai_infer_log)에 best-effort 기록한다.
     */
    @Override
    public GenResult generate(String prompt) {
        long start = System.currentTimeMillis();
        if (aiConfig.isClaudeActive()) {
            String claudeModel = aiConfig.claudeModel();
            GenResult r = claudeClient.generate(prompt, claudeModel);
            if (!r.degraded() && r.text() != null && !r.text().isBlank()) {
                learningStore.logInfer("claude", claudeModel, System.currentTimeMillis() - start, false);
                return r;
            }
            // claude 실패/빈응답 → 온프레미스 Ollama 자동 폴백(선택모델).
            log.warn("Claude path degraded -> Ollama fallback");
            String om = aiConfig.ollamaTextModel();
            GenResult fb = ollamaGenerate(prompt, om);
            learningStore.logInfer("ollama(claude-fallback)", om, System.currentTimeMillis() - start, fb.degraded());
            return fb;
        }
        // provider = ollama / qwen3 / deepseek / glm → 해당 Ollama 소형 모델
        String provider = aiConfig.provider();
        String om = aiConfig.ollamaTextModel();
        GenResult r = ollamaGenerate(prompt, om);
        learningStore.logInfer(provider, om, System.currentTimeMillis() - start, r.degraded());
        return r;
    }

    /**
     * 라우팅된 텍스트를 String 으로 반환(기존 {@code OllamaClient.generate(prompt)} 호출부 호환용).
     * 실패 시 빈 문자열 — 기존 AiService 폴백이 그대로 동작(무회귀).
     */
    public String generateText(String prompt) {
        GenResult r = generate(prompt);
        return (r == null || r.degraded() || r.text() == null) ? "" : r.text();
    }

    /** Ollama 평문 generate 를 GenResult 로 래핑(실패 시 degraded). */
    private GenResult ollamaGenerate(String prompt, String model) {
        String txt = ollamaClient.generate(prompt, model);
        if (txt == null || txt.isBlank()) {
            return new GenResult(null, true);
        }
        return new GenResult(txt, false);
    }
}
