package com.zioinfo.kintex.ai;

import com.zioinfo.kintex.ai.dto.AiConfigDto;
import com.zioinfo.kintex.ai.dto.AiConfigUpdateRequest;
import com.zioinfo.kintex.common.error.ApiException;
import com.zioinfo.kintex.common.error.ErrorCode;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * F100 AI provider 런타임 설정({@code ai_config}) 단일 출처. DB 우선 → 미설정 시 기본값 폴백.
 *
 * <p><b>보안</b>: 이 서비스는 Claude API 키를 다루지 않는다. {@link #claudeKeySet()} 은
 * {@link ClaudeTextClient#isConfigured()}(env 존재 여부)만 반환 — 값/길이/마스킹 일절 미포함.
 * 설정 테이블에도 키는 저장하지 않는다(provider/모델/활성화 플래그만).
 */
@Service
public class AiConfigService {

    public static final String K_PROVIDER = "ai.provider";        // ENUM: claude|ollama
    public static final String K_CLAUDE_MODEL = "ai.claude.model"; // ENUM: 화이트리스트
    public static final String K_OLLAMA_MODEL = "ai.ollama.model"; // STRING
    public static final String K_ENABLED = "ai.enabled";           // BOOL

    public static final String PROVIDER_CLAUDE = "claude";
    public static final String PROVIDER_OLLAMA = "ollama";
    public static final String DEFAULT_CLAUDE_MODEL = "claude-sonnet-4-6";
    public static final String DEFAULT_OLLAMA_MODEL = "qwen3:1.7b";

    public static final Set<String> ALLOWED_PROVIDERS = Set.of(PROVIDER_CLAUDE, PROVIDER_OLLAMA);
    /** 허용 Claude 모델 ID(임의 문자열 거부). */
    public static final Set<String> ALLOWED_CLAUDE_MODELS =
            Set.of("claude-sonnet-4-6", "claude-haiku-4-5", "claude-opus-4-8");

    private final AiConfigMapper mapper;
    private final ClaudeTextClient claudeClient;
    private final OllamaTextClient ollamaClient;

    public AiConfigService(AiConfigMapper mapper, ClaudeTextClient claudeClient, OllamaTextClient ollamaClient) {
        this.mapper = mapper;
        this.claudeClient = claudeClient;
        this.ollamaClient = ollamaClient;
    }

    // ── 효과값(DB 우선 → 기본) ──

    /** 효과 provider("claude"|"ollama"). 기본 claude(키 미설정이면 라우터가 Ollama 폴백). */
    public String provider() {
        String v = dbVal(K_PROVIDER);
        if (v != null) {
            String p = v.trim().toLowerCase();
            if (ALLOWED_PROVIDERS.contains(p)) {
                return p;
            }
        }
        return PROVIDER_CLAUDE;
    }

    /** 효과 Claude 모델(DB 우선, 미설정/비허용 시 기본). */
    public String claudeModel() {
        String v = dbVal(K_CLAUDE_MODEL);
        if (v != null && ALLOWED_CLAUDE_MODELS.contains(v.trim())) {
            return v.trim();
        }
        return DEFAULT_CLAUDE_MODEL;
    }

    /** 효과 Ollama 모델 태그(DB 우선, 미설정 시 기본 소형). */
    public String ollamaModel() {
        String v = dbVal(K_OLLAMA_MODEL);
        return (v != null && !v.isBlank()) ? v.trim() : DEFAULT_OLLAMA_MODEL;
    }

    /** 전역 AI 토글(기본 true). false 면 모든 AI 결과는 degraded → 호출자 폴백. */
    public boolean aiEnabled() {
        String v = dbVal(K_ENABLED);
        return v == null || !"false".equalsIgnoreCase(v.trim());
    }

    /** Claude API 키가 env 로 설정되어 있는지(여부만). */
    public boolean claudeKeySet() {
        return claudeClient.isConfigured();
    }

    /** Ollama 폴백 URL 이 env 로 설정되어 있는지(여부만). */
    public boolean ollamaConfigured() {
        return ollamaClient.isConfigured();
    }

    /** 실제 호출이 Claude 경로로 가야 하는지: provider=claude · 키 설정 · AI 활성. */
    public boolean isClaudeActive() {
        return PROVIDER_CLAUDE.equals(provider()) && claudeKeySet() && aiEnabled();
    }

    // ── ADMIN 조회/갱신 ──

    public AiConfigDto getConfig() {
        return new AiConfigDto(provider(), claudeModel(), ollamaModel(),
                aiEnabled(), claudeKeySet(), ollamaConfigured());
    }

    /**
     * 설정 갱신(upsert). 화이트리스트 검증: provider∈{claude,ollama}, claudeModel∈허용셋.
     * 각 필드 선택(null/공백 = 기존 유지). 재기동 없이 다음 호출부터 반영.
     */
    public AiConfigDto update(AiConfigUpdateRequest req, String actor) {
        String act = (actor == null || actor.isBlank()) ? "SYSTEM" : actor;
        if (req.provider() != null && !req.provider().isBlank()) {
            String p = req.provider().trim().toLowerCase();
            if (!ALLOWED_PROVIDERS.contains(p)) {
                throw new ApiException(ErrorCode.VALIDATION, "provider 는 claude 또는 ollama 여야 합니다.");
            }
            mapper.upsert(K_PROVIDER, p, act);
        }
        if (req.claudeModel() != null && !req.claudeModel().isBlank()) {
            String m = req.claudeModel().trim();
            if (!ALLOWED_CLAUDE_MODELS.contains(m)) {
                throw new ApiException(ErrorCode.VALIDATION, "허용되지 않은 Claude 모델 ID 입니다.");
            }
            mapper.upsert(K_CLAUDE_MODEL, m, act);
        }
        if (req.ollamaModel() != null && !req.ollamaModel().isBlank()) {
            mapper.upsert(K_OLLAMA_MODEL, req.ollamaModel().trim(), act);
        }
        if (req.aiEnabled() != null) {
            mapper.upsert(K_ENABLED, req.aiEnabled() ? "true" : "false", act);
        }
        return getConfig();
    }

    // ── helpers ──

    /** DB 값 로드(전체 조회 후 필터 — 행 수 소량). 공백/null 은 미설정으로 취급. */
    private String dbVal(String key) {
        Map<String, String> all = allValues();
        String v = all.get(key);
        return (v != null && !v.isBlank()) ? v : null;
    }

    private Map<String, String> allValues() {
        Map<String, String> m = new HashMap<>();
        List<Map<String, Object>> rows = mapper.findAll();
        if (rows != null) {
            for (Map<String, Object> r : rows) {
                Object k = r.get("key");
                Object v = r.get("val");
                if (k != null) {
                    m.put(String.valueOf(k), v == null ? null : String.valueOf(v));
                }
            }
        }
        return m;
    }
}
