package com.kt.distribution.mapper;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kt.distribution.dto.ChannelStatus;
import com.kt.distribution.dto.DistributionStatusResponse;
import com.kt.distribution.entity.ChannelStatusEntity;
import com.kt.distribution.entity.DistributionStatus;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 배포 상태 Mapper
 *
 * Entity와 DTO 간의 변환을 담당합니다.
 *
 * @author Backend Developer
 * @since 2025-10-24
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class DistributionStatusMapper {

    private final ObjectMapper objectMapper;

    /**
     * DistributionStatusResponse DTO를 DistributionStatus Entity로 변환
     *
     * @param dto DistributionStatusResponse DTO
     * @return DistributionStatus Entity
     */
    public DistributionStatus toEntity(DistributionStatusResponse dto) {
        if (dto == null) {
            return null;
        }

        DistributionStatus entity = DistributionStatus.builder()
                .eventId(dto.getEventId())
                .overallStatus(dto.getOverallStatus())
                .startedAt(dto.getStartedAt())
                .completedAt(dto.getCompletedAt())
                .build();

        // 채널 상태 변환 및 추가
        if (dto.getChannels() != null) {
            List<ChannelStatusEntity> channelEntities = dto.getChannels().stream()
                    .map(channelDto -> toChannelEntity(channelDto, entity))
                    .collect(Collectors.toList());

            channelEntities.forEach(entity::addChannelStatus);
        }

        return entity;
    }

    /**
     * DistributionStatus Entity를 DistributionStatusResponse DTO로 변환
     *
     * @param entity DistributionStatus Entity
     * @return DistributionStatusResponse DTO
     */
    public DistributionStatusResponse toDto(DistributionStatus entity) {
        if (entity == null) {
            return null;
        }

        List<ChannelStatus> channelDtos = entity.getChannels() != null
                ? entity.getChannels().stream()
                        .map(this::toChannelDto)
                        .collect(Collectors.toList())
                : Collections.emptyList();

        return DistributionStatusResponse.builder()
                .eventId(entity.getEventId())
                .overallStatus(entity.getOverallStatus())
                .startedAt(entity.getStartedAt())
                .completedAt(entity.getCompletedAt())
                .channels(channelDtos)
                .build();
    }

    /**
     * ChannelStatus DTO를 ChannelStatusEntity로 변환
     *
     * @param dto ChannelStatus DTO
     * @param distributionStatus 부모 DistributionStatus Entity
     * @return ChannelStatusEntity
     */
    private ChannelStatusEntity toChannelEntity(ChannelStatus dto, DistributionStatus distributionStatus) {
        if (dto == null) {
            return null;
        }

        // impressionSchedule를 JSON 문자열로 변환
        String impressionScheduleJson = null;
        if (dto.getImpressionSchedule() != null && !dto.getImpressionSchedule().isEmpty()) {
            try {
                impressionScheduleJson = objectMapper.writeValueAsString(dto.getImpressionSchedule());
            } catch (JsonProcessingException e) {
                log.error("Failed to serialize impressionSchedule", e);
            }
        }

        return ChannelStatusEntity.builder()
                .distributionStatus(distributionStatus)
                .channel(dto.getChannel())
                .status(dto.getStatus())
                .progress(dto.getProgress())
                .distributionId(dto.getDistributionId())
                .estimatedViews(dto.getEstimatedViews())
                .updateTimestamp(dto.getUpdateTimestamp())
                .eventId(dto.getEventId())
                .impressionSchedule(impressionScheduleJson)
                .postUrl(dto.getPostUrl())
                .postId(dto.getPostId())
                .messageId(dto.getMessageId())
                .completedAt(dto.getCompletedAt())
                .errorMessage(dto.getErrorMessage())
                .retries(dto.getRetries())
                .lastRetryAt(dto.getLastRetryAt())
                .build();
    }

    /**
     * ChannelStatusEntity를 ChannelStatus DTO로 변환
     *
     * @param entity ChannelStatusEntity
     * @return ChannelStatus DTO
     */
    private ChannelStatus toChannelDto(ChannelStatusEntity entity) {
        if (entity == null) {
            return null;
        }

        // JSON 문자열을 List<String>으로 변환
        List<String> impressionScheduleList = null;
        if (entity.getImpressionSchedule() != null && !entity.getImpressionSchedule().isEmpty()) {
            try {
                impressionScheduleList = objectMapper.readValue(
                        entity.getImpressionSchedule(),
                        new TypeReference<List<String>>() {}
                );
            } catch (JsonProcessingException e) {
                log.error("Failed to deserialize impressionSchedule", e);
            }
        }

        return ChannelStatus.builder()
                .channel(entity.getChannel())
                .status(entity.getStatus())
                .progress(entity.getProgress())
                .distributionId(entity.getDistributionId())
                .estimatedViews(entity.getEstimatedViews())
                .updateTimestamp(entity.getUpdateTimestamp())
                .eventId(entity.getEventId())
                .impressionSchedule(impressionScheduleList)
                .postUrl(entity.getPostUrl())
                .postId(entity.getPostId())
                .messageId(entity.getMessageId())
                .completedAt(entity.getCompletedAt())
                .errorMessage(entity.getErrorMessage())
                .retries(entity.getRetries())
                .lastRetryAt(entity.getLastRetryAt())
                .build();
    }
}
