package com.tasteby.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * #356 영상-식당 관련도 LLM 평가.
 * 설계서: docs/design/356-video-relevance-llm/README.md
 *
 * 신규 등록 시 자동 평가 + 어드민 백필. 결과는 video_restaurants.relevance에 저장.
 * - strong: 본격 다룸 (방문 리뷰, 메뉴 평가)
 * - weak: 잠깐 언급, 비교 대상
 * - incidental: 일반 토픽 중 단순 언급, 입점 전
 * - unknown: 미평가 or LLM 실패 (안전 기본값으로 표시 유지)
 */
@Service
public class VideoRelevanceService {

    private static final Logger log = LoggerFactory.getLogger(VideoRelevanceService.class);
    private static final Set<String> VALID = Set.of("strong", "weak", "incidental", "unknown");
    private static final long BACKFILL_SLEEP_MS = 200;

    private final RestaurantService restaurantService;
    private final OciGenAiService genAi;
    private final ObjectMapper jsonMapper = new ObjectMapper();

    public VideoRelevanceService(RestaurantService restaurantService, OciGenAiService genAi) {
        this.restaurantService = restaurantService;
        this.genAi = genAi;
    }

    @Async
    public void verifyAsync(String linkId) {
        try {
            verify(linkId);
        } catch (Exception e) {
            log.warn("verifyAsync failed for link {}: {}", linkId, e.getMessage());
        }
    }

    public void verify(String linkId) {
        Map<String, Object> ctx = restaurantService.findLinkContext(linkId);
        if (ctx == null) return;
        VerifyResult result;
        try {
            String prompt = buildPrompt(ctx);
            String response = genAi.chat(prompt, 120);
            result = parseRelevance(response);
        } catch (Exception e) {
            log.warn("verify({}) LLM failed: {} — keeping unknown", linkId, e.getMessage());
            return;
        }
        restaurantService.updateLinkRelevance(linkId, result.relevance(), truncate(result.reason(), 120));
    }

    @Async
    public void verifyAllAsync(int batchSize) {
        try {
            int n = verifyAll(batchSize);
            log.info("[VideoRelevance] backfill done: {} processed", n);
        } catch (Exception e) {
            log.warn("verifyAllAsync failed: {}", e.getMessage());
        }
    }

    public int verifyAll(int batchSize) {
        int total = 0;
        List<Map<String, Object>> batch;
        while (!(batch = restaurantService.findUnevaluatedLinks(batchSize)).isEmpty()) {
            for (Map<String, Object> row : batch) {
                String linkId = (String) row.get("link_id");
                if (linkId == null) continue;
                try {
                    verify(linkId);
                } catch (Exception e) {
                    log.warn("verifyAll({}) failed: {}", linkId, e.getMessage());
                }
                total++;
                try { Thread.sleep(BACKFILL_SLEEP_MS); } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    return total;
                }
            }
            if (batch.size() < batchSize) break;
        }
        return total;
    }

    // ---- pure helpers ----

    String buildPrompt(Map<String, Object> ctx) {
        String foods = safeStr(ctx.get("foods_mentioned"));
        String evaluation = safeStr(ctx.get("evaluation"));
        return "다음 YouTube 영상이 이 식당을 어떻게 다루는지 판정하라.\n\n" +
               "식당명: " + safeStr(ctx.get("restaurant_name")) + "\n" +
               "주소: " + safeStr(ctx.get("address")) + "\n" +
               "음식 분류: " + safeStr(ctx.get("cuisine_type")) + "\n" +
               "언급된 음식: " + (foods.isEmpty() ? "(없음)" : foods) + "\n\n" +
               "영상 제목: " + safeStr(ctx.get("video_title")) + "\n" +
               "영상 채널: " + safeStr(ctx.get("channel_name")) + "\n" +
               "영상에 등장한 평가: " + (evaluation.isEmpty() ? "(없음)" : evaluation) + "\n\n" +
               "응답 형식(JSON만, 다른 텍스트 없이):\n" +
               "{\"relevance\": \"strong\"|\"weak\"|\"incidental\", \"reason\": \"20자 이내 한국어\"}\n\n" +
               "가이드:\n" +
               "- strong: 영상이 이 식당을 본격 다룸 (방문 리뷰, 메뉴 평가).\n" +
               "- weak: 잠깐 언급, 다른 식당과 비교 대상으로 등장.\n" +
               "- incidental: 일반 토픽 중 단순 언급, 식당 입점 전 영상.\n" +
               "- 판단 모호 시 strong (보수적 — 사용자에게 표시 유지).";
    }

    private static final Pattern JSON_BLOCK = Pattern.compile("\\{[^{}]*\\}", Pattern.DOTALL);

    VerifyResult parseRelevance(String raw) {
        if (raw == null) return VerifyResult.unknown();
        String trimmed = raw.trim();
        String json = (trimmed.startsWith("{") && trimmed.endsWith("}")) ? trimmed : null;
        if (json == null) {
            Matcher m = JSON_BLOCK.matcher(raw);
            if (m.find()) json = m.group();
        }
        if (json == null) return VerifyResult.unknown();
        try {
            JsonNode node = jsonMapper.readTree(json);
            String rel = node.path("relevance").asText("unknown").toLowerCase();
            if (!VALID.contains(rel)) rel = "unknown";
            String reason = node.path("reason").asText("");
            return new VerifyResult(rel, reason);
        } catch (Exception e) {
            return VerifyResult.unknown();
        }
    }

    private static String safeStr(Object o) {
        return o == null ? "" : o.toString();
    }

    private static String truncate(String s, int max) {
        return s == null ? null : (s.length() <= max ? s : s.substring(0, max));
    }

    public record VerifyResult(String relevance, String reason) {
        public static VerifyResult unknown() { return new VerifyResult("unknown", "parse_failed"); }
    }
}
