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.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.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.HashMap;

/**
 * User Channel Analytics Service
 *
 * 매장(사용자) 전체 이벤트의 채널별 성과를 통합하여 제공하는 서비스
 */
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserChannelAnalyticsService {

    private final EventStatsRepository eventStatsRepository;
    private final ChannelStatsRepository channelStatsRepository;
    private final RedisTemplate<String, String> redisTemplate;
    private final ObjectMapper objectMapper;

    private static final String CACHE_KEY_PREFIX = "analytics:user:channels:";
    private static final long CACHE_TTL = 1800; // 30분

    /**
     * 사용자 전체 채널 분석 데이터 조회
     */
    public UserChannelAnalyticsResponse getUserChannelAnalytics(String userId, List<String> channels, String sortBy, String order,
                                                                 LocalDateTime startDate, LocalDateTime endDate, boolean refresh) {
        log.info("사용자 채널 분석 조회 시작: userId={}, refresh={}", userId, refresh);

        String cacheKey = CACHE_KEY_PREFIX + userId;

        // 1. 캐시 조회
        if (!refresh) {
            String cachedData = redisTemplate.opsForValue().get(cacheKey);
            if (cachedData != null) {
                try {
                    log.info("✅ 캐시 HIT: {}", cacheKey);
                    return objectMapper.readValue(cachedData, UserChannelAnalyticsResponse.class);
                } catch (JsonProcessingException e) {
                    log.warn("캐시 역직렬화 실패: {}", e.getMessage());
                }
            }
        }

        // 2. 데이터 조회
        List<EventStats> allEvents = eventStatsRepository.findAllByUserId(userId);
        if (allEvents.isEmpty()) {
            return buildEmptyResponse(userId, startDate, endDate);
        }

        List<String> eventIds = allEvents.stream().map(EventStats::getEventId).collect(Collectors.toList());
        List<ChannelStats> allChannelStats = channelStatsRepository.findByEventIdIn(eventIds);

        // 3. 응답 구성
        UserChannelAnalyticsResponse response = buildChannelAnalyticsResponse(userId, allEvents, allChannelStats, channels, sortBy, order, startDate, endDate);

        // 4. 캐싱
        try {
            String jsonData = objectMapper.writeValueAsString(response);
            redisTemplate.opsForValue().set(cacheKey, jsonData, CACHE_TTL, TimeUnit.SECONDS);
            log.info("✅ 캐시 저장 완료: {}", cacheKey);
        } catch (Exception e) {
            log.warn("캐시 저장 실패: {}", e.getMessage());
        }

        return response;
    }

    private UserChannelAnalyticsResponse buildEmptyResponse(String userId, LocalDateTime startDate, LocalDateTime endDate) {
        return UserChannelAnalyticsResponse.builder()
                .userId(userId)
                .period(buildPeriodInfo(startDate, endDate))
                .totalEvents(0)
                .channels(new ArrayList<>())
                .comparison(ChannelComparison.builder().build())
                .lastUpdatedAt(LocalDateTime.now())
                .dataSource("empty")
                .build();
    }

    private UserChannelAnalyticsResponse buildChannelAnalyticsResponse(String userId, List<EventStats> allEvents,
                                                                        List<ChannelStats> allChannelStats, List<String> channels,
                                                                        String sortBy, String order, LocalDateTime startDate, LocalDateTime endDate) {
        // 채널 필터링
        List<ChannelStats> filteredChannels = channels != null && !channels.isEmpty()
                ? allChannelStats.stream().filter(c -> channels.contains(c.getChannelName())).collect(Collectors.toList())
                : allChannelStats;

        // 채널별 집계
        List<ChannelAnalytics> channelAnalyticsList = aggregateChannelAnalytics(filteredChannels);

        // 정렬
        channelAnalyticsList = sortChannels(channelAnalyticsList, sortBy, order);

        // 채널 비교
        ChannelComparison comparison = buildChannelComparison(channelAnalyticsList);

        return UserChannelAnalyticsResponse.builder()
                .userId(userId)
                .period(buildPeriodInfo(startDate, endDate))
                .totalEvents(allEvents.size())
                .channels(channelAnalyticsList)
                .comparison(comparison)
                .lastUpdatedAt(LocalDateTime.now())
                .dataSource("cached")
                .build();
    }

    private List<ChannelAnalytics> aggregateChannelAnalytics(List<ChannelStats> allChannelStats) {
        Map<String, List<ChannelStats>> channelGroups = allChannelStats.stream()
                .collect(Collectors.groupingBy(ChannelStats::getChannelName));

        return channelGroups.entrySet().stream()
                .map(entry -> {
                    String channelName = entry.getKey();
                    List<ChannelStats> channelList = entry.getValue();

                    int views = channelList.stream().mapToInt(ChannelStats::getViews).sum();
                    int participants = channelList.stream().mapToInt(ChannelStats::getParticipants).sum();
                    int clicks = channelList.stream().mapToInt(ChannelStats::getClicks).sum();
                    int conversions = channelList.stream().mapToInt(ChannelStats::getConversions).sum();

                    double engagementRate = views > 0 ? (double) participants / views * 100 : 0.0;
                    double conversionRate = participants > 0 ? (double) conversions / participants * 100 : 0.0;

                    BigDecimal cost = channelList.stream()
                            .map(ChannelStats::getDistributionCost)
                            .reduce(BigDecimal.ZERO, BigDecimal::add);

                    double roi = cost.compareTo(BigDecimal.ZERO) > 0
                            ? (participants - cost.doubleValue()) / cost.doubleValue() * 100
                            : 0.0;

                    ChannelMetrics metrics = ChannelMetrics.builder()
                            .impressions(channelList.stream().mapToInt(ChannelStats::getImpressions).sum())
                            .views(views)
                            .clicks(clicks)
                            .participants(participants)
                            .conversions(conversions)
                            .build();

                    ChannelPerformance performance = ChannelPerformance.builder()
                            .engagementRate(Math.round(engagementRate * 10) / 10.0)
                            .conversionRate(Math.round(conversionRate * 10) / 10.0)
                            .clickThroughRate(views > 0 ? Math.round((double) clicks / views * 1000) / 10.0 : 0.0)
                            .build();

                    ChannelCosts costs = ChannelCosts.builder()
                            .distributionCost(cost)
                            .costPerView(views > 0 ? cost.doubleValue() / views : 0.0)
                            .costPerClick(clicks > 0 ? cost.doubleValue() / clicks : 0.0)
                            .costPerAcquisition(participants > 0 ? cost.doubleValue() / participants : 0.0)
                            .roi(Math.round(roi * 10) / 10.0)
                            .build();

                    return ChannelAnalytics.builder()
                            .channelName(channelName)
                            .channelType(channelList.get(0).getChannelType())
                            .metrics(metrics)
                            .performance(performance)
                            .costs(costs)
                            .build();
                })
                .collect(Collectors.toList());
    }

    private List<ChannelAnalytics> sortChannels(List<ChannelAnalytics> channels, String sortBy, String order) {
        Comparator<ChannelAnalytics> comparator;

        switch (sortBy != null ? sortBy.toLowerCase() : "participants") {
            case "views":
                comparator = Comparator.comparingInt(c -> c.getMetrics().getViews());
                break;
            case "engagement_rate":
                comparator = Comparator.comparingDouble(c -> c.getPerformance().getEngagementRate());
                break;
            case "conversion_rate":
                comparator = Comparator.comparingDouble(c -> c.getPerformance().getConversionRate());
                break;
            case "roi":
                comparator = Comparator.comparingDouble(c -> c.getCosts().getRoi());
                break;
            case "participants":
            default:
                comparator = Comparator.comparingInt(c -> c.getMetrics().getParticipants());
                break;
        }

        if ("desc".equalsIgnoreCase(order)) {
            comparator = comparator.reversed();
        }

        return channels.stream().sorted(comparator).collect(Collectors.toList());
    }

    private ChannelComparison buildChannelComparison(List<ChannelAnalytics> channels) {
        if (channels.isEmpty()) {
            return ChannelComparison.builder().build();
        }

        String bestPerformingChannel = channels.stream()
                .max(Comparator.comparingInt(c -> c.getMetrics().getParticipants()))
                .map(ChannelAnalytics::getChannelName)
                .orElse("N/A");

        Map<String, String> bestPerforming = new HashMap<>();
        bestPerforming.put("channel", bestPerformingChannel);
        bestPerforming.put("metric", "participants");

        Map<String, Double> averageMetrics = new HashMap<>();
        int totalChannels = channels.size();
        if (totalChannels > 0) {
            double avgParticipants = channels.stream().mapToInt(c -> c.getMetrics().getParticipants()).average().orElse(0.0);
            double avgEngagement = channels.stream().mapToDouble(c -> c.getPerformance().getEngagementRate()).average().orElse(0.0);
            double avgRoi = channels.stream().mapToDouble(c -> c.getCosts().getRoi()).average().orElse(0.0);

            averageMetrics.put("participants", avgParticipants);
            averageMetrics.put("engagementRate", avgEngagement);
            averageMetrics.put("roi", avgRoi);
        }

        return ChannelComparison.builder()
                .bestPerforming(bestPerforming)
                .averageMetrics(averageMetrics)
                .build();
    }

    private PeriodInfo buildPeriodInfo(LocalDateTime startDate, LocalDateTime endDate) {
        LocalDateTime start = startDate != null ? startDate : LocalDateTime.now().minusDays(30);
        LocalDateTime end = endDate != null ? endDate : LocalDateTime.now();
        long durationDays = ChronoUnit.DAYS.between(start, end);

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