package com.zioinfo.cms.ai;

import com.zioinfo.cms.ai.service.AiTextRouter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * GUARDiA CMS AI 서비스 — 8개 기능, 전부 Ollama 온프레미스 + Java 폴백.
 *
 * <p>보안 불변 규칙: 외부 AI API 금지. Ollama 미가용 시 모든 메서드가 규칙기반 폴백으로 동작한다.
 * 다른 모듈(ugc 모더레이션·media 대체텍스트·content 초안·seo·delivery 검색)에서 재사용한다.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AiService {

    private final OllamaClient ollama;         // 비전(vision) 경로 전용 + 가용성 핑 유지
    private final AiTextRouter aiRouter;        // 텍스트 생성은 provider 라우팅(Claude↔Ollama) 경유

    // 욕설/스팸 폴백 사전 (규칙기반)
    private static final Set<String> PROFANITY = Set.of(
        "욕설", "개새끼", "씨발", "병신", "fuck", "shit", "asshole", "bitch");
    private static final Set<String> SPAM_HINTS = Set.of(
        "http://", "https://", "카지노", "도박", "비아그라", "viagra", "loan", "대출", "텔레그램", "주식리딩");

    public boolean ollamaAvailable() {
        return ollama.available();
    }

    // 1. AI 콘텐츠 초안 생성 (상품설명·블로그·배너카피)
    public String draft(String kind, String topic, String tone) {
        String prompt = String.format(
            "당신은 e-커머스 콘텐츠 작가입니다. 유형: %s, 주제: %s, 톤: %s. "
            + "한국어로 자연스러운 마케팅 초안을 200자 내외로 작성하세요. 설명 없이 본문만 출력.",
            kind, topic, tone == null ? "친근함" : tone);
        String out = aiRouter.generateText(prompt);
        if (out != null && !out.isBlank()) {
            return out;
        }
        // Java 폴백: 템플릿 기반
        String t = topic == null ? "신규 상품" : topic;
        return switch (kind == null ? "" : kind.toLowerCase()) {
            case "product", "상품" -> t + " — 지금 만나보세요. 엄선된 품질과 합리적인 가격으로 일상에 특별함을 더합니다. 한정 수량으로 준비했습니다.";
            case "banner", "배너" -> t + " 특별 기획전! 놓치면 후회하는 혜택, 오늘만 이 가격.";
            default -> t + "에 대해 소개합니다. 핵심 가치와 활용법을 알기 쉽게 정리했습니다. 자세한 내용은 본문에서 확인하세요.";
        };
    }

    // 2. 이미지 태깅·대체텍스트 자동생성 (llava 비전)
    public Map<String, Object> imageTags(String imageBase64, String fileName) {
        Map<String, Object> result = new LinkedHashMap<>();
        if (imageBase64 != null && !imageBase64.isBlank()) {
            String prompt = "이 이미지를 설명하는 한국어 대체텍스트 1문장과, 쉼표로 구분된 태그 5개를 "
                + "'ALT: ...\\nTAGS: a,b,c' 형식으로 출력하세요.";
            String out = ollama.vision(prompt, imageBase64);
            if (out != null && !out.isBlank()) {
                String alt = extractLine(out, "ALT:");
                String tags = extractLine(out, "TAGS:");
                result.put("altText", alt.isBlank() ? out : alt);
                result.put("tags", tags.isBlank() ? List.of() : Arrays.asList(tags.split("\\s*,\\s*")));
                result.put("source", "ollama-llava");
                return result;
            }
        }
        // Java 폴백: 파일명 기반
        String base = fileName == null ? "이미지" : fileName.replaceAll("\\.[^.]+$", "").replaceAll("[_-]", " ");
        result.put("altText", base + " 이미지");
        result.put("tags", List.of("image", "media"));
        result.put("source", "fallback");
        return result;
    }

    // 3. SEO 최적화 제안 (메타·키워드·가독성)
    public Map<String, Object> seoSuggest(String title, String body) {
        Map<String, Object> result = new LinkedHashMap<>();
        String prompt = String.format(
            "다음 콘텐츠의 SEO를 개선하세요. 제목: %s\\n본문: %s\\n"
            + "'META: ...\\nKEYWORDS: a,b,c\\nSCORE: 0~100' 형식으로 한국어 출력.",
            title, truncate(body, 800));
        String out = aiRouter.generateText(prompt);
        if (out != null && !out.isBlank()) {
            result.put("metaDescription", firstNonBlank(extractLine(out, "META:"), truncate(body, 150)));
            String kw = extractLine(out, "KEYWORDS:");
            result.put("keywords", kw.isBlank() ? fallbackKeywords(title, body) : Arrays.asList(kw.split("\\s*,\\s*")));
            result.put("score", parseScore(extractLine(out, "SCORE:")));
            result.put("source", "ollama");
            return result;
        }
        // Java 폴백
        result.put("metaDescription", truncate(stripHtml(body), 150));
        result.put("keywords", fallbackKeywords(title, body));
        result.put("score", readabilityScore(body));
        result.put("source", "fallback");
        return result;
    }

    // 4. 다국어 번역
    public String translate(String text, String targetLocale) {
        if (text == null || text.isBlank()) return text;
        String prompt = String.format(
            "다음 텍스트를 %s 로케일 언어로 자연스럽게 번역하세요. 번역문만 출력:\\n%s",
            targetLocale, truncate(text, 1500));
        String out = aiRouter.generateText(prompt);
        if (out != null && !out.isBlank()) {
            return out;
        }
        // Java 폴백: 원문 유지 + 로케일 표식(번역 불가 시 원문 보존이 안전)
        return text;
    }

    // 5. 개인화 콘텐츠/상품추천 (세그먼트별, 폴백=인기순 가정)
    public List<Map<String, Object>> recommend(String segment, List<Map<String, Object>> candidates, int topN) {
        if (candidates == null || candidates.isEmpty()) return List.of();
        int n = Math.min(topN <= 0 ? 5 : topN, candidates.size());
        // 폴백: 후보를 그대로 상위 N개 (조회수 desc 정렬은 매퍼에서 수행 가정)
        return candidates.subList(0, n);
    }

    // 6. 리뷰 AI 요약·감성분석
    public Map<String, Object> reviewSummary(List<String> reviews) {
        Map<String, Object> result = new LinkedHashMap<>();
        if (reviews == null || reviews.isEmpty()) {
            result.put("summary", "");
            result.put("sentiment", "NEUTRAL");
            result.put("score", 0.0);
            return result;
        }
        String joined = truncate(String.join("\\n- ", reviews), 1500);
        String prompt = "다음 리뷰들을 한 문장으로 요약하고 감성(POSITIVE/NEGATIVE/NEUTRAL)을 판정하세요. "
            + "'SUMMARY: ...\\nSENTIMENT: ...' 형식:\\n- " + joined;
        String out = aiRouter.generateText(prompt);
        if (out != null && !out.isBlank()) {
            result.put("summary", firstNonBlank(extractLine(out, "SUMMARY:"), out));
            String s = extractLine(out, "SENTIMENT:").toUpperCase();
            result.put("sentiment", s.contains("POSI") ? "POSITIVE" : s.contains("NEGA") ? "NEGATIVE" : "NEUTRAL");
            result.put("source", "ollama");
            return result;
        }
        // Java 폴백: 키워드 기반 감성
        return fallbackSentiment(reviews, result);
    }

    /** 단일 텍스트 감성 점수 (폴백 규칙). 0~1 긍정도. */
    public double sentimentScore(String text) {
        if (text == null) return 0.5;
        String t = text.toLowerCase();
        int pos = countAny(t, List.of("좋", "최고", "만족", "추천", "훌륭", "good", "great", "love", "best"));
        int neg = countAny(t, List.of("나쁘", "최악", "실망", "별로", "환불", "bad", "worst", "terrible", "hate"));
        if (pos + neg == 0) return 0.5;
        return (double) pos / (pos + neg);
    }

    // 7. 자연어 콘텐츠 검색 — 쿼리 키워드 추출(임베딩 미사용, 키워드 폴백)
    public List<String> searchKeywords(String naturalQuery) {
        if (naturalQuery == null || naturalQuery.isBlank()) return List.of();
        String prompt = "다음 검색 의도에서 핵심 키워드 3~5개를 쉼표로만 출력:\\n" + naturalQuery;
        String out = aiRouter.generateText(prompt);
        if (out != null && !out.isBlank()) {
            String line = out.replaceAll("[\\n\\r]", " ").trim();
            List<String> kws = new ArrayList<>();
            for (String k : line.split("\\s*,\\s*")) {
                String kk = k.trim();
                if (!kk.isEmpty() && kk.length() < 30) kws.add(kk);
            }
            if (!kws.isEmpty()) return kws.subList(0, Math.min(5, kws.size()));
        }
        // Java 폴백: 불용어 제거 후 토큰화
        return Arrays.stream(naturalQuery.split("[\\s,.]+"))
            .map(String::trim)
            .filter(w -> w.length() > 1 && !STOPWORDS.contains(w))
            .distinct().limit(5).toList();
    }

    // 8. UGC AI 모더레이션 — 욕설·스팸·부적절 분류
    public Map<String, Object> moderate(String text) {
        Map<String, Object> result = new LinkedHashMap<>();
        if (text == null || text.isBlank()) {
            result.put("decision", "APPROVE");
            result.put("risk", 0.0);
            result.put("reason", "empty");
            return result;
        }
        String prompt = "다음 UGC가 욕설/스팸/부적절한지 판정하세요. "
            + "'DECISION: APPROVE|REJECT|REVIEW\\nRISK: 0~1\\nREASON: ...' 형식 한국어:\\n" + truncate(text, 800);
        String out = aiRouter.generateText(prompt);
        if (out != null && !out.isBlank()) {
            String d = extractLine(out, "DECISION:").toUpperCase();
            String decision = d.contains("REJECT") ? "REJECT" : d.contains("REVIEW") ? "REVIEW" : "APPROVE";
            result.put("decision", decision);
            result.put("risk", parseRisk(extractLine(out, "RISK:")));
            result.put("reason", firstNonBlank(extractLine(out, "REASON:"), "ollama 판정"));
            result.put("source", "ollama");
            return result;
        }
        // Java 폴백: 사전 기반
        String lower = text.toLowerCase();
        boolean hasProfanity = PROFANITY.stream().anyMatch(lower::contains);
        long spamHits = SPAM_HINTS.stream().filter(lower::contains).count();
        double risk = (hasProfanity ? 0.8 : 0.0) + Math.min(0.6, spamHits * 0.3);
        risk = Math.min(1.0, risk);
        String decision = risk >= 0.7 ? "REJECT" : risk >= 0.3 ? "REVIEW" : "APPROVE";
        result.put("decision", decision);
        result.put("risk", round2(risk));
        result.put("reason", hasProfanity ? "욕설 감지" : spamHits > 0 ? "스팸 의심" : "정상");
        result.put("source", "fallback");
        return result;
    }

    // ── helpers ─────────────────────────────────────────────────────────────
    private static final Set<String> STOPWORDS = Set.of(
        "그리고", "또는", "은", "는", "이", "가", "을", "를", "에", "의", "and", "or", "the", "a", "of", "for");

    private Map<String, Object> fallbackSentiment(List<String> reviews, Map<String, Object> result) {
        double sum = 0;
        for (String r : reviews) sum += sentimentScore(r);
        double avg = sum / reviews.size();
        result.put("summary", "리뷰 " + reviews.size() + "건 — 평균 긍정도 " + round2(avg));
        result.put("sentiment", avg >= 0.6 ? "POSITIVE" : avg <= 0.4 ? "NEGATIVE" : "NEUTRAL");
        result.put("score", round2(avg));
        result.put("source", "fallback");
        return result;
    }

    private List<String> fallbackKeywords(String title, String body) {
        String text = ((title == null ? "" : title) + " " + stripHtml(body == null ? "" : body));
        return Arrays.stream(text.split("[\\s,.]+"))
            .map(String::trim)
            .filter(w -> w.length() > 1 && !STOPWORDS.contains(w.toLowerCase()))
            .distinct().limit(8).toList();
    }

    private int readabilityScore(String body) {
        if (body == null || body.isBlank()) return 30;
        int len = stripHtml(body).length();
        if (len < 100) return 45;
        if (len < 300) return 70;
        if (len < 1500) return 85;
        return 75;
    }

    private int countAny(String text, List<String> needles) {
        int c = 0;
        for (String n : needles) if (text.contains(n)) c++;
        return c;
    }

    private String extractLine(String out, String tag) {
        if (out == null) return "";
        for (String line : out.split("[\\n\\r]+")) {
            String l = line.trim();
            if (l.toUpperCase().startsWith(tag.toUpperCase())) {
                return l.substring(tag.length()).trim();
            }
        }
        return "";
    }

    private int parseScore(String s) {
        try {
            return Math.max(0, Math.min(100, Integer.parseInt(s.replaceAll("[^0-9]", ""))));
        } catch (Exception e) {
            return 60;
        }
    }

    private double parseRisk(String s) {
        try {
            double d = Double.parseDouble(s.replaceAll("[^0-9.]", ""));
            return round2(Math.max(0, Math.min(1, d)));
        } catch (Exception e) {
            return 0.5;
        }
    }

    private String firstNonBlank(String a, String b) {
        return (a != null && !a.isBlank()) ? a : b;
    }

    private String stripHtml(String s) {
        return s == null ? "" : Pattern.compile("<[^>]+>").matcher(s).replaceAll(" ").trim();
    }

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

    private double round2(double d) {
        return Math.round(d * 100.0) / 100.0;
    }
}
