package com.kt.ai.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kt.ai.circuitbreaker.CircuitBreakerManager;
import com.kt.ai.circuitbreaker.fallback.AIServiceFallback;
import com.kt.ai.client.ClaudeApiClient;
import com.kt.ai.client.dto.ClaudeRequest;
import com.kt.ai.client.dto.ClaudeResponse;
import com.kt.ai.exception.RecommendationNotFoundException;
import com.kt.ai.kafka.message.AIJobMessage;
import com.kt.ai.model.dto.response.AIRecommendationResult;
import com.kt.ai.model.dto.response.EventRecommendation;
import com.kt.ai.model.dto.response.ExpectedMetrics;
import com.kt.ai.model.dto.response.TrendAnalysis;
import com.kt.ai.model.enums.AIProvider;
import com.kt.ai.model.enums.EventMechanicsType;
import com.kt.ai.model.enums.JobStatus;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * AI 추천 서비스
 * - 트렌드 분석 및 이벤트 추천 총괄
 * - Claude API 연동
 *
 * @author AI Service Team
 * @since 1.0.0
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AIRecommendationService {

    private final CacheService cacheService;
    private final JobStatusService jobStatusService;
    private final TrendAnalysisService trendAnalysisService;
    private final ClaudeApiClient claudeApiClient;
    private final CircuitBreakerManager circuitBreakerManager;
    private final AIServiceFallback fallback;
    private final ObjectMapper objectMapper;

    @Value("${ai.provider:CLAUDE}")
    private String aiProvider;

    @Value("${ai.claude.api-key}")
    private String apiKey;

    @Value("${ai.claude.anthropic-version}")
    private String anthropicVersion;

    @Value("${ai.claude.model}")
    private String model;

    @Value("${ai.claude.max-tokens}")
    private Integer maxTokens;

    @Value("${ai.claude.temperature}")
    private Double temperature;

    /**
     * AI 추천 결과 조회
     */
    public AIRecommendationResult getRecommendation(String eventId) {
        Object cached = cacheService.getRecommendation(eventId);
        if (cached == null) {
            throw new RecommendationNotFoundException(eventId);
        }

        return objectMapper.convertValue(cached, AIRecommendationResult.class);
    }

    /**
     * AI 추천 생성 (Kafka Consumer에서 호출)
     */
    public void generateRecommendations(AIJobMessage message) {
        try {
            log.info("AI 추천 생성 시작: jobId={}, eventId={}", message.getJobId(), message.getEventId());

            // Job 상태 업데이트: PROCESSING
            jobStatusService.updateJobStatus(message.getJobId(), JobStatus.PROCESSING, "트렌드 분석 중 (10%)");

            // 1. 트렌드 분석
            TrendAnalysis trendAnalysis = analyzeTrend(message);
            jobStatusService.updateJobStatus(message.getJobId(), JobStatus.PROCESSING, "이벤트 추천안 생성 중 (50%)");

            // 2. 이벤트 추천안 생성
            List<EventRecommendation> recommendations = createRecommendations(message, trendAnalysis);
            jobStatusService.updateJobStatus(message.getJobId(), JobStatus.PROCESSING, "결과 저장 중 (90%)");

            // 3. 결과 생성 및 저장
            AIRecommendationResult result = AIRecommendationResult.builder()
                    .eventId(message.getEventId())
                    .trendAnalysis(trendAnalysis)
                    .recommendations(recommendations)
                    .generatedAt(LocalDateTime.now())
                    .expiresAt(LocalDateTime.now().plusDays(1))
                    .aiProvider(AIProvider.valueOf(aiProvider))
                    .build();

            // 결과 캐싱
            cacheService.saveRecommendation(message.getEventId(), result);

            // Job 상태 업데이트: COMPLETED
            jobStatusService.updateJobStatus(message.getJobId(), JobStatus.COMPLETED, "AI 추천 완료");

            log.info("AI 추천 생성 완료: jobId={}, eventId={}", message.getJobId(), message.getEventId());

        } catch (Exception e) {
            log.error("AI 추천 생성 실패: jobId={}", message.getJobId(), e);
            jobStatusService.updateJobStatus(message.getJobId(), JobStatus.FAILED, "AI 추천 실패: " + e.getMessage());
        }
    }

    /**
     * 트렌드 분석
     */
    private TrendAnalysis analyzeTrend(AIJobMessage message) {
        String industry = message.getIndustry();
        String region = message.getRegion();

        // 캐시 확인
        Object cached = cacheService.getTrend(industry, region);
        if (cached != null) {
            log.info("트렌드 분석 캐시 히트 - industry={}, region={}", industry, region);
            return objectMapper.convertValue(cached, TrendAnalysis.class);
        }

        // TrendAnalysisService를 통한 실제 분석
        log.info("트렌드 분석 시작 - industry={}, region={}", industry, region);
        TrendAnalysis analysis = trendAnalysisService.analyzeTrend(industry, region);

        // 캐시 저장
        cacheService.saveTrend(industry, region, analysis);

        return analysis;
    }

    /**
     * 이벤트 추천안 생성
     */
    private List<EventRecommendation> createRecommendations(AIJobMessage message, TrendAnalysis trendAnalysis) {
        log.info("이벤트 추천안 생성 시작 - eventId={}", message.getEventId());

        return circuitBreakerManager.executeWithCircuitBreaker(
                "claudeApi",
                () -> callClaudeApiForRecommendations(message, trendAnalysis),
                () -> fallback.getDefaultRecommendations(message.getObjective(), message.getIndustry())
        );
    }

    /**
     * Claude API를 통한 추천안 생성
     */
    private List<EventRecommendation> callClaudeApiForRecommendations(AIJobMessage message, TrendAnalysis trendAnalysis) {
        // 프롬프트 생성
        String prompt = buildRecommendationPrompt(message, trendAnalysis);

        // Claude API 요청 생성
        ClaudeRequest request = ClaudeRequest.builder()
                .model(model)
                .messages(List.of(
                        ClaudeRequest.Message.builder()
                                .role("user")
                                .content(prompt)
                                .build()
                ))
                .maxTokens(maxTokens)
                .temperature(temperature)
                .system("당신은 소상공인을 위한 마케팅 이벤트 기획 전문가입니다. 트렌드 분석을 바탕으로 실행 가능한 이벤트 추천안을 제공합니다.")
                .build();

        // API 호출
        log.debug("Claude API 호출 (추천안 생성) - model={}", model);
        ClaudeResponse response = claudeApiClient.sendMessage(
                apiKey,
                anthropicVersion,
                request
        );

        // 응답 파싱
        String responseText = response.extractText();
        log.debug("Claude API 응답 수신 (추천안) - length={}", responseText.length());

        return parseRecommendationResponse(responseText);
    }

    /**
     * 추천안 프롬프트 생성
     */
    private String buildRecommendationPrompt(AIJobMessage message, TrendAnalysis trendAnalysis) {
        StringBuilder trendSummary = new StringBuilder();

        trendSummary.append("**업종 트렌드:**\n");
        trendAnalysis.getIndustryTrends().forEach(trend ->
                trendSummary.append(String.format("- %s (연관도: %.2f): %s\n",
                        trend.getKeyword(), trend.getRelevance(), trend.getDescription()))
        );

        trendSummary.append("\n**지역 트렌드:**\n");
        trendAnalysis.getRegionalTrends().forEach(trend ->
                trendSummary.append(String.format("- %s (연관도: %.2f): %s\n",
                        trend.getKeyword(), trend.getRelevance(), trend.getDescription()))
        );

        trendSummary.append("\n**계절 트렌드:**\n");
        trendAnalysis.getSeasonalTrends().forEach(trend ->
                trendSummary.append(String.format("- %s (연관도: %.2f): %s\n",
                        trend.getKeyword(), trend.getRelevance(), trend.getDescription()))
        );

        return String.format("""
                # 이벤트 추천안 생성 요청

                ## 고객 정보
                - 매장명: %s
                - 업종: %s
                - 지역: %s
                - 목표: %s
                - 타겟 고객: %s
                - 예산: %,d원

                ## 트렌드 분석 결과
                %s

                ## 요구사항
                위 트렌드 분석을 바탕으로 **3가지 이벤트 추천안**을 생성해주세요:
                1. **저비용 옵션** (100,000 ~ 200,000원): SNS/온라인 중심
                2. **중비용 옵션** (300,000 ~ 500,000원): 온/오프라인 결합
                3. **고비용 옵션** (500,000 ~ 1,000,000원): 프리미엄 경험 제공

                ## 응답 형식
                응답은 반드시 다음 JSON 형식으로 작성해주세요:

                ```json
                {
                  "recommendations": [
                    {
                      "optionNumber": 1,
                      "concept": "이벤트 컨셉 (10자 이내)",
                      "title": "이벤트 제목 (20자 이내)",
                      "description": "이벤트 상세 설명 (3-5문장)",
                      "targetAudience": "타겟 고객층",
                      "duration": {
                        "recommendedDays": 14,
                        "recommendedPeriod": "2주"
                      },
                      "mechanics": {
                        "type": "DISCOUNT",
                        "details": "이벤트 참여 방법 및 혜택 상세"
                      },
                      "promotionChannels": ["채널1", "채널2", "채널3"],
                      "estimatedCost": {
                        "min": 100000,
                        "max": 200000,
                        "breakdown": {
                          "경품비": 50000,
                          "홍보비": 50000
                        }
                      },
                      "expectedMetrics": {
                        "newCustomers": { "min": 30.0, "max": 50.0 },
                        "revenueIncrease": { "min": 10.0, "max": 20.0 },
                        "roi": { "min": 100.0, "max": 150.0 }
                      },
                      "differentiator": "차별화 포인트 (2-3문장)"
                    }
                  ]
                }
                ```

                ## mechanics.type 값
                - DISCOUNT: 할인
                - GIFT: 경품/사은품
                - STAMP: 스탬프 적립
                - EXPERIENCE: 체험형 이벤트
                - LOTTERY: 추첨 이벤트
                - COMBO: 결합 혜택

                ## 주의사항
                - 각 옵션은 예산 범위 내에서 실행 가능해야 함
                - 트렌드 분석 결과를 반영한 구체적인 기획
                - 타겟 고객과 지역 특성을 고려
                - expectedMetrics는 백분율(%%로 표기)
                - promotionChannels는 실제 활용 가능한 채널로 제시
                """,
                message.getStoreName(),
                message.getIndustry(),
                message.getRegion(),
                message.getObjective(),
                message.getTargetAudience(),
                message.getBudget(),
                trendSummary.toString()
        );
    }

    /**
     * 추천안 응답 파싱
     */
    private List<EventRecommendation> parseRecommendationResponse(String responseText) {
        try {
            // JSON 부분만 추출
            String jsonText = extractJsonFromMarkdown(responseText);

            // JSON 파싱
            JsonNode rootNode = objectMapper.readTree(jsonText);
            JsonNode recommendationsNode = rootNode.get("recommendations");

            List<EventRecommendation> recommendations = new ArrayList<>();

            if (recommendationsNode != null && recommendationsNode.isArray()) {
                recommendationsNode.forEach(node -> {
                    recommendations.add(parseEventRecommendation(node));
                });
            }

            return recommendations;

        } catch (JsonProcessingException e) {
            log.error("추천안 응답 파싱 실패", e);
            throw new RuntimeException("이벤트 추천안 응답 파싱 중 오류 발생", e);
        }
    }

    /**
     * EventRecommendation 파싱
     */
    private EventRecommendation parseEventRecommendation(JsonNode node) {
        // Mechanics Type 파싱
        String mechanicsTypeStr = node.get("mechanics").get("type").asText();
        EventMechanicsType mechanicsType = EventMechanicsType.valueOf(mechanicsTypeStr);

        // Promotion Channels 파싱
        List<String> promotionChannels = new ArrayList<>();
        JsonNode channelsNode = node.get("promotionChannels");
        if (channelsNode != null && channelsNode.isArray()) {
            channelsNode.forEach(channel -> promotionChannels.add(channel.asText()));
        }

        // Breakdown 파싱
        Map<String, Integer> breakdown = new HashMap<>();
        JsonNode breakdownNode = node.get("estimatedCost").get("breakdown");
        if (breakdownNode != null && breakdownNode.isObject()) {
            breakdownNode.fields().forEachRemaining(entry ->
                    breakdown.put(entry.getKey(), entry.getValue().asInt())
            );
        }

        return EventRecommendation.builder()
                .optionNumber(node.get("optionNumber").asInt())
                .concept(node.get("concept").asText())
                .title(node.get("title").asText())
                .description(node.get("description").asText())
                .targetAudience(node.get("targetAudience").asText())
                .duration(EventRecommendation.Duration.builder()
                        .recommendedDays(node.get("duration").get("recommendedDays").asInt())
                        .recommendedPeriod(node.get("duration").get("recommendedPeriod").asText())
                        .build())
                .mechanics(EventRecommendation.Mechanics.builder()
                        .type(mechanicsType)
                        .details(node.get("mechanics").get("details").asText())
                        .build())
                .promotionChannels(promotionChannels)
                .estimatedCost(EventRecommendation.EstimatedCost.builder()
                        .min(node.get("estimatedCost").get("min").asInt())
                        .max(node.get("estimatedCost").get("max").asInt())
                        .breakdown(breakdown)
                        .build())
                .expectedMetrics(ExpectedMetrics.builder()
                        .newCustomers(parseRange(node.get("expectedMetrics").get("newCustomers")))
                        .revenueIncrease(parseRange(node.get("expectedMetrics").get("revenueIncrease")))
                        .roi(parseRange(node.get("expectedMetrics").get("roi")))
                        .build())
                .differentiator(node.get("differentiator").asText())
                .build();
    }

    /**
     * Range 파싱
     */
    private ExpectedMetrics.Range parseRange(JsonNode node) {
        return ExpectedMetrics.Range.builder()
                .min(node.get("min").asDouble())
                .max(node.get("max").asDouble())
                .build();
    }

    /**
     * Markdown에서 JSON 추출
     */
    private String extractJsonFromMarkdown(String text) {
        // ```json ... ``` 형태에서 JSON만 추출
        if (text.contains("```json")) {
            int start = text.indexOf("```json") + 7;
            int end = text.indexOf("```", start);
            return text.substring(start, end).trim();
        }

        // ```{ ... }``` 형태에서 JSON만 추출
        if (text.contains("```")) {
            int start = text.indexOf("```") + 3;
            int end = text.indexOf("```", start);
            return text.substring(start, end).trim();
        }

        // 순수 JSON인 경우
        return text.trim();
    }
}
