package com.zioinfo.mall.ai;

import com.zioinfo.mall.inventory.mapper.StoreInventoryMapper;
import com.zioinfo.mall.product.MallProduct;
import com.zioinfo.mall.product.mapper.ProductMapper;
import com.zioinfo.mall.review.mapper.ReviewMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;

/**
 * GUARDiA Mall AI 서비스 — Ollama(localhost) 적극 활용 + 전부 Java 폴백.
 *
 * <p>보안 불변: 외부 AI API 금지. OllamaClient는 localhost:11434만 호출하고
 * 빈 응답 시 결정론적 Java 폴백으로 동작한다(서비스 중단 없음).
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class MallAiService {

    private final OllamaClient ollama;
    private final ProductMapper productMapper;
    private final ReviewMapper reviewMapper;
    private final StoreInventoryMapper storeInventoryMapper;

    /** 1. 상품 추천 — 행사/키워드 기반. AI 실패 시 인기/평점 폴백. */
    public List<MallProduct> recommend(String occasion, String keyword, int limit) {
        List<MallProduct> pool = productMapper.search(null, keyword, "ON_SALE", occasion, null, null, "sales", 30, 0);
        if (pool.isEmpty()) {
            pool = productMapper.search(null, null, "ON_SALE", null, null, null, "rating", 30, 0);
        }
        String names = joinNames(pool);
        String prompt = "You are a florist recommender. From this catalog: [" + names + "]. "
                + "Recommend up to " + limit + " bouquets for occasion='" + (occasion == null ? "any" : occasion)
                + "' keyword='" + (keyword == null ? "" : keyword) + "'. Reply ONLY product names comma-separated.";
        String ai = ollama.generate(prompt);
        if (ai != null && !ai.isBlank()) {
            List<MallProduct> ordered = reorderByAi(pool, ai);
            if (!ordered.isEmpty()) return ordered.subList(0, Math.min(limit, ordered.size()));
        }
        return pool.subList(0, Math.min(limit, pool.size()));  // Java 폴백: 판매순
    }

    /** 2. 리뷰 AI 요약. 폴백: 평균 평점 + 최근 키워드 요약. */
    public Map<String, Object> summarizeReviews(Long productId) {
        List<String> contents = reviewMapper.recentContents(productId, 20);
        Map<String, Object> stats = reviewMapper.stats(productId);
        Map<String, Object> out = new LinkedHashMap<>();
        out.put("stats", stats);
        if (contents.isEmpty()) {
            out.put("summary", "No reviews yet.");
            out.put("source", "fallback");
            return out;
        }
        String prompt = "Summarize these flower bouquet reviews in 2 concise sentences (pros/cons):\n"
                + String.join("\n", contents);
        String ai = ollama.generate(prompt);
        if (ai != null && !ai.isBlank()) {
            out.put("summary", ai);
            out.put("source", "ollama");
        } else {
            out.put("summary", "Customers mention: " + topKeywords(contents) + ". Avg rating "
                    + stats.getOrDefault("avg", "N/A") + "/5.");
            out.put("source", "fallback");
        }
        return out;
    }

    /** 3. 자연어 상품 검색 — "$50 이하 기념일 꽃" → 필터 추출. 폴백: 정규식 파싱. */
    public Map<String, Object> nlSearch(String query) {
        BigDecimal maxPrice = parsePrice(query);
        String occasion = parseOccasion(query);
        String flowerType = parseFlower(query);
        List<MallProduct> items = productMapper.search(null, null, "ON_SALE", occasion, flowerType, maxPrice, "sales", 20, 0);
        Map<String, Object> out = new LinkedHashMap<>();
        out.put("query", query);
        out.put("parsed", Map.of("maxPrice", maxPrice == null ? "" : maxPrice,
                "occasion", occasion == null ? "" : occasion, "flowerType", flowerType == null ? "" : flowerType));
        out.put("items", items);
        out.put("source", "fallback-nlp");
        return out;
    }

    /** 4. CS 자동응답 초안. 폴백: 카테고리 템플릿. */
    public String csAutoReply(String subject, String content) {
        String prompt = "You are a polite flower-shop customer support agent. Write a short helpful reply (<=4 sentences) to:\n"
                + "Subject: " + subject + "\nMessage: " + content;
        String ai = ollama.generate(prompt);
        if (ai != null && !ai.isBlank()) return ai;
        return "Thank you for reaching out about \"" + subject + "\". We're sorry for any inconvenience. "
                + "Our team is reviewing your request and will follow up shortly. "
                + "For urgent delivery issues, please reply with your order number.";
    }

    /**
     * 5. 당일 재고 소진 추천 — 매장 잔여 재고로 AI 부케 구성(Farmgirl 'Daily Standard').
     * 폴백: 잔여 수량 많은 상위 상품을 묶음 제안.
     */
    public Map<String, Object> dailyClearanceBouquet(Long storeId) {
        List<com.zioinfo.mall.inventory.MallStoreInventory> inv = storeInventoryMapper.findByStore(storeId);
        inv.sort((a, b) -> Integer.compare(b.getOnHand() == null ? 0 : b.getOnHand(),
                a.getOnHand() == null ? 0 : a.getOnHand()));
        List<String> surplus = new ArrayList<>();
        for (com.zioinfo.mall.inventory.MallStoreInventory si : inv) {
            if (si.getOnHand() != null && si.getOnHand() > 0 && Boolean.TRUE.equals(si.getAvailable())) {
                surplus.add(si.getProductName() + " (x" + si.getOnHand() + ")");
            }
            if (surplus.size() >= 6) break;
        }
        Map<String, Object> out = new LinkedHashMap<>();
        out.put("storeId", storeId);
        out.put("surplus", surplus);
        if (surplus.isEmpty()) {
            out.put("bouquet", "No surplus stock to compose today.");
            out.put("source", "fallback");
            return out;
        }
        String prompt = "Compose a creative 'Daily Standard' bouquet name and 1-line description using surplus flowers: ["
                + String.join(", ", surplus) + "]. Reply as: NAME | DESCRIPTION.";
        String ai = ollama.generate(prompt);
        if (ai != null && !ai.isBlank()) {
            out.put("bouquet", ai);
            out.put("source", "ollama");
        } else {
            out.put("bouquet", "Today's Designer's Choice | A fresh seasonal mix featuring "
                    + surplus.get(0).replaceAll("\\s*\\(.*\\)", "") + " and more.");
            out.put("source", "fallback");
        }
        return out;
    }

    /**
     * 6. 피크시즌 수요 예측 — 매장별 용량 계획(밸런타인/어머니날).
     * 폴백: 최근 일평균 주문 × 시즌 배수 vs 용량 비교.
     */
    public Map<String, Object> demandForecast(String season, List<Map<String, Object>> storeSales) {
        double multiplier = "valentine".equalsIgnoreCase(season) ? 3.5
                : "mother".equalsIgnoreCase(season) ? 3.0 : 1.5;
        List<Map<String, Object>> forecast = new ArrayList<>();
        for (Map<String, Object> s : storeSales) {
            Object oc = s.get("orderCount");
            int recent = oc == null ? 0 : ((Number) oc).intValue();
            int predicted = (int) Math.round(recent * multiplier);
            Map<String, Object> row = new LinkedHashMap<>();
            row.put("storeId", s.get("storeId"));
            row.put("storeName", s.get("storeName"));
            row.put("recentOrders", recent);
            row.put("predictedOrders", predicted);
            row.put("multiplier", multiplier);
            forecast.add(row);
        }
        Map<String, Object> out = new LinkedHashMap<>();
        out.put("season", season);
        out.put("forecast", forecast);
        out.put("source", "fallback-heuristic");
        return out;
    }

    /** 7. AI 카드 메시지 작성 도우미. 폴백: 행사별 템플릿. */
    public List<String> cardMessages(String occasion, String tone, String recipient) {
        String prompt = "Write 3 short flower-card messages for occasion='" + occasion
                + "' tone='" + (tone == null ? "warm" : tone) + "' recipient='" + (recipient == null ? "" : recipient)
                + "'. One per line, no numbering.";
        String ai = ollama.generate(prompt);
        if (ai != null && !ai.isBlank()) {
            List<String> lines = new ArrayList<>();
            for (String l : ai.split("\n")) {
                String t = l.replaceAll("^[0-9.\\-)\\s]+", "").trim();
                if (!t.isEmpty()) lines.add(t);
            }
            if (!lines.isEmpty()) return lines;
        }
        return fallbackCards(occasion);
    }

    /**
     * 8. 매장간 재고 이양 추천 — 잉여/부족 매칭.
     * 폴백: 상품별로 재고 많은 매장(공급) → 적은/품절 매장(수요) 매칭.
     */
    public List<Map<String, Object>> transferRecommendations(List<Long> storeIds) {
        // 상품별 매장 재고 집계
        Map<Long, List<com.zioinfo.mall.inventory.MallStoreInventory>> byProduct = new HashMap<>();
        for (Long sid : storeIds) {
            for (com.zioinfo.mall.inventory.MallStoreInventory si : storeInventoryMapper.findByStore(sid)) {
                byProduct.computeIfAbsent(si.getProductId(), k -> new ArrayList<>()).add(si);
            }
        }
        List<Map<String, Object>> recs = new ArrayList<>();
        for (Map.Entry<Long, List<com.zioinfo.mall.inventory.MallStoreInventory>> e : byProduct.entrySet()) {
            List<com.zioinfo.mall.inventory.MallStoreInventory> list = e.getValue();
            com.zioinfo.mall.inventory.MallStoreInventory max = null, min = null;
            for (com.zioinfo.mall.inventory.MallStoreInventory si : list) {
                int oh = si.getOnHand() == null ? 0 : si.getOnHand();
                if (max == null || oh > (max.getOnHand() == null ? 0 : max.getOnHand())) max = si;
                if (min == null || oh < (min.getOnHand() == null ? 0 : min.getOnHand())) min = si;
            }
            if (max != null && min != null && !Objects.equals(max.getStoreId(), min.getStoreId())) {
                int surplus = (max.getOnHand() == null ? 0 : max.getOnHand());
                int shortage = (min.getOnHand() == null ? 0 : min.getOnHand());
                if (surplus - shortage >= 10) {
                    int qty = (surplus - shortage) / 2;
                    Map<String, Object> r = new LinkedHashMap<>();
                    r.put("productId", e.getKey());
                    r.put("productName", max.getProductName());
                    r.put("fromStoreId", max.getStoreId());
                    r.put("toStoreId", min.getStoreId());
                    r.put("suggestedQty", qty);
                    r.put("reason", "Surplus " + surplus + " vs shortage " + shortage);
                    recs.add(r);
                }
            }
        }
        return recs;
    }

    // ---------------------------------------------------------------- helpers
    private String joinNames(List<MallProduct> ps) {
        StringBuilder sb = new StringBuilder();
        for (MallProduct p : ps) {
            if (sb.length() > 0) sb.append(", ");
            sb.append(p.getName());
        }
        return sb.toString();
    }

    private List<MallProduct> reorderByAi(List<MallProduct> pool, String ai) {
        String lower = ai.toLowerCase();
        List<MallProduct> ordered = new ArrayList<>();
        for (MallProduct p : pool) {
            if (p.getName() != null && lower.contains(p.getName().toLowerCase())) ordered.add(p);
        }
        return ordered;
    }

    private BigDecimal parsePrice(String q) {
        if (q == null) return null;
        java.util.regex.Matcher m = java.util.regex.Pattern.compile("\\$?\\s*(\\d+(?:\\.\\d+)?)").matcher(q);
        BigDecimal found = null;
        if ((q.contains("under") || q.contains("이하") || q.contains("below") || q.contains("$")) && m.find()) {
            found = new BigDecimal(m.group(1));
        }
        return found;
    }

    private String parseOccasion(String q) {
        if (q == null) return null;
        String l = q.toLowerCase();
        if (l.contains("anniversary") || l.contains("기념일")) return "ANNIVERSARY";
        if (l.contains("birthday") || l.contains("생일")) return "BIRTHDAY";
        if (l.contains("sympathy") || l.contains("조의")) return "SYMPATHY";
        if (l.contains("romance") || l.contains("love") || l.contains("사랑")) return "ROMANCE";
        if (l.contains("get well") || l.contains("쾌유")) return "GETWELL";
        if (l.contains("congrat") || l.contains("축하")) return "CONGRATS";
        return null;
    }

    private String parseFlower(String q) {
        if (q == null) return null;
        String l = q.toLowerCase();
        if (l.contains("rose") || l.contains("장미")) return "ROSES";
        if (l.contains("tulip") || l.contains("튤립")) return "TULIPS";
        if (l.contains("lily") || l.contains("백합")) return "LILIES";
        if (l.contains("sunflower") || l.contains("해바라기")) return "SUNFLOWERS";
        if (l.contains("hydrangea") || l.contains("수국")) return "HYDRANGEAS";
        return null;
    }

    private String topKeywords(List<String> contents) {
        Map<String, Integer> freq = new HashMap<>();
        for (String c : contents) {
            for (String w : c.toLowerCase().split("\\W+")) {
                if (w.length() >= 5) freq.merge(w, 1, Integer::sum);
            }
        }
        return freq.entrySet().stream()
                .sorted((a, b) -> b.getValue() - a.getValue())
                .limit(3).map(Map.Entry::getKey)
                .reduce((a, b) -> a + ", " + b).orElse("quality, freshness");
    }

    private List<String> fallbackCards(String occasion) {
        String o = occasion == null ? "" : occasion.toUpperCase();
        return switch (o) {
            case "BIRTHDAY" -> List.of("Happy Birthday! Wishing you a day as bright as these blooms.",
                    "Another year more wonderful — enjoy every petal!",
                    "Hope your special day blossoms with joy.");
            case "ANNIVERSARY" -> List.of("Happy Anniversary — here's to many more years in bloom.",
                    "Celebrating your love today and always.",
                    "Forever and always, with these flowers.");
            case "SYMPATHY" -> List.of("With heartfelt sympathy in your time of loss.",
                    "Thinking of you with deepest condolences.",
                    "May these flowers bring a moment of peace.");
            default -> List.of("Just because you deserve something beautiful.",
                    "Sending smiles your way with these blooms.",
                    "A little flower to brighten your day.");
        };
    }
}
