package com.zioinfo.kintex.ai;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * 온프레미스 Ollama 텍스트 생성 클라이언트 (F100 폴백 경로).
 *
 * <p><b>설정</b>: 베이스 URL 은 환경변수 {@code OLLAMA_URL} 에서만 로드한다({@code @Value("${OLLAMA_URL:}")}).
 * <b>미설정 시 폴백을 스킵</b>({@link #isConfigured()}=false → 항상 degraded)한다 — 개발 서버 RAM 제약상
 * Ollama 미기동 환경에서 조용히 degraded 로 흐르게 한다.
 *
 * <p><b>실패 처리</b>: 비활성/busy/비200/타임아웃/예외 시 {@code GenResult.fail()}(예외 미투척).
 * 외부 API 금지 — {@code OLLAMA_URL}(온프레미스) 만 호출한다. 동시성 1(Semaphore) — 콜드로드 RAM 보호.
 */
@Service
public class OllamaTextClient implements TextAiClient {

    private static final Logger log = LoggerFactory.getLogger(OllamaTextClient.class);
    private static final Semaphore GATE = new Semaphore(1);
    private static final ObjectMapper MAPPER = new ObjectMapper();
    private static final int TIMEOUT_SEC = 120;

    /** 온프레미스 Ollama 베이스 URL(env 전용). 미설정 시 폴백 스킵. */
    @Value("${OLLAMA_URL:}")
    private String baseUrl;

    /** Ollama 폴백이 구성되어 있는지(URL 설정 여부). */
    public boolean isConfigured() {
        return baseUrl != null && !baseUrl.isBlank();
    }

    @Override
    public GenResult generate(String prompt) {
        return generate(prompt, null);
    }

    /** 모델을 지정해 생성. modelOverride 공백이면 라우터가 넘긴 기본 모델을 사용해야 한다(여기선 공백 방어). */
    public GenResult generate(String prompt, String model) {
        if (!isConfigured() || prompt == null || prompt.isBlank()) {
            return GenResult.fail();
        }
        String useModel = (model == null || model.isBlank()) ? "qwen3:1.7b" : model.trim();
        boolean acquired = false;
        try {
            acquired = GATE.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS);
            if (!acquired) {
                log.warn("Ollama generate busy -> degraded fallback");
                return GenResult.fail();
            }
            String payload = "{"
                    + "\"model\":" + str(useModel) + ","
                    + "\"prompt\":" + str(prompt) + ","
                    + "\"stream\":false,"
                    + "\"options\":{\"temperature\":0.2}"
                    + "}";

            HttpClient client = HttpClient.newBuilder()
                    .connectTimeout(Duration.ofSeconds(15))
                    .build();
            HttpRequest req = HttpRequest.newBuilder()
                    .uri(URI.create(baseUrl.trim().replaceAll("/+$", "") + "/api/generate"))
                    .timeout(Duration.ofSeconds(TIMEOUT_SEC))
                    .header("Content-Type", "application/json")
                    .POST(HttpRequest.BodyPublishers.ofString(payload))
                    .build();
            HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());
            if (resp.statusCode() != 200) {
                log.warn("Ollama status {} -> degraded fallback", resp.statusCode());
                return GenResult.fail();
            }
            String text = extractResponse(resp.body());
            return (text == null || text.isBlank()) ? GenResult.fail() : GenResult.ok(text.trim());
        } catch (Exception e) {
            log.warn("Ollama generate failed -> degraded fallback: {}", e.getClass().getSimpleName());
            return GenResult.fail();
        } finally {
            if (acquired) {
                GATE.release();
            }
        }
    }

    private static String extractResponse(String body) {
        if (body == null || body.isBlank()) {
            return null;
        }
        try {
            JsonNode resp = MAPPER.readTree(body).get("response");
            return (resp != null && !resp.isNull()) ? resp.asText() : null;
        } catch (Exception e) {
            return null;
        }
    }

    private static String str(String s) {
        if (s == null) {
            return "\"\"";
        }
        StringBuilder sb = new StringBuilder("\"");
        for (char c : s.toCharArray()) {
            switch (c) {
                case '"' -> sb.append("\\\"");
                case '\\' -> sb.append("\\\\");
                case '\n' -> sb.append("\\n");
                case '\r' -> sb.append("\\r");
                case '\t' -> sb.append("\\t");
                default -> {
                    if (c < 0x20) {
                        sb.append(String.format("\\u%04x", (int) c));
                    } else {
                        sb.append(c);
                    }
                }
            }
        }
        return sb.append("\"").toString();
    }
}
