package com.kt.event.content.biz.service;

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.ContentCommand;
import com.kt.event.content.biz.dto.JobInfo;
import com.kt.event.content.biz.dto.RedisJobData;
import com.kt.event.content.biz.usecase.in.GenerateImagesUseCase;
import com.kt.event.content.biz.usecase.out.CDNUploader;
import com.kt.event.content.biz.usecase.out.ContentWriter;
import com.kt.event.content.biz.usecase.out.JobWriter;
import com.kt.event.content.infra.gateway.client.ReplicateApiClient;
import com.kt.event.content.infra.gateway.client.dto.ReplicateRequest;
import com.kt.event.content.infra.gateway.client.dto.ReplicateResponse;
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * Stable Diffusion 이미지 생성 서비스
 *
 * Replicate API를 사용하여 Stable Diffusion XL 1.0으로 이미지 생성
 */
@Slf4j
@Service
@Primary
public class StableDiffusionImageGenerator implements GenerateImagesUseCase {

    private final ReplicateApiClient replicateClient;
    private final CDNUploader cdnUploader;
    private final JobWriter jobWriter;
    private final ContentWriter contentWriter;
    private final CircuitBreaker circuitBreaker;

    @Value("${replicate.model.version:stability-ai/sdxl:39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b}")
    private String modelVersion;

    @Value("${replicate.mock.enabled:false}")
    private boolean mockEnabled;

    public StableDiffusionImageGenerator(
            ReplicateApiClient replicateClient,
            CDNUploader cdnUploader,
            JobWriter jobWriter,
            ContentWriter contentWriter,
            @Qualifier("replicateCircuitBreaker") CircuitBreaker circuitBreaker) {
        this.replicateClient = replicateClient;
        this.cdnUploader = cdnUploader;
        this.jobWriter = jobWriter;
        this.contentWriter = contentWriter;
        this.circuitBreaker = circuitBreaker;
    }

    @Override
    public JobInfo execute(ContentCommand.GenerateImages command) {
        log.info("Stable Diffusion 이미지 생성 요청: eventId={}, styles={}, platforms={}",
                command.getEventId(), command.getStyles(), command.getPlatforms());

        // Job 생성
        String jobId = "job-" + UUID.randomUUID().toString().substring(0, 8);

        Job job = Job.builder()
                .id(jobId)
                .eventId(command.getEventId())
                .jobType("image-generation")
                .status(Job.Status.PENDING)
                .progress(0)
                .createdAt(java.time.LocalDateTime.now())
                .updatedAt(java.time.LocalDateTime.now())
                .build();

        // Job 저장
        RedisJobData jobData = RedisJobData.builder()
                .id(job.getId())
                .eventId(job.getEventId())
                .jobType(job.getJobType())
                .status(job.getStatus().name())
                .progress(job.getProgress())
                .createdAt(job.getCreatedAt())
                .updatedAt(job.getUpdatedAt())
                .build();

        jobWriter.saveJob(jobData, 3600); // TTL 1시간
        log.info("Job 생성 완료: jobId={}", jobId);

        // 비동기로 이미지 생성
        processImageGeneration(jobId, command);

        return JobInfo.from(job);
    }

    @Async
    private void processImageGeneration(String jobId, ContentCommand.GenerateImages command) {
        try {
            log.info("Stable Diffusion 이미지 생성 시작: jobId={}", jobId);

            // Content 생성 또는 조회
            Content content = Content.builder()
                    .eventId(command.getEventId())
                    .eventTitle(command.getEventId() + " 이벤트")
                    .eventDescription("AI 생성 이벤트 이미지")
                    .createdAt(java.time.LocalDateTime.now())
                    .updatedAt(java.time.LocalDateTime.now())
                    .build();
            Content savedContent = contentWriter.save(content);
            log.info("Content 생성 완료: contentId={}", savedContent.getId());

            // 스타일 x 플랫폼 조합으로 이미지 생성
            List<ImageStyle> styles = command.getStyles() != null && !command.getStyles().isEmpty()
                    ? command.getStyles()
                    : List.of(ImageStyle.FANCY, ImageStyle.SIMPLE);

            List<Platform> platforms = command.getPlatforms() != null && !command.getPlatforms().isEmpty()
                    ? command.getPlatforms()
                    : List.of(Platform.INSTAGRAM, Platform.KAKAO);

            List<GeneratedImage> images = new ArrayList<>();
            int totalCount = styles.size() * platforms.size();
            int currentCount = 0;

            for (ImageStyle style : styles) {
                for (Platform platform : platforms) {
                    currentCount++;

                    // 진행률 업데이트
                    int progress = (currentCount * 100) / totalCount;
                    jobWriter.updateJobStatus(jobId, "IN_PROGRESS", progress);

                    // Stable Diffusion으로 이미지 생성
                    String prompt = buildPrompt(command, style, platform);
                    String imageUrl = generateImage(prompt, platform);

                    // GeneratedImage 저장
                    GeneratedImage image = GeneratedImage.builder()
                            .eventId(command.getEventId())
                            .style(style)
                            .platform(platform)
                            .cdnUrl(imageUrl)
                            .prompt(prompt)
                            .selected(currentCount == 1) // 첫 번째 이미지를 선택
                            .createdAt(java.time.LocalDateTime.now())
                            .updatedAt(java.time.LocalDateTime.now())
                            .build();

                    if (currentCount == 1) {
                        image.select();
                    }

                    GeneratedImage savedImage = contentWriter.saveImage(image);
                    images.add(savedImage);
                    log.info("이미지 생성 완료: imageId={}, style={}, platform={}, url={}",
                            savedImage.getId(), style, platform, imageUrl);
                }
            }

            // Job 완료
            String resultMessage = String.format("%d개의 이미지가 성공적으로 생성되었습니다.", images.size());
            jobWriter.updateJobStatus(jobId, "COMPLETED", 100);
            jobWriter.updateJobResult(jobId, resultMessage);
            log.info("Stable Diffusion Job 완료: jobId={}, 생성된 이미지 수={}", jobId, images.size());

        } catch (Exception e) {
            log.error("Stable Diffusion 이미지 생성 실패: jobId={}", jobId, e);
            jobWriter.updateJobError(jobId, e.getMessage());
        }
    }

    /**
     * Stable Diffusion으로 이미지 생성
     *
     * @param prompt 이미지 생성 프롬프트
     * @param platform 플랫폼 (이미지 크기 결정)
     * @return 생성된 이미지 URL
     */
    private String generateImage(String prompt, Platform platform) {
        try {
            // Mock 모드일 경우 Mock 데이터 반환
//            if (mockEnabled) {
//                log.info("[MOCK] 이미지 생성 요청 (실제 API 호출 없음): prompt={}, platform={}", prompt, platform);
//                String mockUrl = generateMockImageUrl(platform);
//                log.info("[MOCK] 이미지 생성 완료: url={}", mockUrl);
//                return mockUrl;
//            }

            // 플랫폼별 이미지 크기 설정 (Platform enum에서 가져옴)
            int width = platform.getWidth();
            int height = platform.getHeight();

            // Replicate API 요청
            ReplicateRequest request = ReplicateRequest.builder()
                    .version(modelVersion)
                    .input(ReplicateRequest.Input.builder()
                            .prompt(prompt)
                            .negativePrompt("blurry, bad quality, distorted, ugly, low resolution, text, letters, words, typography, writing, numbers, characters, labels, watermark, logo, signage")
                            .width(width)
                            .height(height)
                            .numOutputs(1)
                            .guidanceScale(7.5)
                            .numInferenceSteps(50)
                            .seed(System.currentTimeMillis()) // 랜덤 시드 생성
                            .build())
                    .build();

            log.info("Replicate API 호출 시작: prompt={}, size={}x{}", prompt, width, height);
            ReplicateResponse response = createPredictionWithCircuitBreaker(request);
            String predictionId = response.getId();
            log.info("Replicate 예측 생성: predictionId={}, status={}", predictionId, response.getStatus());

            // 이미지 생성 완료까지 대기 (폴링)
            String replicateUrl = waitForCompletion(predictionId);
            log.info("Replicate 이미지 생성 완료: predictionId={}, url={}", predictionId, replicateUrl);

            // Replicate URL에서 이미지 다운로드
            byte[] imageData = downloadImage(replicateUrl);
            log.info("이미지 다운로드 완료: size={} bytes", imageData.length);

            // Azure Blob Storage에 업로드
            String fileName = String.format("event-%s-%s-%s.png",
                    platform.name().toLowerCase(),
                    predictionId.substring(0, 8),
                    System.currentTimeMillis());
            String azureCdnUrl = cdnUploader.upload(imageData, fileName);
            log.info("Azure CDN 업로드 완료: fileName={}, url={}", fileName, azureCdnUrl);

            return azureCdnUrl;

        } catch (Exception e) {
            log.error("Stable Diffusion 이미지 생성 실패: prompt={}", prompt, e);
            throw new RuntimeException("이미지 생성 실패: " + e.getMessage(), e);
        }
    }

    /**
     * Mock 이미지 URL 생성 (dev 환경용)
     *
     * @param platform 플랫폼 (이미지 크기 결정)
     * @return Mock 이미지 URL
     */
    private String generateMockImageUrl(Platform platform) {
        // 플랫폼별 크기에 맞는 placeholder 이미지 URL 생성
        int width = platform.getWidth();
        int height = platform.getHeight();

        // placeholder.com을 사용한 Mock 이미지 URL
        String mockId = UUID.randomUUID().toString().substring(0, 8);
        return String.format("https://via.placeholder.com/%dx%d/FF6B6B/FFFFFF?text=%s+Event+%s",
                width, height, platform.name(), mockId);
    }

    /**
     * Replicate API 예측 완료 대기 (폴링)
     *
     * @param predictionId 예측 ID
     * @return 생성된 이미지 URL
     */
    private String waitForCompletion(String predictionId) throws InterruptedException {
        int maxRetries = 60; // 최대 5분 (5초 x 60회)
        int retryCount = 0;

        while (retryCount < maxRetries) {
            ReplicateResponse response = getPredictionWithCircuitBreaker(predictionId);
            String status = response.getStatus();

            log.debug("Replicate 상태 조회: predictionId={}, status={}, retry={}/{}",
                    predictionId, status, retryCount, maxRetries);

            if ("succeeded".equals(status)) {
                List<String> output = response.getOutput();
                if (output != null && !output.isEmpty()) {
                    return output.get(0);
                }
                throw new RuntimeException("이미지 URL이 없습니다");
            } else if ("failed".equals(status) || "canceled".equals(status)) {
                String error = response.getError() != null ? response.getError() : "알 수 없는 오류";
                throw new RuntimeException("이미지 생성 실패: " + error);
            }

            // 5초 대기 후 재시도
            Thread.sleep(5000);
            retryCount++;
        }

        throw new RuntimeException("이미지 생성 타임아웃 (5분 초과)");
    }

    /**
     * 이미지 생성 프롬프트 구성
     */
    private String buildPrompt(ContentCommand.GenerateImages command, ImageStyle style, Platform platform) {
        StringBuilder prompt = new StringBuilder();

        // 음식 사진 전문성 강조
        prompt.append("professional food photography, appetizing food shot, ");

        // 업종 정보 추가
        if (command.getIndustry() != null && !command.getIndustry().trim().isEmpty()) {
            prompt.append(command.getIndustry()).append(" cuisine, ");
        }

        // 지역 정보 추가
        if (command.getLocation() != null && !command.getLocation().trim().isEmpty()) {
            prompt.append(command.getLocation()).append(" style, ");
        }

        // 트렌드 키워드 추가 (최대 3개)
        if (command.getTrends() != null && !command.getTrends().isEmpty()) {
            prompt.append("featuring ");
            int count = Math.min(3, command.getTrends().size());
            for (int i = 0; i < count; i++) {
                if (i > 0) prompt.append(", ");
                prompt.append(command.getTrends().get(i));
            }
            prompt.append(", ");
        }

        // 스타일별 프롬프트
        switch (style) {
            case FANCY:
                prompt.append("elegant plating, luxurious presentation, premium dish, vibrant colors, ");
                break;
            case SIMPLE:
                prompt.append("minimalist plating, clean presentation, simple arrangement, modern style, ");
                break;
            case TRENDY:
                prompt.append("trendy plating, contemporary style, stylish presentation, modern gastronomy, ");
                break;
        }

        // 플랫폼별 특성 추가
        prompt.append("optimized for ").append(platform.name().toLowerCase()).append(" platform, ");

        // 고품질 음식 사진 + 텍스트 제외 명시
        prompt.append("high quality, detailed, 4k resolution, professional lighting, no text overlay, text-free, clean image");

        return prompt.toString();
    }

    /**
     * URL에서 이미지 다운로드
     *
     * @param imageUrl 이미지 URL
     * @return 이미지 바이트 데이터
     */
    private byte[] downloadImage(String imageUrl) throws Exception {
        log.info("이미지 다운로드 시작: url={}", imageUrl);

        URL url = new URL(imageUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.setConnectTimeout(30000); // 30초
        connection.setReadTimeout(30000); // 30초

        int responseCode = connection.getResponseCode();
        if (responseCode != HttpURLConnection.HTTP_OK) {
            throw new RuntimeException("이미지 다운로드 실패: HTTP " + responseCode);
        }

        // 이미지 데이터 읽기
        try (InputStream inputStream = connection.getInputStream();
             ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {

            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }

            return outputStream.toByteArray();
        }
    }

    /**
     * Circuit Breaker로 보호된 Replicate 예측 생성
     *
     * @param request Replicate 요청
     * @return Replicate 응답
     */
    private ReplicateResponse createPredictionWithCircuitBreaker(ReplicateRequest request) {
        try {
            return circuitBreaker.executeSupplier(() -> replicateClient.createPrediction(request));
        } catch (CallNotPermittedException e) {
            log.error("Replicate Circuit Breaker가 OPEN 상태입니다. 예측 생성 차단");
            throw new RuntimeException("Replicate API에 일시적으로 접근할 수 없습니다. 잠시 후 다시 시도해주세요.", e);
        } catch (Exception e) {
            log.error("Replicate 예측 생성 실패", e);
            throw new RuntimeException("이미지 생성 요청 실패: " + e.getMessage(), e);
        }
    }

    /**
     * Circuit Breaker로 보호된 Replicate 예측 조회
     *
     * @param predictionId 예측 ID
     * @return Replicate 응답
     */
    private ReplicateResponse getPredictionWithCircuitBreaker(String predictionId) {
        try {
            return circuitBreaker.executeSupplier(() -> replicateClient.getPrediction(predictionId));
        } catch (CallNotPermittedException e) {
            log.error("Replicate Circuit Breaker가 OPEN 상태입니다. 예측 조회 차단: predictionId={}", predictionId);
            throw new RuntimeException("Replicate API에 일시적으로 접근할 수 없습니다. 잠시 후 다시 시도해주세요.", e);
        } catch (Exception e) {
            log.error("Replicate 예측 조회 실패: predictionId={}", predictionId, e);
            throw new RuntimeException("이미지 생성 상태 확인 실패: " + e.getMessage(), e);
        }
    }
}
