package com.kt.event.content.infra.gateway;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.kt.event.content.biz.domain.Content;
import com.kt.event.content.biz.domain.GeneratedImage;
import com.kt.event.content.biz.domain.ImageStyle;
import com.kt.event.content.biz.domain.Job;
import com.kt.event.content.biz.domain.Platform;
import com.kt.event.content.biz.dto.RedisImageData;
import com.kt.event.content.biz.dto.RedisJobData;
import com.kt.event.content.biz.usecase.out.ContentReader;
import com.kt.event.content.biz.usecase.out.ContentWriter;
import com.kt.event.content.biz.usecase.out.ImageReader;
import com.kt.event.content.biz.usecase.out.ImageWriter;
import com.kt.event.content.biz.usecase.out.JobReader;
import com.kt.event.content.biz.usecase.out.JobWriter;
import com.kt.event.content.biz.usecase.out.RedisAIDataReader;
import com.kt.event.content.biz.usecase.out.RedisImageWriter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * Redis Gateway 구현체
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class RedisGateway implements RedisAIDataReader, RedisImageWriter, ImageWriter, ImageReader, JobWriter, JobReader, ContentReader, ContentWriter {

    private final RedisTemplate<String, Object> redisTemplate;
    private final ObjectMapper objectMapper;

    private static final String AI_DATA_KEY_PREFIX = "ai:event:";
    private static final String IMAGE_URL_KEY_PREFIX = "image:url:";
    private static final Duration DEFAULT_TTL = Duration.ofHours(24);

    @Override
    public Optional<Map<String, Object>> getAIRecommendation(String eventId) {
        try {
            String key = AI_DATA_KEY_PREFIX + eventId;
            Object data = redisTemplate.opsForValue().get(key);

            if (data == null) {
                log.warn("AI 이벤트 데이터를 찾을 수 없음: eventId={}", eventId);
                return Optional.empty();
            }

            @SuppressWarnings("unchecked")
            Map<String, Object> aiData = objectMapper.convertValue(data, Map.class);
            return Optional.of(aiData);
        } catch (Exception e) {
            log.error("AI 이벤트 데이터 조회 실패: eventId={}", eventId, e);
            return Optional.empty();
        }
    }

    @Override
    public void cacheImages(String eventId, List<GeneratedImage> images, long ttlSeconds) {
        try {
            String key = IMAGE_URL_KEY_PREFIX + eventId;

            // 이미지 목록을 캐싱
            redisTemplate.opsForValue().set(key, images, Duration.ofSeconds(ttlSeconds));
            log.info("이미지 목록 캐싱 완료: eventId={}, count={}, ttl={}초",
                    eventId, images.size(), ttlSeconds);
        } catch (Exception e) {
            log.error("이미지 목록 캐싱 실패: eventId={}", eventId, e);
        }
    }

    /**
     * 이미지 URL 캐시 삭제
     */
    public void deleteImageUrl(String eventId) {
        try {
            String key = IMAGE_URL_KEY_PREFIX + eventId;
            redisTemplate.delete(key);
            log.info("이미지 URL 캐시 삭제: eventId={}", eventId);
        } catch (Exception e) {
            log.error("이미지 URL 캐시 삭제 실패: eventId={}", eventId, e);
        }
    }

    /**
     * AI 이벤트 데이터 캐시 삭제
     */
    public void deleteAIEventData(String eventId) {
        try {
            String key = AI_DATA_KEY_PREFIX + eventId;
            redisTemplate.delete(key);
            log.info("AI 이벤트 데이터 캐시 삭제: eventId={}", eventId);
        } catch (Exception e) {
            log.error("AI 이벤트 데이터 캐시 삭제 실패: eventId={}", eventId, e);
        }
    }

    // ==================== 이미지 CRUD ====================

    private static final String IMAGE_KEY_PREFIX = "content:image:";

    /**
     * 이미지 저장
     * Key: content:image:{eventId}:{style}:{platform}
     */
    public void saveImage(RedisImageData imageData, long ttlSeconds) {
        try {
            String key = buildImageKey(imageData.getEventId(), imageData.getStyle(), imageData.getPlatform());
            String json = objectMapper.writeValueAsString(imageData);
            redisTemplate.opsForValue().set(key, json, Duration.ofSeconds(ttlSeconds));
            log.info("이미지 저장 완료: key={}, ttl={}초", key, ttlSeconds);
        } catch (Exception e) {
            log.error("이미지 저장 실패: eventId={}, style={}, platform={}",
                    imageData.getEventId(), imageData.getStyle(), imageData.getPlatform(), e);
        }
    }

    /**
     * 특정 이미지 조회
     */
    public Optional<RedisImageData> getImage(String eventId, ImageStyle style, Platform platform) {
        try {
            String key = buildImageKey(eventId, style, platform);
            Object data = redisTemplate.opsForValue().get(key);

            if (data == null) {
                log.warn("이미지를 찾을 수 없음: key={}", key);
                return Optional.empty();
            }

            RedisImageData imageData = objectMapper.readValue(data.toString(), RedisImageData.class);
            return Optional.of(imageData);
        } catch (Exception e) {
            log.error("이미지 조회 실패: eventId={}, style={}, platform={}", eventId, style, platform, e);
            return Optional.empty();
        }
    }

    /**
     * 이벤트의 모든 이미지 조회
     */
    public List<RedisImageData> getImagesByEventId(String eventId) {
        try {
            String pattern = IMAGE_KEY_PREFIX + eventId + ":*";
            var keys = redisTemplate.keys(pattern);

            if (keys == null || keys.isEmpty()) {
                log.warn("이벤트 이미지를 찾을 수 없음: eventId={}", eventId);
                return new ArrayList<>();
            }

            List<RedisImageData> images = new ArrayList<>();
            for (Object key : keys) {
                Object data = redisTemplate.opsForValue().get(key);
                if (data != null) {
                    RedisImageData imageData = objectMapper.readValue(data.toString(), RedisImageData.class);
                    images.add(imageData);
                }
            }

            log.info("이벤트 이미지 조회 완료: eventId={}, count={}", eventId, images.size());
            return images;
        } catch (Exception e) {
            log.error("이벤트 이미지 조회 실패: eventId={}", eventId, e);
            return new ArrayList<>();
        }
    }

    /**
     * 이미지 삭제
     */
    public void deleteImage(String eventId, ImageStyle style, Platform platform) {
        try {
            String key = buildImageKey(eventId, style, platform);
            redisTemplate.delete(key);
            log.info("이미지 삭제 완료: key={}", key);
        } catch (Exception e) {
            log.error("이미지 삭제 실패: eventId={}, style={}, platform={}", eventId, style, platform, e);
        }
    }

    /**
     * 여러 이미지 저장
     */
    public void saveImages(String eventId, List<RedisImageData> images, long ttlSeconds) {
        images.forEach(image -> saveImage(image, ttlSeconds));
        log.info("여러 이미지 저장 완료: eventId={}, count={}", eventId, images.size());
    }

    /**
     * 이미지 Key 생성
     */
    private String buildImageKey(String eventId, ImageStyle style, Platform platform) {
        return IMAGE_KEY_PREFIX + eventId + ":" + style.name() + ":" + platform.name();
    }

    // ==================== Job 상태 관리 ====================

    private static final String JOB_KEY_PREFIX = "job:";

    /**
     * Job 생성/저장
     * Key: job:{jobId}
     */
    public void saveJob(RedisJobData jobData, long ttlSeconds) {
        try {
            String key = JOB_KEY_PREFIX + jobData.getId();

            // Hash 형태로 저장
            Map<String, String> jobFields = Map.of(
                    "id", jobData.getId(),
                    "eventId", jobData.getEventId(),
                    "jobType", jobData.getJobType(),
                    "status", jobData.getStatus(),
                    "progress", String.valueOf(jobData.getProgress()),
                    "resultMessage", jobData.getResultMessage() != null ? jobData.getResultMessage() : "",
                    "errorMessage", jobData.getErrorMessage() != null ? jobData.getErrorMessage() : "",
                    "createdAt", jobData.getCreatedAt().toString(),
                    "updatedAt", jobData.getUpdatedAt().toString()
            );

            redisTemplate.opsForHash().putAll(key, jobFields);
            redisTemplate.expire(key, Duration.ofSeconds(ttlSeconds));

            log.info("Job 저장 완료: jobId={}, status={}, ttl={}초", jobData.getId(), jobData.getStatus(), ttlSeconds);
        } catch (Exception e) {
            log.error("Job 저장 실패: jobId={}", jobData.getId(), e);
        }
    }

    /**
     * Job 조회
     */
    public Optional<RedisJobData> getJob(String jobId) {
        try {
            String key = JOB_KEY_PREFIX + jobId;
            Map<Object, Object> jobFields = redisTemplate.opsForHash().entries(key);

            if (jobFields.isEmpty()) {
                log.warn("Job을 찾을 수 없음: jobId={}", jobId);
                return Optional.empty();
            }

            RedisJobData jobData = RedisJobData.builder()
                    .id(getString(jobFields, "id"))
                    .eventId(getString(jobFields, "eventId"))
                    .jobType(getString(jobFields, "jobType"))
                    .status(getString(jobFields, "status"))
                    .progress(getInteger(jobFields, "progress"))
                    .resultMessage(getString(jobFields, "resultMessage"))
                    .errorMessage(getString(jobFields, "errorMessage"))
                    .createdAt(getLocalDateTime(jobFields, "createdAt"))
                    .updatedAt(getLocalDateTime(jobFields, "updatedAt"))
                    .build();

            return Optional.of(jobData);
        } catch (Exception e) {
            log.error("Job 조회 실패: jobId={}", jobId, e);
            return Optional.empty();
        }
    }

    /**
     * Job 상태 업데이트
     */
    public void updateJobStatus(String jobId, String status, Integer progress) {
        try {
            String key = JOB_KEY_PREFIX + jobId;
            redisTemplate.opsForHash().put(key, "status", status);
            redisTemplate.opsForHash().put(key, "progress", String.valueOf(progress));
            redisTemplate.opsForHash().put(key, "updatedAt", LocalDateTime.now().toString());

            log.info("Job 상태 업데이트: jobId={}, status={}, progress={}", jobId, status, progress);
        } catch (Exception e) {
            log.error("Job 상태 업데이트 실패: jobId={}", jobId, e);
        }
    }

    /**
     * Job 결과 메시지 업데이트
     */
    public void updateJobResult(String jobId, String resultMessage) {
        try {
            String key = JOB_KEY_PREFIX + jobId;
            redisTemplate.opsForHash().put(key, "resultMessage", resultMessage);
            redisTemplate.opsForHash().put(key, "updatedAt", LocalDateTime.now().toString());

            log.info("Job 결과 업데이트: jobId={}, resultMessage={}", jobId, resultMessage);
        } catch (Exception e) {
            log.error("Job 결과 업데이트 실패: jobId={}", jobId, e);
        }
    }

    /**
     * Job 에러 메시지 업데이트
     */
    public void updateJobError(String jobId, String errorMessage) {
        try {
            String key = JOB_KEY_PREFIX + jobId;
            redisTemplate.opsForHash().put(key, "errorMessage", errorMessage);
            redisTemplate.opsForHash().put(key, "status", "FAILED");
            redisTemplate.opsForHash().put(key, "updatedAt", LocalDateTime.now().toString());

            log.info("Job 에러 업데이트: jobId={}, errorMessage={}", jobId, errorMessage);
        } catch (Exception e) {
            log.error("Job 에러 업데이트 실패: jobId={}", jobId, e);
        }
    }

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

    private String getString(Map<Object, Object> map, String key) {
        Object value = map.get(key);
        return value != null ? value.toString() : null;
    }

    private Long getLong(Map<Object, Object> map, String key) {
        String value = getString(map, key);
        return value != null && !value.isEmpty() ? Long.parseLong(value) : null;
    }

    private Integer getInteger(Map<Object, Object> map, String key) {
        String value = getString(map, key);
        return value != null && !value.isEmpty() ? Integer.parseInt(value) : null;
    }

    private LocalDateTime getLocalDateTime(Map<Object, Object> map, String key) {
        String value = getString(map, key);
        return value != null && !value.isEmpty() ? LocalDateTime.parse(value) : null;
    }

    // ==================== ContentReader 구현 ====================

    private static final String CONTENT_META_KEY_PREFIX = "content:meta:";
    private static final String IMAGE_BY_ID_KEY_PREFIX = "content:image:id:";
    private static final String IMAGE_IDS_SET_KEY_PREFIX = "content:images:";

    @Override
    public Optional<Content> findByEventDraftIdWithImages(String eventId) {
        try {
            String contentKey = CONTENT_META_KEY_PREFIX + eventId;
            Map<Object, Object> contentFields = redisTemplate.opsForHash().entries(contentKey);

            if (contentFields.isEmpty()) {
                log.warn("Content를 찾을 수 없음: eventId={}", eventId);
                return Optional.empty();
            }

            // 이미지 목록 조회
            List<GeneratedImage> images = findImagesByEventDraftId(eventId);

            // Content 재구성
            Content content = Content.builder()
                    .id(getLong(contentFields, "id"))
                    .eventId(getString(contentFields, "eventId"))
                    .eventTitle(getString(contentFields, "eventTitle"))
                    .eventDescription(getString(contentFields, "eventDescription"))
                    .images(images)
                    .createdAt(getLocalDateTime(contentFields, "createdAt"))
                    .updatedAt(getLocalDateTime(contentFields, "updatedAt"))
                    .build();

            return Optional.of(content);
        } catch (Exception e) {
            log.error("Content 조회 실패: eventId={}", eventId, e);
            return Optional.empty();
        }
    }

    @Override
    public Optional<GeneratedImage> findImageById(Long imageId) {
        try {
            String key = IMAGE_BY_ID_KEY_PREFIX + imageId;
            Object data = redisTemplate.opsForValue().get(key);

            if (data == null) {
                log.warn("이미지를 찾을 수 없음: imageId={}", imageId);
                return Optional.empty();
            }

            GeneratedImage image = objectMapper.readValue(data.toString(), GeneratedImage.class);
            return Optional.of(image);
        } catch (Exception e) {
            log.error("이미지 조회 실패: imageId={}", imageId, e);
            return Optional.empty();
        }
    }

    @Override
    public List<GeneratedImage> findImagesByEventDraftId(String eventId) {
        try {
            String setKey = IMAGE_IDS_SET_KEY_PREFIX + eventId;
            var imageIdSet = redisTemplate.opsForSet().members(setKey);

            if (imageIdSet == null || imageIdSet.isEmpty()) {
                log.info("이미지 목록이 비어있음: eventId={}", eventId);
                return new ArrayList<>();
            }

            List<GeneratedImage> images = new ArrayList<>();
            for (Object imageIdObj : imageIdSet) {
                Long imageId = Long.valueOf(imageIdObj.toString());
                findImageById(imageId).ifPresent(images::add);
            }

            log.info("이미지 목록 조회 완료: eventId={}, count={}", eventId, images.size());
            return images;
        } catch (Exception e) {
            log.error("이미지 목록 조회 실패: eventId={}", eventId, e);
            return new ArrayList<>();
        }
    }

    // ==================== ContentWriter 구현 ====================

    private static Long nextContentId = 1L;
    private static Long nextImageId = 1L;

    @Override
    public Content save(Content content) {
        try {
            Long id = content.getId() != null ? content.getId() : nextContentId++;
            String contentKey = CONTENT_META_KEY_PREFIX + content.getEventId();

            // Content 메타 정보 저장
            Map<String, String> contentFields = new java.util.HashMap<>();
            contentFields.put("id", String.valueOf(id));
            contentFields.put("eventId", String.valueOf(content.getEventId()));
            contentFields.put("eventTitle", content.getEventTitle() != null ? content.getEventTitle() : "");
            contentFields.put("eventDescription", content.getEventDescription() != null ? content.getEventDescription() : "");
            contentFields.put("createdAt", content.getCreatedAt() != null ? content.getCreatedAt().toString() : LocalDateTime.now().toString());
            contentFields.put("updatedAt", content.getUpdatedAt() != null ? content.getUpdatedAt().toString() : LocalDateTime.now().toString());

            redisTemplate.opsForHash().putAll(contentKey, contentFields);
            redisTemplate.expire(contentKey, DEFAULT_TTL);

            // Content 재구성하여 반환
            Content savedContent = Content.builder()
                    .id(id)
                    .eventId(content.getEventId())
                    .eventTitle(content.getEventTitle())
                    .eventDescription(content.getEventDescription())
                    .images(content.getImages())
                    .createdAt(content.getCreatedAt())
                    .updatedAt(content.getUpdatedAt())
                    .build();

            log.info("Content 저장 완료: contentId={}, eventId={}", id, content.getEventId());
            return savedContent;
        } catch (Exception e) {
            log.error("Content 저장 실패: eventId={}", content.getEventId(), e);
            throw new RuntimeException("Content 저장 실패", e);
        }
    }

    @Override
    public GeneratedImage saveImage(GeneratedImage image) {
        try {
            Long imageId = image.getId() != null ? image.getId() : nextImageId++;

            // GeneratedImage 저장
            String imageKey = IMAGE_BY_ID_KEY_PREFIX + imageId;
            GeneratedImage savedImage = GeneratedImage.builder()
                    .id(imageId)
                    .eventId(image.getEventId())
                    .style(image.getStyle())
                    .platform(image.getPlatform())
                    .cdnUrl(image.getCdnUrl())
                    .prompt(image.getPrompt())
                    .selected(image.isSelected())
                    .createdAt(image.getCreatedAt() != null ? image.getCreatedAt() : LocalDateTime.now())
                    .updatedAt(image.getUpdatedAt() != null ? image.getUpdatedAt() : LocalDateTime.now())
                    .build();

            String json = objectMapper.writeValueAsString(savedImage);
            redisTemplate.opsForValue().set(imageKey, json, DEFAULT_TTL);

            // Image ID를 Set에 추가
            String setKey = IMAGE_IDS_SET_KEY_PREFIX + image.getEventId();
            redisTemplate.opsForSet().add(setKey, imageId);
            redisTemplate.expire(setKey, DEFAULT_TTL);

            log.info("이미지 저장 완료: imageId={}, eventId={}", imageId, image.getEventId());
            return savedImage;
        } catch (Exception e) {
            log.error("이미지 저장 실패: eventId={}", image.getEventId(), e);
            throw new RuntimeException("이미지 저장 실패", e);
        }
    }

    @Override
    public GeneratedImage getImageById(Long imageId) {
        try {
            Optional<GeneratedImage> imageOpt = findImageById(imageId);
            return imageOpt.orElse(null);
        } catch (Exception e) {
            log.error("이미지 조회 실패: imageId={}", imageId, e);
            return null;
        }
    }

    @Override
    public void deleteImageById(Long imageId) {
        try {
            // 이미지 조회
            Optional<GeneratedImage> imageOpt = findImageById(imageId);
            if (imageOpt.isEmpty()) {
                log.warn("삭제할 이미지를 찾을 수 없음: imageId={}", imageId);
                return;
            }

            GeneratedImage image = imageOpt.get();

            // Image 삭제
            String imageKey = IMAGE_BY_ID_KEY_PREFIX + imageId;
            redisTemplate.delete(imageKey);

            // Set에서 Image ID 제거
            String setKey = IMAGE_IDS_SET_KEY_PREFIX + image.getEventId();
            redisTemplate.opsForSet().remove(setKey, imageId);

            log.info("이미지 삭제 완료: imageId={}, eventId={}", imageId, image.getEventId());
        } catch (Exception e) {
            log.error("이미지 삭제 실패: imageId={}", imageId, e);
            throw new RuntimeException("이미지 삭제 실패", e);
        }
    }
}
