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.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * User ROI Analytics Service
 */
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserRoiAnalyticsService {

    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:roi:";
    private static final long CACHE_TTL = 1800;

    public UserRoiAnalyticsResponse getUserRoiAnalytics(String userId, boolean includeProjection, boolean refresh) {
        log.info("사용자 ROI 분석 조회 시작: userId={}, refresh={}", userId, refresh);

        String cacheKey = CACHE_KEY_PREFIX + userId;

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

        List<EventStats> allEvents = eventStatsRepository.findAllByUserId(userId);
        if (allEvents.isEmpty()) {
            return buildEmptyResponse(userId);
        }

        UserRoiAnalyticsResponse response = buildRoiResponse(userId, allEvents, includeProjection);

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

        return response;
    }

    private UserRoiAnalyticsResponse buildEmptyResponse(String userId) {
        LocalDateTime now = LocalDateTime.now();
        return UserRoiAnalyticsResponse.builder()
                .userId(userId)
                .period(PeriodInfo.builder()
                        .startDate(now)
                        .endDate(now)
                        .durationDays(0)
                        .build())
                .totalEvents(0)
                .overallInvestment(InvestmentDetails.builder()
                        .total(BigDecimal.ZERO)
                        .contentCreation(BigDecimal.ZERO)
                        .operation(BigDecimal.ZERO)
                        .distribution(BigDecimal.ZERO)
                        .prizeCost(BigDecimal.ZERO)
                        .channelCost(BigDecimal.ZERO)
                        .build())
                .overallRevenue(RevenueDetails.builder()
                        .total(BigDecimal.ZERO)
                        .directSales(BigDecimal.ZERO)
                        .expectedSales(BigDecimal.ZERO)
                        .newCustomerRevenue(BigDecimal.ZERO)
                        .existingCustomerRevenue(BigDecimal.ZERO)
                        .brandValue(BigDecimal.ZERO)
                        .build())
                .overallRoi(RoiCalculation.builder()
                        .netProfit(BigDecimal.ZERO)
                        .roiPercentage(0.0)
                        .build())
                .eventRois(new ArrayList<>())
                .lastUpdatedAt(LocalDateTime.now())
                .dataSource("empty")
                .build();
    }

    private UserRoiAnalyticsResponse buildRoiResponse(String userId, List<EventStats> allEvents, boolean includeProjection) {
        BigDecimal totalInvestment = allEvents.stream().map(EventStats::getTotalInvestment).reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal totalRevenue = allEvents.stream().map(EventStats::getExpectedRevenue).reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal totalProfit = totalRevenue.subtract(totalInvestment);

        Double roiPercentage = totalInvestment.compareTo(BigDecimal.ZERO) > 0
                ? totalProfit.divide(totalInvestment, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).doubleValue()
                : 0.0;

        // ChannelStats에서 실제 배포 비용 집계
        List<String> eventIds = allEvents.stream().map(EventStats::getEventId).collect(Collectors.toList());
        List<ChannelStats> allChannelStats = channelStatsRepository.findByEventIdIn(eventIds);

        BigDecimal actualDistribution = allChannelStats.stream()
                .map(ChannelStats::getDistributionCost)
                .reduce(BigDecimal.ZERO, BigDecimal::add);

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

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

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

        // 매출 분배: 직접 매출 70%, 예상 추가 매출 30% / 신규 고객 40%, 기존 고객 60%
        BigDecimal directSales = totalRevenue.multiply(BigDecimal.valueOf(0.70));
        BigDecimal expectedSales = totalRevenue.multiply(BigDecimal.valueOf(0.30));
        BigDecimal newCustomerRevenue = totalRevenue.multiply(BigDecimal.valueOf(0.40));
        BigDecimal existingCustomerRevenue = totalRevenue.multiply(BigDecimal.valueOf(0.60));

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

        RoiCalculation roiCalc = RoiCalculation.builder()
                .netProfit(totalProfit)
                .roiPercentage(Math.round(roiPercentage * 10) / 10.0)
                .build();

        int totalParticipants = allEvents.stream().mapToInt(EventStats::getTotalParticipants).sum();
        CostEfficiency efficiency = CostEfficiency.builder()
                .costPerParticipant(totalParticipants > 0 ? totalInvestment.doubleValue() / totalParticipants : 0.0)
                .revenuePerParticipant(totalParticipants > 0 ? totalRevenue.doubleValue() / totalParticipants : 0.0)
                .build();

        RevenueProjection projection = includeProjection ? RevenueProjection.builder()
                .currentRevenue(totalRevenue)
                .projectedFinalRevenue(totalRevenue.multiply(BigDecimal.valueOf(1.2)))
                .confidenceLevel(85.0)
                .basedOn("Historical trend analysis")
                .build() : null;

        List<UserRoiAnalyticsResponse.EventRoiSummary> eventRois = allEvents.stream()
                .map(event -> {
                    Double eventRoi = event.getTotalInvestment().compareTo(BigDecimal.ZERO) > 0
                            ? event.getExpectedRevenue().subtract(event.getTotalInvestment())
                            .divide(event.getTotalInvestment(), 4, RoundingMode.HALF_UP)
                            .multiply(BigDecimal.valueOf(100)).doubleValue()
                            : 0.0;

                    return UserRoiAnalyticsResponse.EventRoiSummary.builder()
                            .eventId(event.getEventId())
                            .eventTitle(event.getEventTitle())
                            .totalInvestment(event.getTotalInvestment().doubleValue())
                            .expectedRevenue(event.getExpectedRevenue().doubleValue())
                            .roi(Math.round(eventRoi * 10) / 10.0)
                            .status(event.getStatus())
                            .build();
                })
                .sorted(Comparator.comparingDouble(UserRoiAnalyticsResponse.EventRoiSummary::getRoi).reversed())
                .collect(Collectors.toList());

        // 전체 이벤트의 최소/최대 날짜로 period 계산
        PeriodInfo period = buildPeriodFromEvents(allEvents);

        return UserRoiAnalyticsResponse.builder()
                .userId(userId)
                .period(period)
                .totalEvents(allEvents.size())
                .overallInvestment(investment)
                .overallRevenue(revenue)
                .overallRoi(roiCalc)
                .costEfficiency(efficiency)
                .projection(projection)
                .eventRois(eventRois)
                .lastUpdatedAt(LocalDateTime.now())
                .dataSource("cached")
                .build();
    }

    /**
     * 전체 이벤트의 생성/수정 시간 기반으로 period 계산
     */
    private PeriodInfo buildPeriodFromEvents(List<EventStats> events) {
        LocalDateTime start = events.stream()
                .map(EventStats::getCreatedAt)
                .min(LocalDateTime::compareTo)
                .orElse(LocalDateTime.now());

        LocalDateTime end = events.stream()
                .map(EventStats::getUpdatedAt)
                .max(LocalDateTime::compareTo)
                .orElse(LocalDateTime.now());

        return PeriodInfo.builder()
                .startDate(start)
                .endDate(end)
                .durationDays((int) ChronoUnit.DAYS.between(start, end))
                .build();
    }
}
