package com.kt.distribution.service;

import com.kt.distribution.adapter.ChannelAdapter;
import com.kt.distribution.dto.*;
import com.kt.distribution.event.DistributionCompletedEvent;
import com.kt.distribution.repository.DistributionStatusRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * Distribution Service
 * 다중 채널 병렬 배포 및 Kafka Event 발행
 *
 * @author System Architect
 * @since 2025-10-23
 */
@Slf4j
@Service
public class DistributionService {

    private final List<ChannelAdapter> channelAdapters;
    private final Optional<KafkaEventPublisher> kafkaEventPublisher;
    private final DistributionStatusRepository statusRepository;

    @Autowired
    public DistributionService(List<ChannelAdapter> channelAdapters,
                                Optional<KafkaEventPublisher> kafkaEventPublisher,
                                DistributionStatusRepository statusRepository) {
        this.channelAdapters = channelAdapters;
        this.kafkaEventPublisher = kafkaEventPublisher;
        this.statusRepository = statusRepository;
    }

    // 병렬 실행을 위한 ExecutorService (채널별 스레드 풀)
    private final ExecutorService executorService = Executors.newFixedThreadPool(10);

    /**
     * 다중 채널 병렬 배포
     *
     * @param request DistributionRequest
     * @return DistributionResponse
     */
    public DistributionResponse distribute(DistributionRequest request) {
        LocalDateTime startedAt = LocalDateTime.now();
        long startTime = System.currentTimeMillis();

        log.info("Starting multi-channel distribution: eventId={}, channels={}",
                request.getEventId(), request.getChannels());

        // 배포 시작 상태 저장 (IN_PROGRESS)
        saveInProgressStatus(request.getEventId(), request.getChannels(), startedAt);

        // 채널 어댑터 매핑 (타입별)
        Map<String, ChannelAdapter> adapterMap = channelAdapters.stream()
                .collect(Collectors.toMap(
                        adapter -> adapter.getChannelType().name(),
                        adapter -> adapter
                ));

        // 병렬 배포 실행
        List<CompletableFuture<ChannelDistributionResult>> futures = request.getChannels().stream()
                .map(channelType -> {
                    ChannelAdapter adapter = adapterMap.get(channelType.name());
                    if (adapter == null) {
                        log.warn("No adapter found for channel: {}", channelType);
                        return CompletableFuture.completedFuture(
                                ChannelDistributionResult.builder()
                                        .channel(channelType)
                                        .success(false)
                                        .errorMessage("Adapter not found")
                                        .build()
                        );
                    }

                    // 비동기 실행
                    return CompletableFuture.supplyAsync(
                            () -> adapter.distribute(request),
                            executorService
                    );
                })
                .collect(Collectors.toList());

        // 모든 배포 완료 대기
        CompletableFuture<Void> allOf = CompletableFuture.allOf(
                futures.toArray(new CompletableFuture[0])
        );

        allOf.join(); // 블로킹 대기 (최대 1분 목표)

        // 결과 수집
        List<ChannelDistributionResult> results = futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList());

        long totalExecutionTime = System.currentTimeMillis() - startTime;
        LocalDateTime completedAt = LocalDateTime.now();

        // 성공/실패 카운트
        long successCount = results.stream().filter(ChannelDistributionResult::isSuccess).count();
        long failureCount = results.size() - successCount;

        log.info("Multi-channel distribution completed: eventId={}, successCount={}, failureCount={}, totalTime={}ms",
                request.getEventId(), successCount, failureCount, totalExecutionTime);

        // 배포 완료 상태 저장 (COMPLETED/PARTIAL_FAILURE/FAILED)
        saveCompletedStatus(request.getEventId(), results, startedAt, completedAt, successCount, failureCount);

        // Kafka Event 발행
        publishDistributionCompletedEvent(request.getEventId(), results);

        // 응답 생성
        return DistributionResponse.builder()
                .eventId(request.getEventId())
                .success(successCount > 0) // 1개 이상 성공하면 성공으로 간주
                .channelResults(results)
                .successCount((int) successCount)
                .failureCount((int) failureCount)
                .completedAt(completedAt)
                .totalExecutionTimeMs(totalExecutionTime)
                .message(String.format("Distribution completed: %d succeeded, %d failed",
                        successCount, failureCount))
                .build();
    }

    /**
     * 배포 상태 조회
     *
     * @param eventId 이벤트 ID
     * @return 배포 상태
     */
    public DistributionStatusResponse getDistributionStatus(String eventId) {
        return statusRepository.findByEventId(eventId)
                .orElse(DistributionStatusResponse.builder()
                        .eventId(eventId)
                        .overallStatus("NOT_FOUND")
                        .channels(List.of())
                        .build());
    }

    /**
     * 배포 시작 상태 저장 (IN_PROGRESS)
     *
     * @param eventId 이벤트 ID
     * @param channels 배포 채널 목록
     * @param startedAt 시작 시각
     */
    private void saveInProgressStatus(String eventId, List<ChannelType> channels, LocalDateTime startedAt) {
        List<ChannelStatus> channelStatuses = channels.stream()
                .map(channelType -> ChannelStatus.builder()
                        .channel(channelType)
                        .status("PENDING")
                        .eventId(eventId)
                        .build())
                .collect(Collectors.toList());

        DistributionStatusResponse status = DistributionStatusResponse.builder()
                .eventId(eventId)
                .overallStatus("IN_PROGRESS")
                .startedAt(startedAt)
                .channels(channelStatuses)
                .build();

        statusRepository.save(eventId, status);
    }

    /**
     * 배포 완료 상태 저장
     *
     * @param eventId 이벤트 ID
     * @param results 배포 결과
     * @param startedAt 시작 시각
     * @param completedAt 완료 시각
     * @param successCount 성공 개수
     * @param failureCount 실패 개수
     */
    private void saveCompletedStatus(String eventId, List<ChannelDistributionResult> results,
                                      LocalDateTime startedAt, LocalDateTime completedAt,
                                      long successCount, long failureCount) {
        // 전체 상태 결정
        String overallStatus;
        if (successCount == 0) {
            overallStatus = "FAILED";
        } else if (failureCount == 0) {
            overallStatus = "COMPLETED";
        } else {
            overallStatus = "PARTIAL_FAILURE";
        }

        // ChannelDistributionResult → ChannelStatus 변환
        List<ChannelStatus> channelStatuses = results.stream()
                .map(result -> convertToChannelStatus(result, eventId, completedAt))
                .collect(Collectors.toList());

        DistributionStatusResponse status = DistributionStatusResponse.builder()
                .eventId(eventId)
                .overallStatus(overallStatus)
                .startedAt(startedAt)
                .completedAt(completedAt)
                .channels(channelStatuses)
                .build();

        statusRepository.save(eventId, status);
    }

    /**
     * ChannelDistributionResult를 ChannelStatus로 변환
     *
     * @param result 배포 결과
     * @param eventId 이벤트 ID
     * @param completedAt 완료 시각
     * @return 채널 상태
     */
    private ChannelStatus convertToChannelStatus(ChannelDistributionResult result, String eventId, LocalDateTime completedAt) {
        return ChannelStatus.builder()
                .channel(result.getChannel())
                .status(result.isSuccess() ? "COMPLETED" : "FAILED")
                .distributionId(result.getDistributionId())
                .estimatedViews(result.getEstimatedReach())
                .eventId(eventId)
                .completedAt(completedAt)
                .errorMessage(result.getErrorMessage())
                .build();
    }

    /**
     * DistributionCompleted 이벤트 발행
     *
     * @param eventId 이벤트 ID
     * @param results 채널별 배포 결과
     */
    private void publishDistributionCompletedEvent(String eventId, List<ChannelDistributionResult> results) {
        if (kafkaEventPublisher.isEmpty()) {
            log.warn("KafkaEventPublisher not available - skipping event publishing");
            return;
        }

        List<com.kt.distribution.event.DistributedChannelInfo> distributedChannels = results.stream()
                .map(result -> com.kt.distribution.event.DistributedChannelInfo.builder()
                        .channel(result.getChannel().getDisplayName())
                        .channelType(result.getChannel().getCategory())
                        .status(result.isSuccess() ? "SUCCESS" : "FAILED")
                        .expectedViews(result.getEstimatedReach())
                        .build())
                .collect(Collectors.toList());

        DistributionCompletedEvent event = DistributionCompletedEvent.builder()
                .eventId(eventId)
                .distributedChannels(distributedChannels)
                .completedAt(LocalDateTime.now())
                .build();

        kafkaEventPublisher.get().publishDistributionCompleted(event);
    }
}
