package com.kt.event.analytics.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.kt.event.analytics.messaging.event.DistributionCompletedEvent;
import com.kt.event.analytics.messaging.event.EventCreatedEvent;
import com.kt.event.analytics.messaging.event.ParticipantRegisteredEvent;
import com.kt.event.analytics.repository.ChannelStatsRepository;
import com.kt.event.analytics.repository.EventStatsRepository;
import com.kt.event.analytics.repository.TimelineDataRepository;
import jakarta.annotation.PreDestroy;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.admin.DeleteConsumerGroupOffsetsResult;
import org.apache.kafka.clients.admin.ListConsumerGroupOffsetsResult;
import org.apache.kafka.common.TopicPartition;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.kafka.core.KafkaAdmin;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 샘플 데이터 로더 (Kafka Producer 방식)
 *
 * ⚠️ MVP 전용: 다른 마이크로서비스(Event, Participant, Distribution)가
 *             없는 환경에서 해당 서비스들의 역할을 시뮬레이션합니다.
 *
 * ⚠️ 실제 운영: Analytics Service는 순수 Consumer 역할만 수행해야 하며,
 *             이 클래스는 비활성화되어야 합니다.
 *             → SAMPLE_DATA_ENABLED=false 설정
 *
 * - 서비스 시작 시: Kafka 이벤트 발행하여 샘플 데이터 자동 생성
 * - 서비스 종료 시: PostgreSQL 전체 데이터 삭제
 *
 * 활성화 조건: spring.sample-data.enabled=true (기본값: true)
 */
@Slf4j
@Component
@ConditionalOnProperty(name = "spring.sample-data.enabled", havingValue = "true", matchIfMissing = true)
@RequiredArgsConstructor
public class SampleDataLoader implements ApplicationRunner {

    private final KafkaTemplate<String, String> kafkaTemplate;
    private final KafkaAdmin kafkaAdmin;
    private final ObjectMapper objectMapper;
    private final EventStatsRepository eventStatsRepository;
    private final ChannelStatsRepository channelStatsRepository;
    private final TimelineDataRepository timelineDataRepository;
    private final EntityManager entityManager;
    private final RedisTemplate<String, String> redisTemplate;

    private final Random random = new Random();

    @Value("${spring.kafka.consumer.group-id}")
    private String consumerGroupId;

    // Kafka Topic Names (MVP용 샘플 토픽)
    private static final String EVENT_CREATED_TOPIC = "sample.event.created";
    private static final String PARTICIPANT_REGISTERED_TOPIC = "sample.participant.registered";
    private static final String DISTRIBUTION_COMPLETED_TOPIC = "sample.distribution.completed";

    @Override
    @Transactional
    public void run(ApplicationArguments args) {
        log.info("========================================");
        log.info("🚀 서비스 시작: Kafka 이벤트 발행하여 샘플 데이터 생성");
        log.info("========================================");

        // 항상 기존 데이터 삭제 후 새로 생성
        long existingCount = eventStatsRepository.count();
        if (existingCount > 0) {
            log.info("기존 데이터 {} 건 삭제 중...", existingCount);
            timelineDataRepository.deleteAll();
            channelStatsRepository.deleteAll();
            eventStatsRepository.deleteAll();

            // 삭제 커밋 보장
            entityManager.flush();
            entityManager.clear();

            log.info("✅ 기존 데이터 삭제 완료");
        }

        // Redis 멱등성 키 삭제 (새로운 이벤트 처리를 위해)
        log.info("Redis 멱등성 키 삭제 중...");
        try {
            redisTemplate.delete("processed_events_v2");
            redisTemplate.delete("distribution_completed_v2");
            redisTemplate.delete("processed_participants_v2");
            log.info("✅ Redis 멱등성 키 삭제 완료");
        } catch (Exception e) {
            log.warn("⚠️ Redis 삭제 실패 (read-only replica일 수 있음): {}", e.getMessage());
            log.info("→ Redis 삭제 건너뛰고 계속 진행...");
        }

        try {
            // 1. EventCreated 이벤트 발행 (3개 이벤트)
            publishEventCreatedEvents();
            log.info("⏳ EventStats 생성 대기 중... (5초)");
            Thread.sleep(5000);  // EventCreatedConsumer가 EventStats 생성할 시간

            // 2. DistributionCompleted 이벤트 발행 (각 이벤트당 4개 채널)
            publishDistributionCompletedEvents();
            log.info("⏳ ChannelStats 생성 대기 중... (3초)");
            Thread.sleep(3000);  // DistributionCompletedConsumer가 ChannelStats 생성할 시간

            // 3. ParticipantRegistered 이벤트 발행 (각 이벤트당 다수 참여자)
            publishParticipantRegisteredEvents();
            log.info("⏳ 참여자 등록 이벤트 처리 대기 중... (20초)");
            Thread.sleep(20000);  // ParticipantRegisteredConsumer가 180개 이벤트 처리할 시간 (비관적 락 고려)

            log.info("========================================");
            log.info("🎉 Kafka 이벤트 발행 완료! (Consumer가 처리 중...)");
            log.info("========================================");
            log.info("발행된 이벤트:");
            log.info("  - EventCreated: 3건");
            log.info("  - DistributionCompleted: 3건 (각 이벤트당 4개 채널 배열)");
            log.info("  - ParticipantRegistered: 180건 (MVP 테스트용)");
            log.info("========================================");

            // Consumer 처리 대기 (5초)
            log.info("⏳ 참여자 수 업데이트 대기 중... (5초)");
            Thread.sleep(5000);

            // 4. TimelineData 생성 (시간대별 데이터)
            createTimelineData();
            log.info("✅ TimelineData 생성 완료");

        } catch (Exception e) {
            log.error("샘플 데이터 적재 중 오류 발생", e);
        }
    }

    /**
     * 서비스 종료 시 전체 데이터 삭제 및 Consumer Offset 리셋
     */
    @PreDestroy
    @Transactional
    public void onShutdown() {
        log.info("========================================");
        log.info("🛑 서비스 종료: PostgreSQL 전체 데이터 삭제 + Kafka Consumer Offset 리셋");
        log.info("========================================");

        try {
            // 1. PostgreSQL 데이터 삭제
            long timelineCount = timelineDataRepository.count();
            long channelCount = channelStatsRepository.count();
            long eventCount = eventStatsRepository.count();

            log.info("삭제 대상: 이벤트={}, 채널={}, 타임라인={}",
                    eventCount, channelCount, timelineCount);

            timelineDataRepository.deleteAll();
            channelStatsRepository.deleteAll();
            eventStatsRepository.deleteAll();

            // 삭제 커밋 보장
            entityManager.flush();
            entityManager.clear();

            log.info("✅ 모든 샘플 데이터 삭제 완료!");

            // 2. Kafka Consumer Offset 리셋 (다음 시작 시 처음부터 읽도록)
            resetConsumerOffsets();

            log.info("========================================");

        } catch (Exception e) {
            log.error("샘플 데이터 삭제 중 오류 발생", e);
        }
    }

    /**
     * Kafka Consumer Group Offset 리셋
     *
     * 서비스 종료 시 Consumer offset을 삭제하여 다음 시작 시
     * auto.offset.reset=earliest 설정에 따라 처음부터 읽도록 함
     */
    private void resetConsumerOffsets() {
        try (AdminClient adminClient = AdminClient.create(kafkaAdmin.getConfigurationProperties())) {
            log.info("🔄 Kafka Consumer Offset 리셋 시작: group={}", consumerGroupId);

            // 모든 토픽의 offset 삭제
            Set<TopicPartition> partitions = new HashSet<>();

            // 토픽별 파티션 추가 (설계서상 각 토픽은 3개 파티션)
            for (int i = 0; i < 3; i++) {
                partitions.add(new TopicPartition(EVENT_CREATED_TOPIC, i));
                partitions.add(new TopicPartition(PARTICIPANT_REGISTERED_TOPIC, i));
                partitions.add(new TopicPartition(DISTRIBUTION_COMPLETED_TOPIC, i));
            }

            // Consumer Group Offset 삭제
            DeleteConsumerGroupOffsetsResult result = adminClient.deleteConsumerGroupOffsets(
                consumerGroupId,
                partitions
            );

            // 완료 대기 (최대 10초)
            result.all().get(10, TimeUnit.SECONDS);

            log.info("✅ Kafka Consumer Offset 리셋 완료!");
            log.info("   → 다음 시작 시 처음부터(earliest) 메시지를 읽습니다.");

        } catch (Exception e) {
            // Offset 리셋 실패는 치명적이지 않으므로 경고만 출력
            log.warn("⚠️ Kafka Consumer Offset 리셋 실패 (무시 가능): {}", e.getMessage());
            log.warn("   → 수동으로 Consumer Group ID를 변경하거나, Kafka 도구로 offset을 삭제하세요.");
        }
    }

    /**
     * EventCreated 이벤트 발행
     */
    private void publishEventCreatedEvents() throws Exception {
        // 이벤트 1: 신년맞이 할인 이벤트 (진행중, 높은 성과 - ROI 200%)
        EventCreatedEvent event1 = EventCreatedEvent.builder()
            .eventId("1")
            .eventTitle("신년맞이 20% 할인 이벤트")
            .storeId("store_001")
            .totalInvestment(new BigDecimal("5000000"))
            .expectedRevenue(new BigDecimal("15000000"))  // 투자 대비 3배 수익
            .status("ACTIVE")
            .startDate(java.time.LocalDateTime.of(2025, 1, 23, 0, 0))  // 2025-01-23 시작
            .endDate(null)  // 진행중
            .build();
        publishEvent(EVENT_CREATED_TOPIC, event1);

        // 이벤트 2: 설날 특가 이벤트 (진행중, 중간 성과 - ROI 100%)
        EventCreatedEvent event2 = EventCreatedEvent.builder()
            .eventId("2")
            .eventTitle("설날 특가 선물세트 이벤트")
            .storeId("store_001")
            .totalInvestment(new BigDecimal("3500000"))
            .expectedRevenue(new BigDecimal("7000000"))  // 투자 대비 2배 수익
            .status("ACTIVE")
            .startDate(java.time.LocalDateTime.of(2025, 2, 1, 0, 0))  // 2025-02-01 시작
            .endDate(null)  // 진행중
            .build();
        publishEvent(EVENT_CREATED_TOPIC, event2);

        // 이벤트 3: 겨울 신메뉴 런칭 이벤트 (종료, 저조한 성과 - ROI 50%)
        EventCreatedEvent event3 = EventCreatedEvent.builder()
            .eventId("3")
            .eventTitle("겨울 신메뉴 런칭 이벤트")
            .storeId("store_001")
            .totalInvestment(new BigDecimal("2000000"))
            .expectedRevenue(new BigDecimal("3000000"))  // 투자 대비 1.5배 수익
            .status("COMPLETED")
            .startDate(java.time.LocalDateTime.of(2025, 1, 15, 0, 0))  // 2025-01-15 시작
            .endDate(java.time.LocalDateTime.of(2025, 1, 31, 23, 59))  // 2025-01-31 종료
            .build();
        publishEvent(EVENT_CREATED_TOPIC, event3);

        log.info("✅ EventCreated 이벤트 3건 발행 완료");
    }

    /**
     * DistributionCompleted 이벤트 발행 (설계서 기준 - 이벤트당 1번 발행, 여러 채널 배열)
     */
    private void publishDistributionCompletedEvents() throws Exception {
        String[] eventIds = {"1", "2", "3"};
        int[][] expectedViews = {
            {5000, 10000, 3000, 2000},  // 이벤트1: 우리동네TV, 지니TV, 링고비즈, SNS
            {3500, 7000, 2000, 1500},   // 이벤트2
            {1500, 3000, 1000, 500}     // 이벤트3
        };

        // 각 이벤트의 총 투자 금액
        BigDecimal[] totalInvestments = {
            new BigDecimal("5000000"),  // 이벤트1: 500만원
            new BigDecimal("3500000"),  // 이벤트2: 350만원
            new BigDecimal("2000000")   // 이벤트3: 200만원
        };

        // 채널 배포는 총 투자의 50%만 사용 (나머지는 경품/콘텐츠/운영비용)
        double channelBudgetRatio = 0.50;

        // 채널별 비용 비율 (채널 예산 내에서: 우리동네TV 30%, 지니TV 30%, 링고비즈 25%, SNS 15%)
        double[] costRatios = {0.30, 0.30, 0.25, 0.15};

        for (int i = 0; i < eventIds.length; i++) {
            String eventId = eventIds[i];
            BigDecimal totalInvestment = totalInvestments[i];

            // 채널 배포 예산: 총 투자의 50%
            BigDecimal channelBudget = totalInvestment.multiply(BigDecimal.valueOf(channelBudgetRatio));

            // 4개 채널을 배열로 구성
            List<DistributionCompletedEvent.ChannelDistribution> channels = new ArrayList<>();

            // 1. 우리동네TV (TV) - 채널 예산의 30%
            channels.add(DistributionCompletedEvent.ChannelDistribution.builder()
                .channel("우리동네TV")
                .channelType("TV")
                .status("SUCCESS")
                .expectedViews(expectedViews[i][0])
                .distributionCost(channelBudget.multiply(BigDecimal.valueOf(costRatios[0])))
                .build());

            // 2. 지니TV (TV) - 채널 예산의 30%
            channels.add(DistributionCompletedEvent.ChannelDistribution.builder()
                .channel("지니TV")
                .channelType("TV")
                .status("SUCCESS")
                .expectedViews(expectedViews[i][1])
                .distributionCost(channelBudget.multiply(BigDecimal.valueOf(costRatios[1])))
                .build());

            // 3. 링고비즈 (CALL) - 채널 예산의 25%
            channels.add(DistributionCompletedEvent.ChannelDistribution.builder()
                .channel("링고비즈")
                .channelType("CALL")
                .status("SUCCESS")
                .expectedViews(expectedViews[i][2])
                .distributionCost(channelBudget.multiply(BigDecimal.valueOf(costRatios[2])))
                .build());

            // 4. SNS (SNS) - 채널 예산의 15%
            channels.add(DistributionCompletedEvent.ChannelDistribution.builder()
                .channel("SNS")
                .channelType("SNS")
                .status("SUCCESS")
                .expectedViews(expectedViews[i][3])
                .distributionCost(channelBudget.multiply(BigDecimal.valueOf(costRatios[3])))
                .build());

            // 이벤트 발행 (채널 배열 포함)
            DistributionCompletedEvent event = DistributionCompletedEvent.builder()
                .eventId(eventId)
                .distributedChannels(channels)
                .completedAt(java.time.LocalDateTime.now())
                .build();

            publishEvent(DISTRIBUTION_COMPLETED_TOPIC, event);
        }

        log.info("✅ DistributionCompleted 이벤트 3건 발행 완료 (3 이벤트 × 4 채널 배열)");
    }

    /**
     * ParticipantRegistered 이벤트 발행
     *
     * 현실적인 참여 패턴 반영:
     * - 총 120명의 고유 참여자 풀 생성
     * - 일부 참여자는 여러 이벤트에 중복 참여
     * - 이벤트1: 100명 (user001~user100)
     * - 이벤트2: 50명 (user051~user100) → 50명이 이벤트1과 중복
     * - 이벤트3: 30명 (user071~user100) → 30명이 이전 이벤트들과 중복
     */
    private void publishParticipantRegisteredEvents() throws Exception {
        String[] eventIds = {"1", "2", "3"};
        String[] channels = {"우리동네TV", "지니TV", "링고비즈", "SNS"};

        // 이벤트별 참여자 범위 (중복 참여 반영)
        int[][] participantRanges = {
            {1, 100},    // 이벤트1: user001~user100 (100명)
            {51, 100},   // 이벤트2: user051~user100 (50명, 이벤트1과 50명 중복)
            {71, 100}    // 이벤트3: user071~user100 (30명, 모두 중복)
        };

        int totalPublished = 0;

        for (int i = 0; i < eventIds.length; i++) {
            String eventId = eventIds[i];
            int startUser = participantRanges[i][0];
            int endUser = participantRanges[i][1];
            int eventParticipants = endUser - startUser + 1;

            log.info("이벤트 {} 참여자 발행 시작: user{:03d}~user{:03d} ({}명)",
                eventId, startUser, endUser, eventParticipants);

            // 각 참여자에 대해 ParticipantRegistered 이벤트 발행
            for (int userId = startUser; userId <= endUser; userId++) {
                String participantId = String.format("user%03d", userId);  // user001, user002, ...

                // 채널별 가중치 기반 랜덤 배정
                // SNS: 45%, 우리동네TV: 25%, 지니TV: 20%, 링고비즈: 10%
                int randomValue = random.nextInt(100);
                String channel;
                if (randomValue < 45) {
                    channel = "SNS";  // 0~44: 45%
                } else if (randomValue < 70) {
                    channel = "우리동네TV";  // 45~69: 25%
                } else if (randomValue < 90) {
                    channel = "지니TV";  // 70~89: 20%
                } else {
                    channel = "링고비즈";  // 90~99: 10%
                }

                ParticipantRegisteredEvent event = ParticipantRegisteredEvent.builder()
                    .eventId(eventId)
                    .participantId(participantId)
                    .channel(channel)
                    .build();

                publishEvent(PARTICIPANT_REGISTERED_TOPIC, event);
                totalPublished++;

                // 동시성 충돌 방지: 10개마다 100ms 대기
                if (totalPublished % 10 == 0) {
                    Thread.sleep(100);
                }
            }

            log.info("✅ 이벤트 {} 참여자 발행 완료: {}명", eventId, eventParticipants);
        }

        log.info("========================================");
        log.info("✅ ParticipantRegistered 이벤트 {}건 발행 완료", totalPublished);
        log.info("📊 참여 패턴:");
        log.info("  - 총 고유 참여자: 100명 (user001~user100)");
        log.info("  - 이벤트1 참여: 100명");
        log.info("  - 이벤트2 참여: 50명 (이벤트1과 50명 중복)");
        log.info("  - 이벤트3 참여: 30명 (이벤트1,2와 모두 중복)");
        log.info("  - 3개 이벤트 모두 참여: 30명");
        log.info("  - 2개 이벤트 참여: 20명");
        log.info("  - 1개 이벤트만 참여: 50명");
        log.info("📺 채널별 참여 비율 (가중치):");
        log.info("  - SNS: 45% (가장 높음)");
        log.info("  - 우리동네TV: 25%");
        log.info("  - 지니TV: 20%");
        log.info("  - 링고비즈: 10%");
        log.info("========================================");
    }

    /**
     * TimelineData 생성 (시간대별 샘플 데이터)
     *
     * - 각 이벤트마다 30일 × 24시간 = 720시간 치 hourly 데이터 생성
     * - interval=hourly: 시간별 표시 (최근 7일 적합)
     * - interval=daily: 일별 자동 집계 (30일 전체)
     * - 참여자 수, 조회수, 참여행동, 전환수, 누적 참여자 수
     */
    private void createTimelineData() {
        log.info("📊 TimelineData 생성 시작...");

        String[] eventIds = {"evt_2025012301", "evt_2025012302", "evt_2025012303"};

        // 각 이벤트별 시간당 기준 참여자 수 (이벤트 성과에 따라 다름)
        int[] baseParticipantsPerHour = {4, 2, 1};  // 이벤트1(높음), 이벤트2(중간), 이벤트3(낮음)

        for (int eventIndex = 0; eventIndex < eventIds.length; eventIndex++) {
            String eventId = eventIds[eventIndex];
            int baseParticipant = baseParticipantsPerHour[eventIndex];
            int cumulativeParticipants = 0;

            // 이벤트 ID에서 날짜 파싱 (evt_2025012301 → 2025-01-23)
            String dateStr = eventId.substring(4);  // "2025012301"
            int year = Integer.parseInt(dateStr.substring(0, 4));   // 2025
            int month = Integer.parseInt(dateStr.substring(4, 6));  // 01
            int day = Integer.parseInt(dateStr.substring(6, 8));    // 23

            // 이벤트 시작일부터 30일 치 hourly 데이터 생성
            java.time.LocalDateTime startDate = java.time.LocalDateTime.of(year, month, day, 0, 0);

            for (int dayOffset = 0; dayOffset < 30; dayOffset++) {
                for (int hour = 0; hour < 24; hour++) {
                    java.time.LocalDateTime timestamp = startDate.plusDays(dayOffset).plusHours(hour);

                    // 시간대별 참여자 수 변화 (낮 시간대 12~20시에 더 많음)
                    int hourMultiplier = (hour >= 12 && hour <= 20) ? 2 : 1;
                    int hourlyParticipants = (baseParticipant * hourMultiplier) + random.nextInt(baseParticipant + 1);

                    cumulativeParticipants += hourlyParticipants;

                    // 조회수는 참여자의 3~5배
                    int hourlyViews = hourlyParticipants * (3 + random.nextInt(3));

                    // 참여행동은 참여자의 1~2배
                    int hourlyEngagement = hourlyParticipants * (1 + random.nextInt(2));

                    // 전환수는 참여자의 50~80%
                    int hourlyConversions = (int) (hourlyParticipants * (0.5 + random.nextDouble() * 0.3));

                    // TimelineData 생성
                    com.kt.event.analytics.entity.TimelineData timelineData =
                        com.kt.event.analytics.entity.TimelineData.builder()
                            .eventId(eventId)
                            .timestamp(timestamp)
                            .participants(hourlyParticipants)
                            .views(hourlyViews)
                            .engagement(hourlyEngagement)
                            .conversions(hourlyConversions)
                            .cumulativeParticipants(cumulativeParticipants)
                            .build();

                    timelineDataRepository.save(timelineData);
                }
            }

            log.info("✅ TimelineData 생성 완료: eventId={}, 시작일={}-{:02d}-{:02d}, 30일 × 24시간 = 720건",
                eventId, year, month, day);
        }

        log.info("✅ 전체 TimelineData 생성 완료: 3개 이벤트 × 30일 × 24시간 = 2,160건");
    }

    /**
     * Kafka 이벤트 발행 공통 메서드
     */
    private void publishEvent(String topic, Object event) throws Exception {
        String jsonMessage = objectMapper.writeValueAsString(event);
        kafkaTemplate.send(topic, jsonMessage);
    }
}
