package com.kt.event.analytics.messaging.consumer;

import com.kt.event.analytics.entity.ChannelStats;
import com.kt.event.analytics.messaging.event.DistributionCompletedEvent;
import com.kt.event.analytics.repository.ChannelStatsRepository;
import com.kt.event.analytics.repository.EventStatsRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 배포 완료 Consumer
 *
 * 배포 완료 시 채널 통계 업데이트
 */
@Slf4j
@Component
@ConditionalOnProperty(name = "spring.kafka.enabled", havingValue = "true", matchIfMissing = false)
@RequiredArgsConstructor
public class DistributionCompletedConsumer {

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

    private static final String PROCESSED_DISTRIBUTIONS_KEY = "distribution_completed";
    private static final String CACHE_KEY_PREFIX = "analytics:dashboard:";
    private static final long IDEMPOTENCY_TTL_DAYS = 7;

    /**
     * DistributionCompleted 이벤트 처리 (설계서 기준 - 여러 채널 배열)
     *
     * @Transactional 필수: DB 저장 작업을 위해 트랜잭션 컨텍스트 필요
     */
    @Transactional
    @KafkaListener(topics = "sample.distribution.completed", groupId = "${spring.kafka.consumer.group-id}")
    public void handleDistributionCompleted(String message) {
        try {
            log.info("📩 DistributionCompleted 이벤트 수신: {}", message);

            DistributionCompletedEvent event = objectMapper.readValue(message, DistributionCompletedEvent.class);
            String eventId = event.getEventId();

            // ✅ 1. 멱등성 체크 (중복 처리 방지) - eventId 기반
            Boolean isProcessed = redisTemplate.opsForSet().isMember(PROCESSED_DISTRIBUTIONS_KEY, eventId);
            if (Boolean.TRUE.equals(isProcessed)) {
                log.warn("⚠️ 중복 이벤트 스킵 (이미 처리됨): eventId={}", eventId);
                return;
            }

            // 2. 채널 배열 루프 처리 (설계서: distributedChannels 배열)
            if (event.getDistributedChannels() != null && !event.getDistributedChannels().isEmpty()) {
                for (DistributionCompletedEvent.ChannelDistribution channel : event.getDistributedChannels()) {
                    processChannelStats(eventId, channel);
                }

                log.info("✅ 채널 통계 일괄 업데이트 완료: eventId={}, channelCount={}",
                    eventId, event.getDistributedChannels().size());
            } else {
                log.warn("⚠️ 배포된 채널 없음: eventId={}", eventId);
            }

            // 3. EventStats의 totalViews 업데이트 (모든 채널 노출 수 합계)
            updateTotalViews(eventId);

            // 4. 캐시 무효화 (다음 조회 시 최신 배포 통계 반영)
            String cacheKey = CACHE_KEY_PREFIX + eventId;
            redisTemplate.delete(cacheKey);
            log.debug("🗑️ 캐시 무효화: {}", cacheKey);

            // 5. 멱등성 처리 완료 기록 (7일 TTL) - eventId 기반
            redisTemplate.opsForSet().add(PROCESSED_DISTRIBUTIONS_KEY, eventId);
            redisTemplate.expire(PROCESSED_DISTRIBUTIONS_KEY, IDEMPOTENCY_TTL_DAYS, TimeUnit.DAYS);
            log.debug("✅ 멱등성 기록: eventId={}", eventId);

        } catch (Exception e) {
            log.error("❌ DistributionCompleted 이벤트 처리 실패: {}", e.getMessage(), e);
            throw new RuntimeException("DistributionCompleted 처리 실패", e);
        }
    }

    /**
     * 개별 채널 통계 처리
     */
    private void processChannelStats(String eventId, DistributionCompletedEvent.ChannelDistribution channel) {
        try {
            String channelName = channel.getChannel();

            // 채널 통계 생성 또는 업데이트
            ChannelStats channelStats = channelStatsRepository
                .findByEventIdAndChannelName(eventId, channelName)
                .orElse(ChannelStats.builder()
                    .eventId(eventId)
                    .channelName(channelName)
                    .channelType(channel.getChannelType())
                    .build());

            // 예상 노출 수 저장
            if (channel.getExpectedViews() != null) {
                channelStats.setImpressions(channel.getExpectedViews());
            }

            channelStatsRepository.save(channelStats);

            log.debug("✅ 채널 통계 저장: eventId={}, channel={}, expectedViews={}",
                eventId, channelName, channel.getExpectedViews());

        } catch (Exception e) {
            log.error("❌ 채널 통계 처리 실패: eventId={}, channel={}", eventId, channel.getChannel(), e);
        }
    }

    /**
     * 모든 채널의 예상 노출 수를 합산하여 EventStats.totalViews 업데이트
     */
    private void updateTotalViews(String eventId) {
        try {
            // 모든 채널 통계 조회
            List<ChannelStats> channelStatsList = channelStatsRepository.findByEventId(eventId);

            // 총 노출 수 계산
            int totalViews = channelStatsList.stream()
                .mapToInt(ChannelStats::getImpressions)
                .sum();

            // EventStats 업데이트 - 비관적 락 적용
            eventStatsRepository.findByEventIdWithLock(eventId)
                .ifPresentOrElse(
                    eventStats -> {
                        eventStats.setTotalViews(totalViews);
                        eventStatsRepository.save(eventStats);
                        log.info("✅ 총 노출 수 업데이트: eventId={}, totalViews={}", eventId, totalViews);
                    },
                    () -> log.warn("⚠️ 이벤트 통계 없음: eventId={}", eventId)
                );
        } catch (Exception e) {
            log.error("❌ totalViews 업데이트 실패: eventId={}", eventId, e);
        }
    }
}
