@startuml
!theme mono

title Content Service - 클래스 다이어그램 (Clean Architecture)

' ============================================
' Domain Layer (엔티티 및 비즈니스 로직)
' ============================================
package "com.kt.event.content.biz.domain" <<Rectangle>> {

    class Content {
        - id: Long
        - eventId: String
        - eventTitle: String
        - eventDescription: String
        - images: List<GeneratedImage>
        - createdAt: LocalDateTime
        - updatedAt: LocalDateTime
        + addImage(image: GeneratedImage): void
        + getSelectedImages(): List<GeneratedImage>
        + getImagesByStyle(style: ImageStyle): List<GeneratedImage>
        + getImagesByPlatform(platform: Platform): List<GeneratedImage>
    }

    class GeneratedImage {
        - id: Long
        - eventId: String
        - style: ImageStyle
        - platform: Platform
        - cdnUrl: String
        - prompt: String
        - selected: boolean
        - createdAt: LocalDateTime
        - updatedAt: LocalDateTime
        + select(): void
        + deselect(): void
    }

    class Job {
        - id: String
        - eventId: String
        - jobType: String
        - status: Status
        - progress: int
        - resultMessage: String
        - errorMessage: String
        - createdAt: LocalDateTime
        - updatedAt: LocalDateTime
        + start(): void
        + updateProgress(progress: int): void
        + complete(resultMessage: String): void
        + fail(errorMessage: String): void
        + isProcessing(): boolean
        + isCompleted(): boolean
        + isFailed(): boolean
    }

    enum JobStatus {
        PENDING
        PROCESSING
        COMPLETED
        FAILED
    }

    enum ImageStyle {
        FANCY
        SIMPLE
        TRENDY
        + getDescription(): String
    }

    enum Platform {
        INSTAGRAM
        FACEBOOK
        KAKAO
        BLOG
        + getWidth(): int
        + getHeight(): int
        + getAspectRatio(): String
    }
}

' ============================================
' Application Layer (Use Cases)
' ============================================
package "com.kt.event.content.biz.usecase" <<Rectangle>> {

    package "in" {
        interface GenerateImagesUseCase {
            + execute(command: GenerateImagesCommand): JobInfo
        }

        interface GetJobStatusUseCase {
            + execute(jobId: String): JobInfo
        }

        interface GetEventContentUseCase {
            + execute(eventId: String): ContentInfo
        }

        interface GetImageListUseCase {
            + execute(eventId: String, style: ImageStyle, platform: Platform): List<ImageInfo>
        }

        interface GetImageDetailUseCase {
            + execute(imageId: Long): ImageInfo
        }

        interface RegenerateImageUseCase {
            + execute(command: RegenerateImageCommand): JobInfo
        }

        interface DeleteImageUseCase {
            + execute(imageId: Long): void
        }
    }

    package "out" {
        interface ContentReader {
            + findByEventDraftIdWithImages(eventId: String): Optional<Content>
            + findImageById(imageId: Long): Optional<GeneratedImage>
            + findImagesByEventDraftId(eventId: String): List<GeneratedImage>
        }

        interface ContentWriter {
            + save(content: Content): Content
            + saveImage(image: GeneratedImage): GeneratedImage
            + getImageById(imageId: Long): GeneratedImage
            + deleteImageById(imageId: Long): void
        }

        interface ImageReader {
            + getImage(eventId: String, style: ImageStyle, platform: Platform): Optional<RedisImageData>
            + getImagesByEventId(eventId: String): List<RedisImageData>
        }

        interface ImageWriter {
            + saveImage(image: GeneratedImage): GeneratedImage
            + getImageById(imageId: Long): GeneratedImage
            + deleteImageById(imageId: Long): void
        }

        interface JobReader {
            + getJob(jobId: String): Optional<RedisJobData>
        }

        interface JobWriter {
            + saveJob(jobData: RedisJobData, ttlSeconds: long): void
            + updateJobStatus(jobId: String, status: String, progress: Integer): void
            + updateJobResult(jobId: String, resultMessage: String): void
            + updateJobError(jobId: String, errorMessage: String): void
        }

        interface RedisAIDataReader {
            + getAIRecommendation(eventId: String): Optional<Map<String, Object>>
        }

        interface RedisImageWriter {
            + cacheImages(eventId: String, images: List<GeneratedImage>, ttlSeconds: long): void
        }

        interface CDNUploader {
            + upload(imageData: byte[], fileName: String): String
        }

        interface ImageGeneratorCaller {
            + generateImage(prompt: String, width: int, height: int): String
        }
    }
}

' ============================================
' Application Service Layer
' ============================================
package "com.kt.event.content.biz.service" <<Rectangle>> {

    class StableDiffusionImageGenerator implements GenerateImagesUseCase {
        - replicateClient: ReplicateApiClient
        - cdnUploader: CDNUploader
        - jobWriter: JobWriter
        - contentWriter: ContentWriter
        - circuitBreaker: CircuitBreaker
        - modelVersion: String
        + execute(command: GenerateImagesCommand): JobInfo
        - processImageGeneration(jobId: String, command: GenerateImagesCommand): void
        - generateImage(prompt: String, platform: Platform): String
        - waitForCompletion(predictionId: String): String
        - buildPrompt(command: GenerateImagesCommand, style: ImageStyle, platform: Platform): String
        - downloadImage(imageUrl: String): byte[]
        - createPredictionWithCircuitBreaker(request: ReplicateRequest): ReplicateResponse
        - getPredictionWithCircuitBreaker(predictionId: String): ReplicateResponse
    }

    class JobManagementService implements GetJobStatusUseCase {
        - jobReader: JobReader
        + execute(jobId: String): JobInfo
    }

    class GetEventContentService implements GetEventContentUseCase {
        - contentReader: ContentReader
        - redisAIDataReader: RedisAIDataReader
        + execute(eventId: String): ContentInfo
    }

    class GetImageListService implements GetImageListUseCase {
        - imageReader: ImageReader
        + execute(eventId: String, style: ImageStyle, platform: Platform): List<ImageInfo>
    }

    class GetImageDetailService implements GetImageDetailUseCase {
        - imageReader: ImageReader
        + execute(imageId: Long): ImageInfo
    }

    class RegenerateImageService implements RegenerateImageUseCase {
        - imageReader: ImageReader
        - imageWriter: ImageWriter
        - jobWriter: JobWriter
        - cdnUploader: CDNUploader
        - replicateClient: ReplicateApiClient
        + execute(command: RegenerateImageCommand): JobInfo
    }

    class DeleteImageService implements DeleteImageUseCase {
        - imageWriter: ImageWriter
        + execute(imageId: Long): void
    }
}

' ============================================
' DTO Layer
' ============================================
package "com.kt.event.content.biz.dto" <<Rectangle>> {

    class ContentCommand

    class GenerateImagesCommand {
        - eventId: String
        - eventTitle: String
        - eventDescription: String
        - industry: String
        - location: String
        - trends: List<String>
        - styles: List<ImageStyle>
        - platforms: List<Platform>
    }

    class RegenerateImageCommand {
        - imageId: Long
        - newPrompt: String
    }

    class ContentInfo {
        - id: Long
        - eventId: String
        - eventTitle: String
        - eventDescription: String
        - images: List<ImageInfo>
        - createdAt: LocalDateTime
        - updatedAt: LocalDateTime
        + {static} from(content: Content): ContentInfo
    }

    class ImageInfo {
        - id: Long
        - eventId: String
        - style: ImageStyle
        - platform: Platform
        - cdnUrl: String
        - prompt: String
        - selected: boolean
        - createdAt: LocalDateTime
        - updatedAt: LocalDateTime
        + {static} from(image: GeneratedImage): ImageInfo
    }

    class JobInfo {
        - id: String
        - eventId: String
        - jobType: String
        - status: String
        - progress: int
        - resultMessage: String
        - errorMessage: String
        - createdAt: LocalDateTime
        - updatedAt: LocalDateTime
        + {static} from(job: Job): JobInfo
    }

    class RedisJobData {
        - id: String
        - eventId: String
        - jobType: String
        - status: String
        - progress: int
        - resultMessage: String
        - errorMessage: String
        - createdAt: LocalDateTime
        - updatedAt: LocalDateTime
    }

    class RedisImageData {
        - eventId: String
        - style: ImageStyle
        - platform: Platform
        - imageUrl: String
        - prompt: String
        - createdAt: LocalDateTime
    }

    class RedisAIEventData {
        - eventId: String
        - recommendedStyles: List<String>
        - recommendedKeywords: List<String>
        - cachedAt: LocalDateTime
    }
}

' ============================================
' Infrastructure Layer (Gateway & Adapter)
' ============================================
package "com.kt.event.content.infra.gateway" <<Rectangle>> {

    class RedisGateway implements ContentReader, ContentWriter, ImageReader, ImageWriter, JobReader, JobWriter, RedisAIDataReader, RedisImageWriter {
        - redisTemplate: RedisTemplate<String, Object>
        - objectMapper: ObjectMapper
        - nextContentId: Long
        - nextImageId: Long
        + getAIRecommendation(eventId: String): Optional<Map<String, Object>>
        + cacheImages(eventId: String, images: List<GeneratedImage>, ttlSeconds: long): void
        + saveImage(imageData: RedisImageData, ttlSeconds: long): void
        + getImage(eventId: String, style: ImageStyle, platform: Platform): Optional<RedisImageData>
        + getImagesByEventId(eventId: String): List<RedisImageData>
        + deleteImage(eventId: String, style: ImageStyle, platform: Platform): void
        + saveImages(eventId: String, images: List<RedisImageData>, ttlSeconds: long): void
        + saveJob(jobData: RedisJobData, ttlSeconds: long): void
        + getJob(jobId: String): Optional<RedisJobData>
        + updateJobStatus(jobId: String, status: String, progress: Integer): void
        + updateJobResult(jobId: String, resultMessage: String): void
        + updateJobError(jobId: String, errorMessage: String): void
        + findByEventDraftIdWithImages(eventId: String): Optional<Content>
        + findImageById(imageId: Long): Optional<GeneratedImage>
        + findImagesByEventDraftId(eventId: String): List<GeneratedImage>
        + save(content: Content): Content
        + saveImage(image: GeneratedImage): GeneratedImage
        + getImageById(imageId: Long): GeneratedImage
        + deleteImageById(imageId: Long): void
        - buildImageKey(eventId: String, style: ImageStyle, platform: Platform): String
        - getString(map: Map<Object, Object>, key: String): String
        - getLong(map: Map<Object, Object>, key: String): Long
        - getInteger(map: Map<Object, Object>, key: String): Integer
        - getLocalDateTime(map: Map<Object, Object>, key: String): LocalDateTime
    }

    package "client" {
        class ReplicateApiClient {
            - apiToken: String
            - baseUrl: String
            - restClient: RestClient
            + createPrediction(request: ReplicateRequest): ReplicateResponse
            + getPrediction(predictionId: String): ReplicateResponse
        }

        class AzureBlobStorageUploader implements CDNUploader {
            - connectionString: String
            - containerName: String
            - circuitBreaker: CircuitBreaker
            - blobServiceClient: BlobServiceClient
            - containerClient: BlobContainerClient
            + init(): void
            + upload(imageData: byte[], fileName: String): String
            - doUpload(imageData: byte[], fileName: String): String
            - generateBlobName(fileName: String): String
        }

        class ReplicateRequest {
            - version: String
            - input: Input
        }

        class ReplicateInputRequest {
            - prompt: String
            - negativePrompt: String
            - width: int
            - height: int
            - numOutputs: int
            - guidanceScale: double
            - numInferenceSteps: int
            - seed: long
        }

        class ReplicateResponse {
            - id: String
            - status: String
            - output: List<String>
            - error: String
        }

        class ReplicateApiConfig {
            - apiToken: String
            - baseUrl: String
            + restClient(): RestClient
        }
    }
}

' ============================================
' Presentation Layer (REST Controller)
' ============================================
package "com.kt.event.content.infra.web.controller" <<Rectangle>> {

    class ContentController {
        - generateImagesUseCase: GenerateImagesUseCase
        - getJobStatusUseCase: GetJobStatusUseCase
        - getEventContentUseCase: GetEventContentUseCase
        - getImageListUseCase: GetImageListUseCase
        - getImageDetailUseCase: GetImageDetailUseCase
        - regenerateImageUseCase: RegenerateImageUseCase
        - deleteImageUseCase: DeleteImageUseCase
        + generateImages(command: GenerateImagesCommand): ResponseEntity<JobInfo>
        + getJobStatus(jobId: String): ResponseEntity<JobInfo>
        + getContentByEventId(eventId: String): ResponseEntity<ContentInfo>
        + getImages(eventId: String, style: String, platform: String): ResponseEntity<List<ImageInfo>>
        + getImageById(imageId: Long): ResponseEntity<ImageInfo>
        + deleteImage(imageId: Long): ResponseEntity<Void>
        + regenerateImage(imageId: Long, requestBody: RegenerateImageCommand): ResponseEntity<JobInfo>
    }
}

' ============================================
' Configuration Layer
' ============================================
package "com.kt.event.content.infra.config" <<Rectangle>> {

    class RedisConfig {
        - host: String
        - port: int
        + redisConnectionFactory(): RedisConnectionFactory
        + redisTemplate(): RedisTemplate<String, Object>
        + objectMapper(): ObjectMapper
    }

    class Resilience4jConfig {
        + replicateCircuitBreaker(): CircuitBreaker
        + azureCircuitBreaker(): CircuitBreaker
    }

    class SecurityConfig {
        + securityFilterChain(http: HttpSecurity): SecurityFilterChain
    }

    class SwaggerConfig {
        + openAPI(): OpenAPI
    }
}

' ============================================
' 관계 정의 (Domain)
' ============================================
Content "1" o-- "0..*" GeneratedImage : contains
GeneratedImage --> ImageStyle : uses
GeneratedImage --> Platform : uses
Job --> JobStatus : has

' ============================================
' 관계 정의 (Service → Port)
' ============================================
StableDiffusionImageGenerator ..> CDNUploader
StableDiffusionImageGenerator ..> JobWriter
StableDiffusionImageGenerator ..> ContentWriter
StableDiffusionImageGenerator --> ReplicateApiClient

JobManagementService ..> JobReader
GetEventContentService ..> ContentReader
GetEventContentService ..> RedisAIDataReader
GetImageListService ..> ImageReader
GetImageDetailService ..> ImageReader
RegenerateImageService ..> ImageReader
RegenerateImageService ..> ImageWriter
RegenerateImageService ..> JobWriter
RegenerateImageService ..> CDNUploader
DeleteImageService ..> ImageWriter

' ============================================
' 관계 정의 (Gateway → Port Implementation)
' ============================================
RedisGateway ..|> ContentReader
RedisGateway ..|> ContentWriter
RedisGateway ..|> ImageReader
RedisGateway ..|> ImageWriter
RedisGateway ..|> JobReader
RedisGateway ..|> JobWriter
RedisGateway ..|> RedisAIDataReader
RedisGateway ..|> RedisImageWriter

AzureBlobStorageUploader ..|> CDNUploader

' ============================================
' 관계 정의 (Controller → UseCase)
' ============================================
ContentController ..> GenerateImagesUseCase
ContentController ..> GetJobStatusUseCase
ContentController ..> GetEventContentUseCase
ContentController ..> GetImageListUseCase
ContentController ..> GetImageDetailUseCase
ContentController ..> RegenerateImageUseCase
ContentController ..> DeleteImageUseCase

' ============================================
' 관계 정의 (DTO)
' ============================================
ContentInfo ..> ImageInfo
ContentCommand ..> GenerateImagesCommand : uses
ContentCommand ..> RegenerateImageCommand : uses
ReplicateRequest ..> ReplicateInputRequest : contains

' ============================================
' 레이어 노트
' ============================================
note top of Content : Domain Layer\n순수 비즈니스 로직\n외부 의존성 없음
note top of GenerateImagesUseCase : Application Layer (Input Port)\nUse Case 인터페이스
note top of ContentReader : Application Layer (Output Port)\n외부 시스템 의존성 추상화
note top of StableDiffusionImageGenerator : Application Service Layer\nUse Case 구현체\n비즈니스 로직 오케스트레이션
note top of RedisGateway : Infrastructure Layer\nOutput Port 구현체\nRedis 연동
note top of ContentController : Presentation Layer\nREST API 엔드포인트
note top of RedisConfig : Configuration Layer\n인프라 설정

@enduml
