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

import com.kt.event.content.biz.domain.GeneratedImage;
import com.kt.event.content.biz.domain.Job;
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.RegenerateImageUseCase;
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.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.List;
import java.util.UUID;

/**
 * 이미지 재생성 서비스
 *
 * Stable Diffusion으로 기존 이미지를 새 프롬프트로 재생성
 */
@Slf4j
@Service
public class RegenerateImageService implements RegenerateImageUseCase {

    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 RegenerateImageService(
            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.RegenerateImage command) {
        log.info("이미지 재생성 요청: imageId={}, newPrompt={}",
                command.getImageId(), command.getNewPrompt());

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

        Job job = Job.builder()
                .id(jobId)
                .eventId("regenerate-" + command.getImageId())
                .jobType("image-regeneration")
                .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);

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

        return JobInfo.from(job);
    }

    @Async
    private void processImageRegeneration(String jobId, ContentCommand.RegenerateImage command) {
        try {
            log.info("이미지 재생성 시작: jobId={}, imageId={}", jobId, command.getImageId());

            // 기존 이미지 조회
            GeneratedImage existingImage = contentWriter.getImageById(command.getImageId());
            if (existingImage == null) {
                throw new RuntimeException("이미지를 찾을 수 없습니다: imageId=" + command.getImageId());
            }

            jobWriter.updateJobStatus(jobId, "IN_PROGRESS", 30);

            // 새 프롬프트로 이미지 생성
            String newPrompt = command.getNewPrompt() != null && !command.getNewPrompt().trim().isEmpty()
                    ? command.getNewPrompt()
                    : existingImage.getPrompt();

            String imageUrl = generateImage(newPrompt, existingImage.getPlatform());

            jobWriter.updateJobStatus(jobId, "IN_PROGRESS", 80);

            // 기존 이미지를 기반으로 새 이미지 생성
            GeneratedImage updatedImage = GeneratedImage.builder()
                    .id(existingImage.getId())
                    .eventId(existingImage.getEventId())
                    .style(existingImage.getStyle())
                    .platform(existingImage.getPlatform())
                    .cdnUrl(imageUrl)  // 새 URL
                    .prompt(newPrompt)  // 새 프롬프트
                    .selected(existingImage.isSelected())
                    .createdAt(existingImage.getCreatedAt())
                    .updatedAt(java.time.LocalDateTime.now())
                    .build();

            contentWriter.saveImage(updatedImage);

            log.info("이미지 재생성 완료: imageId={}, url={}", command.getImageId(), imageUrl);

            // Job 완료
            jobWriter.updateJobStatus(jobId, "COMPLETED", 100);
            jobWriter.updateJobResult(jobId, "이미지가 성공적으로 재생성되었습니다.");

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

    /**
     * Stable Diffusion으로 이미지 생성
     */
    private String generateImage(String prompt, com.kt.event.content.biz.domain.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;
            }

            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")
                            .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();

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

            // 이미지 다운로드
            byte[] imageData = downloadImage(replicateUrl);

            // Azure Blob Storage에 업로드
            String fileName = String.format("regenerate-%s-%s.png",
                    predictionId.substring(0, 8),
                    System.currentTimeMillis());
            String azureCdnUrl = cdnUploader.upload(imageData, fileName);

            return azureCdnUrl;

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

    /**
     * Replicate API 예측 완료 대기
     */
    private String waitForCompletion(String predictionId) throws InterruptedException {
        int maxRetries = 60;
        int retryCount = 0;

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

            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);
            }

            Thread.sleep(5000);
            retryCount++;
        }

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

    /**
     * 이미지 다운로드
     */
    private byte[] downloadImage(String imageUrl) throws Exception {
        URL url = new URL(imageUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.setConnectTimeout(30000);
        connection.setReadTimeout(30000);

        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 예측 생성
     */
    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);
        }
    }

    /**
     * Circuit Breaker로 보호된 Replicate 예측 조회
     */
    private ReplicateResponse getPredictionWithCircuitBreaker(String predictionId) {
        try {
            return circuitBreaker.executeSupplier(() -> replicateClient.getPrediction(predictionId));
        } catch (CallNotPermittedException e) {
            log.error("Replicate Circuit Breaker가 OPEN 상태입니다");
            throw new RuntimeException("Replicate API에 일시적으로 접근할 수 없습니다", e);
        }
    }

    /**
     * Mock 이미지 URL 생성 (dev 환경용)
     *
     * @param platform 플랫폼 (이미지 크기 결정)
     * @return Mock 이미지 URL
     */
    private String generateMockImageUrl(com.kt.event.content.biz.domain.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/6BCF7F/FFFFFF?text=Regenerated+%s+%s",
                width, height, platform.name(), mockId);
    }
}
