package com.kt.event.analytics.service;

import com.kt.event.analytics.dto.response.*;
import com.kt.event.analytics.entity.ChannelStats;
import com.kt.event.analytics.entity.EventStats;
import com.kt.event.analytics.repository.ChannelStatsRepository;
import com.kt.event.analytics.repository.EventStatsRepository;
import com.kt.event.common.exception.BusinessException;
import com.kt.event.common.exception.ErrorCode;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Analytics Service
 *
 * 이벤트 성과 대시보드 데이터를 제공하는 서비스
 */
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AnalyticsService {

    private final EventStatsRepository eventStatsRepository;
    private final ChannelStatsRepository channelStatsRepository;
    private final ExternalChannelService externalChannelService;
    private final ROICalculator roiCalculator;
    private final RedisTemplate<String, String> redisTemplate;
    private final ObjectMapper objectMapper;

    private static final String CACHE_KEY_PREFIX = "analytics:dashboard:";
    private static final long CACHE_TTL = 3600; // 1시간 (단일 캐시)

    /**
     * 대시보드 데이터 조회
     *
     * @param eventId   이벤트 ID
     * @param refresh 캐시 갱신 여부
     * @return 대시보드 응답 (이벤트 시작일 ~ 현재까지)
     */
    public AnalyticsDashboardResponse getDashboardData(String eventId, boolean refresh) {
        log.info("대시보드 데이터 조회 시작: eventId={}, refresh={}", eventId, refresh);

        String cacheKey = CACHE_KEY_PREFIX + eventId;

        // 1. Redis 캐시 조회 (refresh가 false일 때만)
        if (!refresh) {
            String cachedData = redisTemplate.opsForValue().get(cacheKey);
            if (cachedData != null) {
                try {
                    log.info("✅ 캐시 HIT: {} (1시간 캐시)", cacheKey);
                    return objectMapper.readValue(cachedData, AnalyticsDashboardResponse.class);
                } catch (JsonProcessingException e) {
                    log.warn("캐시 데이터 역직렬화 실패: {}", e.getMessage());
                }
            }
        }

        // 2. 캐시 MISS: 데이터 통합 작업
        log.info("캐시 MISS 또는 refresh=true: PostgreSQL + 외부 API 호출");

        // 2-1. Analytics DB 조회 (PostgreSQL)
        EventStats eventStats = eventStatsRepository.findByEventId(eventId)
            .orElseThrow(() -> new BusinessException(ErrorCode.EVENT_001));

        List<ChannelStats> channelStatsList = channelStatsRepository.findByEventId(eventId);
        log.debug("PostgreSQL 조회 완료: eventId={}, 채널 수={}", eventId, channelStatsList.size());

        // 2-2. 외부 채널 API 병렬 호출 (Circuit Breaker 적용)
        try {
            externalChannelService.updateChannelStatsFromExternalAPIs(eventId, channelStatsList);
            log.info("외부 API 호출 성공: eventId={}", eventId);
        } catch (Exception e) {
            log.warn("외부 API 호출 실패, PostgreSQL 샘플 데이터 사용: eventId={}, error={}",
                    eventId, e.getMessage());
            // Fallback: PostgreSQL 샘플 데이터만 사용
        }

        // 3. 대시보드 데이터 구성
        AnalyticsDashboardResponse response = buildDashboardData(eventStats, channelStatsList);

        // 4. Redis 캐싱 (1시간 TTL)
        try {
            String jsonData = objectMapper.writeValueAsString(response);
            redisTemplate.opsForValue().set(cacheKey, jsonData, CACHE_TTL, TimeUnit.SECONDS);
            log.info("✅ Redis 캐시 저장 완료: {} (TTL: 1시간)", cacheKey);
        } catch (JsonProcessingException e) {
            log.warn("캐시 데이터 직렬화 실패: {}", e.getMessage());
        } catch (Exception e) {
            log.warn("캐시 저장 실패 (무시하고 계속 진행): {}", e.getMessage());
        }

        return response;
    }

    /**
     * 대시보드 데이터 구성
     */
    private AnalyticsDashboardResponse buildDashboardData(EventStats eventStats, List<ChannelStats> channelStatsList) {
        // 기간 정보 (이벤트 시작일 ~ 현재)
        PeriodInfo period = buildPeriodInfo(eventStats);

        // 성과 요약
        AnalyticsSummary summary = buildAnalyticsSummary(eventStats, channelStatsList);

        // 채널별 성과 요약
        List<ChannelSummary> channelPerformance = buildChannelPerformance(channelStatsList, eventStats.getTotalInvestment());

        // ROI 요약
        RoiSummary roiSummary = roiCalculator.calculateRoiSummary(eventStats);

        // 투자 비용 상세
        InvestmentDetails investment = buildInvestmentDetails(eventStats, channelStatsList);

        // 수익 상세
        RevenueDetails revenue = buildRevenueDetails(eventStats);

        // 비용 효율성
        CostEfficiency costEfficiency = buildCostEfficiency(eventStats);

        return AnalyticsDashboardResponse.builder()
            .eventId(eventStats.getEventId())
            .eventTitle(eventStats.getEventTitle())
            .period(period)
            .summary(summary)
            .channelPerformance(channelPerformance)
            .roi(roiSummary)
            .investment(investment)
            .revenue(revenue)
            .costEfficiency(costEfficiency)
            .lastUpdatedAt(LocalDateTime.now())
            .dataSource("cached")
            .build();
    }

    /**
     * 기간 정보 구성 (이벤트 시작일 ~ 종료일 또는 현재)
     */
    private PeriodInfo buildPeriodInfo(EventStats eventStats) {
        LocalDateTime start = eventStats.getStartDate();
        LocalDateTime end = eventStats.getEndDate() != null ?
            eventStats.getEndDate() : LocalDateTime.now();

        long durationDays = ChronoUnit.DAYS.between(start, end);

        return PeriodInfo.builder()
            .startDate(start)
            .endDate(end)
            .durationDays((int) durationDays)
            .build();
    }

    /**
     * 성과 요약 구성
     */
    private AnalyticsSummary buildAnalyticsSummary(EventStats eventStats, List<ChannelStats> channelStatsList) {
        int totalViews = channelStatsList.stream()
            .mapToInt(ChannelStats::getViews)
            .sum();

        int totalReach = channelStatsList.stream()
            .mapToInt(ChannelStats::getImpressions)
            .sum();

        double engagementRate = totalViews > 0 ? (eventStats.getTotalParticipants() * 100.0 / totalViews) : 0.0;
        double conversionRate = totalViews > 0 ? (eventStats.getTotalParticipants() * 100.0 / totalViews) : 0.0;

        // SNS 반응 통계 집계
        int totalLikes = channelStatsList.stream().mapToInt(ChannelStats::getLikes).sum();
        int totalComments = channelStatsList.stream().mapToInt(ChannelStats::getComments).sum();
        int totalShares = channelStatsList.stream().mapToInt(ChannelStats::getShares).sum();

        SocialInteractionStats socialStats = SocialInteractionStats.builder()
            .likes(totalLikes)
            .comments(totalComments)
            .shares(totalShares)
            .build();

        return AnalyticsSummary.builder()
            .participants(eventStats.getTotalParticipants())
            .participantsDelta(0) // TODO: 이전 기간 데이터와 비교하여 계산
            .totalViews(totalViews)
            .totalReach(totalReach)
            .engagementRate(Math.round(engagementRate * 10.0) / 10.0)
            .conversionRate(Math.round(conversionRate * 10.0) / 10.0)
            .averageEngagementTime(145) // 고정값 (실제로는 외부 API에서 가져와야 함)
            .targetRoi(eventStats.getTargetRoi() != null ? eventStats.getTargetRoi().doubleValue() : null)
            .socialInteractions(socialStats)
            .build();
    }

    /**
     * 채널별 성과 구성
     */
    private List<ChannelSummary> buildChannelPerformance(List<ChannelStats> channelStatsList, java.math.BigDecimal totalInvestment) {
        List<ChannelSummary> summaries = new ArrayList<>();

        for (ChannelStats stats : channelStatsList) {
            double engagementRate = stats.getViews() > 0 ? (stats.getParticipants() * 100.0 / stats.getViews()) : 0.0;
            double conversionRate = stats.getViews() > 0 ? (stats.getConversions() * 100.0 / stats.getViews()) : 0.0;
            double roi = stats.getDistributionCost().compareTo(java.math.BigDecimal.ZERO) > 0 ?
                (stats.getParticipants() * 100.0 / stats.getDistributionCost().doubleValue()) : 0.0;

            summaries.add(ChannelSummary.builder()
                .channel(stats.getChannelName())
                .views(stats.getViews())
                .participants(stats.getParticipants())
                .engagementRate(Math.round(engagementRate * 10.0) / 10.0)
                .conversionRate(Math.round(conversionRate * 10.0) / 10.0)
                .roi(Math.round(roi * 10.0) / 10.0)
                .build());
        }

        return summaries;
    }

    /**
     * 투자 비용 상세 구성
     *
     * UserRoiAnalyticsService와 동일한 로직:
     * - 실제 채널 배포 비용 집계
     * - 나머지 비용 분배: 경품 50%, 콘텐츠 제작 30%, 운영 20%
     */
    private InvestmentDetails buildInvestmentDetails(EventStats eventStats, List<ChannelStats> channelStatsList) {
        java.math.BigDecimal totalInvestment = eventStats.getTotalInvestment();

        // ChannelStats에서 실제 배포 비용 집계
        java.math.BigDecimal actualDistribution = channelStatsList.stream()
            .map(ChannelStats::getDistributionCost)
            .reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);

        // 나머지 비용 계산 (총 투자 - 실제 채널 배포 비용)
        java.math.BigDecimal remaining = totalInvestment.subtract(actualDistribution);

        // 나머지 비용 분배: 경품 50%, 콘텐츠 제작 30%, 운영 20%
        java.math.BigDecimal prizeCost = remaining.multiply(java.math.BigDecimal.valueOf(0.50));
        java.math.BigDecimal contentCreation = remaining.multiply(java.math.BigDecimal.valueOf(0.30));
        java.math.BigDecimal operation = remaining.multiply(java.math.BigDecimal.valueOf(0.20));

        return InvestmentDetails.builder()
            .total(totalInvestment)
            .contentCreation(contentCreation)
            .operation(operation)
            .distribution(actualDistribution)
            .prizeCost(prizeCost)
            .channelCost(actualDistribution)  // 채널비용은 배포비용과 동일
            .build();
    }

    /**
     * 수익 상세 구성
     *
     * UserRoiAnalyticsService와 동일한 로직:
     * - 직접 매출 70%, 예상 추가 매출 30%
     * - 신규 고객 40%, 기존 고객 60%
     */
    private RevenueDetails buildRevenueDetails(EventStats eventStats) {
        java.math.BigDecimal totalRevenue = eventStats.getExpectedRevenue();

        // 매출 분배: 직접 매출 70%, 예상 추가 매출 30%
        java.math.BigDecimal directSales = totalRevenue.multiply(java.math.BigDecimal.valueOf(0.70));
        java.math.BigDecimal expectedSales = totalRevenue.multiply(java.math.BigDecimal.valueOf(0.30));

        // 신규 고객 40%, 기존 고객 60%
        java.math.BigDecimal newCustomerRevenue = totalRevenue.multiply(java.math.BigDecimal.valueOf(0.40));
        java.math.BigDecimal existingCustomerRevenue = totalRevenue.multiply(java.math.BigDecimal.valueOf(0.60));

        return RevenueDetails.builder()
            .total(totalRevenue)
            .directSales(directSales)
            .expectedSales(expectedSales)
            .newCustomerRevenue(newCustomerRevenue)
            .existingCustomerRevenue(existingCustomerRevenue)
            .brandValue(java.math.BigDecimal.ZERO)  // 브랜드 가치는 별도 계산 필요 시 추가
            .build();
    }

    /**
     * 비용 효율성 구성
     *
     * UserRoiAnalyticsService와 동일한 로직:
     * - 참여자당 비용 = 총투자 ÷ 총참여자수
     * - 참여자당 수익 = 총수익 ÷ 총참여자수
     */
    private CostEfficiency buildCostEfficiency(EventStats eventStats) {
        int totalParticipants = eventStats.getTotalParticipants();
        java.math.BigDecimal totalInvestment = eventStats.getTotalInvestment();
        java.math.BigDecimal totalRevenue = eventStats.getExpectedRevenue();

        double costPerParticipant = totalParticipants > 0 ?
            totalInvestment.doubleValue() / totalParticipants : 0.0;
        double revenuePerParticipant = totalParticipants > 0 ?
            totalRevenue.doubleValue() / totalParticipants : 0.0;

        return CostEfficiency.builder()
            .costPerParticipant(costPerParticipant)
            .revenuePerParticipant(revenuePerParticipant)
            .build();
    }
}
