package com.kt.event.eventservice.application.service;

import com.kt.event.common.exception.BusinessException;
import com.kt.event.common.exception.ErrorCode;
import com.kt.event.eventservice.application.dto.request.*;
import com.kt.event.eventservice.application.dto.response.*;
import com.kt.event.eventservice.domain.enums.JobStatus;
import com.kt.event.eventservice.domain.enums.JobType;
import com.kt.event.eventservice.domain.entity.*;
import com.kt.event.eventservice.domain.enums.EventStatus;
import com.kt.event.eventservice.domain.repository.EventRepository;
import com.kt.event.eventservice.domain.repository.JobRepository;
import com.kt.event.eventservice.infrastructure.client.AIServiceClient;
import com.kt.event.eventservice.infrastructure.client.ContentServiceClient;
import com.kt.event.eventservice.infrastructure.client.dto.AIRecommendationResponse;
import com.kt.event.eventservice.infrastructure.client.dto.ContentImageGenerationRequest;
import com.kt.event.eventservice.infrastructure.client.dto.ContentJobResponse;
import com.kt.event.eventservice.infrastructure.kafka.AIJobKafkaProducer;
import com.kt.event.eventservice.infrastructure.kafka.EventKafkaProducer;
import com.kt.event.eventservice.infrastructure.kafka.ImageJobKafkaProducer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.Hibernate;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.stream.Collectors;

/**
 * 이벤트 서비스
 *
 * 이벤트 전체 생명주기를 관리합니다.
 *
 * @author Event Service Team
 * @version 1.0.0
 * @since 2025-10-23
 */
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class EventService {

    private final EventRepository eventRepository;
    private final JobRepository jobRepository;
    private final AIServiceClient aiServiceClient;
    private final ContentServiceClient contentServiceClient;
    private final AIJobKafkaProducer aiJobKafkaProducer;
    private final ImageJobKafkaProducer imageJobKafkaProducer;
    private final EventKafkaProducer eventKafkaProducer;
    private final EventIdGenerator eventIdGenerator;
    private final JobIdGenerator jobIdGenerator;

    /**
     * 이벤트 생성 (Step 1: 목적 선택)
     *
     * @param userId 사용자 ID
     * @param storeId 매장 ID
     * @param request 목적 선택 요청 (eventId 포함)
     * @return 생성된 이벤트 응답
     */
    @Transactional
    public EventCreatedResponse createEvent(String userId, String storeId, SelectObjectiveRequest request) {
        log.info("이벤트 생성 시작 - userId: {}, storeId: {}, eventId: {}, objective: {}",
                 userId, storeId, request.getEventId(), request.getObjective());

        String eventId = request.getEventId();

        // 동일한 eventId가 이미 존재하는지 확인
        if (eventRepository.findByEventId(eventId).isPresent()) {
            throw new BusinessException(ErrorCode.EVENT_005);
        }

        // 이벤트 엔티티 생성
        Event event = Event.builder()
            .eventId(eventId)
            .userId(userId)
            .storeId(storeId)
            .objective(request.getObjective())
            .eventName("") // 초기에는 비어있음, AI 추천 후 설정
            .status(EventStatus.DRAFT)
            .build();

        // 저장
        event = eventRepository.save(event);

        log.info("이벤트 생성 완료 - eventId: {}", event.getEventId());

        return EventCreatedResponse.builder()
            .eventId(event.getEventId())
            .status(event.getStatus())
            .objective(event.getObjective())
            .createdAt(event.getCreatedAt())
            .build();
    }

    /**
     * 이벤트 상세 조회
     *
     * @param userId 사용자 ID
     * @param eventId 이벤트 ID
     * @return 이벤트 상세 응답
     */
    public EventDetailResponse getEvent(String userId, String eventId) {
        log.info("이벤트 조회 - userId: {}, eventId: {}", userId, eventId);

        Event event = eventRepository.findByEventIdAndUserId(eventId, userId)
            .orElseThrow(() -> new BusinessException(ErrorCode.EVENT_001));

        // Lazy 컬렉션 초기화
        Hibernate.initialize(event.getChannels());
        Hibernate.initialize(event.getGeneratedImages());
        Hibernate.initialize(event.getAiRecommendations());

        return mapToDetailResponse(event);
    }

    /**
     * 이벤트 목록 조회 (페이징, 필터링)
     *
     * @param userId 사용자 ID
     * @param status 상태 필터
     * @param search 검색어
     * @param objective 목적 필터
     * @param pageable 페이징 정보
     * @return 이벤트 목록
     */
    public Page<EventDetailResponse> getEvents(
            String userId,
            EventStatus status,
            String search,
            String objective,
            Pageable pageable) {

        log.info("이벤트 목록 조회 - userId: {}, status: {}, search: {}, objective: {}",
                 userId, status, search, objective);

        Page<Event> events = eventRepository.findEventsByUser(userId, status, search, objective, pageable);

        return events.map(event -> {
            // Lazy 컬렉션 초기화
            Hibernate.initialize(event.getChannels());
            Hibernate.initialize(event.getGeneratedImages());
            Hibernate.initialize(event.getAiRecommendations());
            return mapToDetailResponse(event);
        });
    }

    /**
     * 이벤트 삭제
     *
     * @param userId 사용자 ID
     * @param eventId 이벤트 ID
     */
    @Transactional
    public void deleteEvent(String userId, String eventId) {
        log.info("이벤트 삭제 - userId: {}, eventId: {}", userId, eventId);

        Event event = eventRepository.findByEventIdAndUserId(eventId, userId)
            .orElseThrow(() -> new BusinessException(ErrorCode.EVENT_001));

        if (!event.isDeletable()) {
            throw new BusinessException(ErrorCode.EVENT_002);
        }

        eventRepository.delete(event);

        log.info("이벤트 삭제 완료 - eventId: {}", eventId);
    }

    /**
     * 이벤트 배포
     *
     * @param userId 사용자 ID
     * @param eventId 이벤트 ID
     */
    @Transactional
    public void publishEvent(String userId, String eventId) {
        log.info("이벤트 배포 - userId: {}, eventId: {}", userId, eventId);

        Event event = eventRepository.findByEventIdAndUserId(eventId, userId)
            .orElseThrow(() -> new BusinessException(ErrorCode.EVENT_001));

        // 배포 가능 여부 검증 및 상태 변경
        event.publish();

        eventRepository.save(event);

        // Kafka 이벤트 발행
        eventKafkaProducer.publishEventCreated(
            event.getEventId(),
            event.getUserId(),
            event.getEventName(),
            event.getObjective()
        );

        log.info("이벤트 배포 완료 - eventId: {}", eventId);
    }

    /**
     * 이벤트 종료
     *
     * @param userId 사용자 ID
     * @param eventId 이벤트 ID
     */
    @Transactional
    public void endEvent(String userId, String eventId) {
        log.info("이벤트 종료 - userId: {}, eventId: {}", userId, eventId);

        Event event = eventRepository.findByEventIdAndUserId(eventId, userId)
            .orElseThrow(() -> new BusinessException(ErrorCode.EVENT_001));

        event.end();

        eventRepository.save(event);

        log.info("이벤트 종료 완료 - eventId: {}", eventId);
    }

    /**
     * 이미지 생성 요청
     *
     * @param userId 사용자 ID
     * @param eventId 이벤트 ID
     * @param request 이미지 생성 요청
     * @return 이미지 생성 응답 (Job ID 포함)
     */
    @Transactional
    public ImageGenerationResponse requestImageGeneration(String userId, String eventId, ImageGenerationRequest request) {
        log.info("이미지 생성 요청 - userId: {}, eventId: {}", userId, eventId);

        // 이벤트 조회 및 권한 확인
        Event event = eventRepository.findByEventIdAndUserId(eventId, userId)
            .orElseThrow(() -> new BusinessException(ErrorCode.EVENT_001));

        // DRAFT 상태 확인
        if (!event.isModifiable()) {
            throw new BusinessException(ErrorCode.EVENT_002);
        }

        // 이미지 생성 프롬프트 생성
        String prompt = String.format("이벤트: %s, 설명: %s, 스타일: %s, 플랫폼: %s",
            event.getEventName() != null ? event.getEventName() : "이벤트",
            event.getDescription() != null ? event.getDescription() : "",
            String.join(", ", request.getStyles()),
            String.join(", ", request.getPlatforms()));

        // Job 엔티티 생성
        String jobId = jobIdGenerator.generate(JobType.IMAGE_GENERATION);
        log.info("생성된 jobId: {}", jobId);

        Job job = Job.builder()
            .jobId(jobId)
            .eventId(eventId)
            .jobType(JobType.IMAGE_GENERATION)
            .build();

        job = jobRepository.save(job);

        // Kafka 메시지 발행
        imageJobKafkaProducer.publishImageGenerationJob(
            job.getJobId(),
            userId,
            eventId,
            prompt
        );

        log.info("이미지 생성 작업 메시지 발행 완료 - jobId: {}", job.getJobId());

        // 응답 생성
        return ImageGenerationResponse.builder()
            .jobId(job.getJobId())
            .status(job.getStatus().name())
            .message("이미지 생성 요청이 접수되었습니다.")
            .createdAt(job.getCreatedAt())
            .build();
    }

    /**
     * 이미지 선택
     *
     * @param userId 사용자 ID
     * @param eventId 이벤트 ID
     * @param imageId 이미지 ID
     * @param request 이미지 선택 요청
     */
    @Transactional
    public void selectImage(String userId, String eventId, String imageId, SelectImageRequest request) {
        log.info("이미지 선택 - userId: {}, eventId: {}, imageId: {}", userId, eventId, imageId);

        // 이벤트 조회 및 권한 확인
        Event event = eventRepository.findByEventIdAndUserId(eventId, userId)
            .orElseThrow(() -> new BusinessException(ErrorCode.EVENT_001));

        // DRAFT 상태 확인
        if (!event.isModifiable()) {
            throw new BusinessException(ErrorCode.EVENT_002);
        }

        // 이미지 선택
        event.selectImage(request.getImageId(), request.getImageUrl());

        eventRepository.save(event);

        log.info("이미지 선택 완료 - eventId: {}, imageId: {}", eventId, imageId);
    }

    /**
     * AI 추천 요청
     *
     * @param userId 사용자 ID
     * @param eventId 이벤트 ID (프론트엔드에서 생성한 ID)
     * @param request AI 추천 요청 (objective 포함)
     * @return Job 접수 응답
     */
    @Transactional
    public JobAcceptedResponse requestAiRecommendations(String userId, String eventId, AiRecommendationRequest request) {
        log.info("AI 추천 요청 - userId: {}, eventId: {}, objective: {}",
                 userId, eventId, request.getObjective());

        // 이벤트 조회 또는 생성
        Event event = eventRepository.findByEventIdAndUserId(eventId, userId)
            .orElseGet(() -> {
                log.info("이벤트가 존재하지 않아 새로 생성합니다 - eventId: {}", eventId);

                // storeId 추출 (eventId 형식: EVT-{storeId}-{timestamp}-{random})
                String storeId = request.getStoreInfo().getStoreId();

                // 새 이벤트 생성
                Event newEvent = Event.builder()
                    .eventId(eventId)
                    .userId(userId)
                    .storeId(storeId)
                    .objective(request.getObjective())
                    .eventName("") // 초기에는 비어있음, AI 추천 후 설정
                    .status(EventStatus.DRAFT)
                    .build();

                return eventRepository.save(newEvent);
            });

        // DRAFT 상태 확인
        if (!event.isModifiable()) {
            throw new BusinessException(ErrorCode.EVENT_002);
        }

        // Job 엔티티 생성
        String jobId = jobIdGenerator.generate(JobType.AI_RECOMMENDATION);
        log.info("생성된 jobId: {}", jobId);

        Job job = Job.builder()
            .jobId(jobId)
            .eventId(eventId)
            .jobType(JobType.AI_RECOMMENDATION)
            .build();

        job = jobRepository.save(job);

        // Kafka 메시지 발행
        aiJobKafkaProducer.publishAIGenerationJob(
            job.getJobId(),
            userId,
            eventId,
            request.getStoreInfo().getStoreName(),
            request.getStoreInfo().getCategory(),      // industry
            request.getRegion(),                        // region
            event.getObjective(),                       // objective
            request.getTargetAudience(),                // targetAudience
            request.getBudget()                         // budget
        );

        log.info("AI 추천 요청 완료 - jobId: {}", job.getJobId());

        return JobAcceptedResponse.builder()
            .jobId(job.getJobId())
            .status(job.getStatus())
            .message("AI 추천 생성 요청이 접수되었습니다. /jobs/" + job.getJobId() + "로 상태를 확인하세요.")
            .build();
    }

    /**
     * AI 추천 선택
     *
     * @param userId 사용자 ID
     * @param eventId 이벤트 ID
     * @param request AI 추천 선택 요청
     */
    @Transactional
    public void selectRecommendation(String userId, String eventId, SelectRecommendationRequest request) {
        log.info("AI 추천 선택 - userId: {}, eventId: {}, recommendationId: {}",
                 userId, eventId, request.getRecommendationId());

        // 이벤트 조회 및 권한 확인
        Event event = eventRepository.findByEventIdAndUserId(eventId, userId)
            .orElseThrow(() -> new BusinessException(ErrorCode.EVENT_001));

        // DRAFT 상태 확인
        if (!event.isModifiable()) {
            throw new BusinessException(ErrorCode.EVENT_002);
        }

        // Lazy 컬렉션 초기화
        Hibernate.initialize(event.getAiRecommendations());

        // AI 추천 조회
        AiRecommendation selectedRecommendation = event.getAiRecommendations().stream()
            .filter(rec -> rec.getRecommendationId().equals(request.getRecommendationId()))
            .findFirst()
            .orElseThrow(() -> new BusinessException(ErrorCode.EVENT_003));

        // 모든 추천 선택 해제
        event.getAiRecommendations().forEach(rec -> rec.setSelected(false));

        // 선택한 추천만 선택 처리
        selectedRecommendation.setSelected(true);

        // 커스터마이징이 있으면 적용
        if (request.getCustomizations() != null) {
            SelectRecommendationRequest.Customizations custom = request.getCustomizations();

            if (custom.getEventName() != null) {
                event.updateEventName(custom.getEventName());
            } else {
                event.updateEventName(selectedRecommendation.getEventName());
            }

            if (custom.getDescription() != null) {
                event.updateDescription(custom.getDescription());
            } else {
                event.updateDescription(selectedRecommendation.getDescription());
            }

            if (custom.getStartDate() != null && custom.getEndDate() != null) {
                event.updateEventPeriod(custom.getStartDate(), custom.getEndDate());
            }
        } else {
            // 커스터마이징이 없으면 AI 추천 그대로 적용
            event.updateEventName(selectedRecommendation.getEventName());
            event.updateDescription(selectedRecommendation.getDescription());
        }

        eventRepository.save(event);

        log.info("AI 추천 선택 완료 - eventId: {}, recommendationId: {}", eventId, request.getRecommendationId());
    }

    /**
     * 이미지 편집
     *
     * @param userId 사용자 ID
     * @param eventId 이벤트 ID
     * @param imageId 이미지 ID
     * @param request 이미지 편집 요청
     * @return 이미지 편집 응답
     */
    @Transactional
    public ImageEditResponse editImage(String userId, String eventId, String imageId, ImageEditRequest request) {
        log.info("이미지 편집 - userId: {}, eventId: {}, imageId: {}", userId, eventId, imageId);

        // 이벤트 조회 및 권한 확인
        Event event = eventRepository.findByEventIdAndUserId(eventId, userId)
            .orElseThrow(() -> new BusinessException(ErrorCode.EVENT_001));

        // DRAFT 상태 확인
        if (!event.isModifiable()) {
            throw new BusinessException(ErrorCode.EVENT_002);
        }

        // 이미지가 선택된 이미지인지 확인
        if (!imageId.equals(event.getSelectedImageId())) {
            throw new BusinessException(ErrorCode.EVENT_003);
        }

        // TODO: Content Service에 이미지 편집 요청
        // 현재는 Content Service 연동이 없으므로 Mock 응답 반환
        // 실제로는 ContentServiceClient를 통해 편집 요청을 보내야 함

        log.info("이미지 편집 완료 - eventId: {}, imageId: {}", eventId, imageId);

        // Mock 응답 (실제로는 Content Service의 응답을 반환해야 함)
        return ImageEditResponse.builder()
            .imageId(imageId)
            .imageUrl(event.getSelectedImageUrl())  // 편집된 URL은 Content Service에서 받아와야 함
            .editedAt(java.time.LocalDateTime.now())
            .build();
    }

    /**
     * 배포 채널 선택
     *
     * @param userId 사용자 ID
     * @param eventId 이벤트 ID
     * @param request 배포 채널 선택 요청
     */
    @Transactional
    public void selectChannels(String userId, String eventId, SelectChannelsRequest request) {
        log.info("배포 채널 선택 - userId: {}, eventId: {}, channels: {}",
                 userId, eventId, request.getChannels());

        // 이벤트 조회 및 권한 확인
        Event event = eventRepository.findByEventIdAndUserId(eventId, userId)
            .orElseThrow(() -> new BusinessException(ErrorCode.EVENT_001));

        // DRAFT 상태 확인
        if (!event.isModifiable()) {
            throw new BusinessException(ErrorCode.EVENT_002);
        }

        // 배포 채널 설정
        event.updateChannels(request.getChannels());

        eventRepository.save(event);

        log.info("배포 채널 선택 완료 - eventId: {}, channels: {}", eventId, request.getChannels());
    }

    /**
     * 이벤트 수정
     *
     * @param userId 사용자 ID
     * @param eventId 이벤트 ID
     * @param request 이벤트 수정 요청
     * @return 이벤트 상세 응답
     */
    @Transactional
    public EventDetailResponse updateEvent(String userId, String eventId, UpdateEventRequest request) {
        log.info("이벤트 수정 - userId: {}, eventId: {}", userId, eventId);

        // 이벤트 조회 및 권한 확인
        Event event = eventRepository.findByEventIdAndUserId(eventId, userId)
            .orElseThrow(() -> new BusinessException(ErrorCode.EVENT_001));

        // DRAFT 상태 확인
        if (!event.isModifiable()) {
            throw new BusinessException(ErrorCode.EVENT_002);
        }

        // 이벤트명 수정
        if (request.getEventName() != null && !request.getEventName().trim().isEmpty()) {
            event.updateEventName(request.getEventName());
        }

        // 설명 수정
        if (request.getDescription() != null && !request.getDescription().trim().isEmpty()) {
            event.updateDescription(request.getDescription());
        }

        // 이벤트 기간 수정
        if (request.getStartDate() != null && request.getEndDate() != null) {
            event.updateEventPeriod(request.getStartDate(), request.getEndDate());
        }

        event = eventRepository.save(event);

        // Lazy 컬렉션 초기화
        Hibernate.initialize(event.getChannels());
        Hibernate.initialize(event.getGeneratedImages());
        Hibernate.initialize(event.getAiRecommendations());

        log.info("이벤트 수정 완료 - eventId: {}", eventId);

        return mapToDetailResponse(event);
    }

    // ==== Private Helper Methods ==== //

    /**
     * Event Entity를 EventDetailResponse DTO로 변환
     */
    private EventDetailResponse mapToDetailResponse(Event event) {
        return EventDetailResponse.builder()
            .eventId(event.getEventId())
            .userId(event.getUserId())
            .storeId(event.getStoreId())
            .eventName(event.getEventName())
            .description(event.getDescription())
            .objective(event.getObjective())
            .startDate(event.getStartDate())
            .endDate(event.getEndDate())
            .status(event.getStatus())
            .selectedImageId(event.getSelectedImageId())
            .selectedImageUrl(event.getSelectedImageUrl())
            .participants(event.getParticipants())
            .targetParticipants(event.getTargetParticipants())
            .roi(event.getRoi())
            .generatedImages(
                event.getGeneratedImages().stream()
                    .map(img -> EventDetailResponse.GeneratedImageDto.builder()
                        .imageId(img.getImageId())
                        .imageUrl(img.getImageUrl())
                        .style(img.getStyle())
                        .platform(img.getPlatform())
                        .isSelected(img.isSelected())
                        .createdAt(img.getCreatedAt())
                        .build())
                    .collect(Collectors.toList())
            )
            .aiRecommendations(
                event.getAiRecommendations().stream()
                    .map(rec -> EventDetailResponse.AiRecommendationDto.builder()
                        .recommendationId(rec.getRecommendationId())
                        .eventName(rec.getEventName())
                        .description(rec.getDescription())
                        .promotionType(rec.getPromotionType())
                        .targetAudience(rec.getTargetAudience())
                        .isSelected(rec.isSelected())
                        .build())
                    .collect(Collectors.toList())
            )
            .channels(event.getChannels())
            .createdAt(event.getCreatedAt())
            .updatedAt(event.getUpdatedAt())
            .build();
    }

    /**
     * AI 추천안 조회 (AI Service에서 직접 조회)
     *
     * @param userId 사용자 ID
     * @param eventId 이벤트 ID
     * @return AI 추천 결과
     */
    public AIRecommendationResponse getAiRecommendations(String userId, String eventId) {
        log.info("AI 추천안 조회 - userId: {}, eventId: {}", userId, eventId);

        // 이벤트 권한 확인
        Event event = eventRepository.findByEventIdAndUserId(eventId, userId)
            .orElseThrow(() -> new BusinessException(ErrorCode.EVENT_001));

        // AI Service에서 추천안 조회
        try {
            AIRecommendationResponse response = aiServiceClient.getRecommendation(eventId);
            log.info("AI 추천안 조회 성공 - eventId: {}, 추천안 수: {}",
                     eventId, response.getRecommendations() != null ? response.getRecommendations().size() : 0);
            return response;
        } catch (Exception e) {
            log.error("AI 추천안 조회 실패 - eventId: {}", eventId, e);
            throw new BusinessException(ErrorCode.AI_004);
        }
    }
}
