@startuml
!theme mono

title Event Service 클래스 다이어그램 (상세)

' ==============================
' Domain Layer (핵심 비즈니스 로직)
' ==============================
package "com.kt.event.eventservice.domain" {

    package "entity" {
        class Event extends BaseTimeEntity {
            - eventId: UUID
            - userId: UUID
            - storeId: UUID
            - eventName: String
            - description: String
            - objective: String
            - startDate: LocalDate
            - endDate: LocalDate
            - status: EventStatus
            - selectedImageId: UUID
            - selectedImageUrl: String
            - channels: List<String>
            - generatedImages: Set<GeneratedImage>
            - aiRecommendations: Set<AiRecommendation>

            ' 비즈니스 로직
            + updateEventName(eventName: String): void
            + updateDescription(description: String): void
            + updateEventPeriod(startDate: LocalDate, endDate: LocalDate): void
            + selectImage(imageId: UUID, imageUrl: String): void
            + updateChannels(channels: List<String>): void
            + publish(): void
            + end(): void
            + addGeneratedImage(image: GeneratedImage): void
            + addAiRecommendation(recommendation: AiRecommendation): void
            + isModifiable(): boolean
            + isDeletable(): boolean
        }

        class AiRecommendation extends BaseTimeEntity {
            - recommendationId: UUID
            - event: Event
            - eventName: String
            - description: String
            - promotionType: String
            - targetAudience: String
            - isSelected: boolean
        }

        class GeneratedImage extends BaseTimeEntity {
            - imageId: UUID
            - event: Event
            - imageUrl: String
            - style: String
            - platform: String
            - isSelected: boolean
        }

        class Job extends BaseTimeEntity {
            - jobId: UUID
            - eventId: UUID
            - jobType: JobType
            - status: JobStatus
            - progress: int
            - resultKey: String
            - errorMessage: String
            - completedAt: LocalDateTime

            ' 비즈니스 로직
            + start(): void
            + updateProgress(progress: int): void
            + complete(resultKey: String): void
            + fail(errorMessage: String): void
        }
    }

    package "enums" {
        enum EventStatus {
            DRAFT
            PUBLISHED
            ENDED
        }

        enum JobStatus {
            PENDING
            PROCESSING
            COMPLETED
            FAILED
        }

        enum JobType {
            AI_RECOMMENDATION
            IMAGE_GENERATION
        }
    }

    package "repository" {
        interface EventRepository extends JpaRepository {
            + findByEventIdAndUserId(eventId: UUID, userId: UUID): Optional<Event>
            + findEventsByUser(userId: UUID, status: EventStatus, search: String, objective: String, pageable: Pageable): Page<Event>
        }

        interface AiRecommendationRepository extends JpaRepository {
            + findByEvent(event: Event): List<AiRecommendation>
        }

        interface GeneratedImageRepository extends JpaRepository {
            + findByEvent(event: Event): List<GeneratedImage>
        }

        interface JobRepository extends JpaRepository {
            + findByEventId(eventId: UUID): List<Job>
            + findByJobTypeAndStatus(jobType: JobType, status: JobStatus): List<Job>
        }
    }
}

' ==============================
' Application Layer (유스케이스)
' ==============================
package "com.kt.event.eventservice.application" {

    package "service" {
        class EventService {
            - eventRepository: EventRepository
            - jobRepository: JobRepository
            - contentServiceClient: ContentServiceClient
            - aiJobKafkaProducer: AIJobKafkaProducer

            ' 이벤트 생명주기 관리
            + createEvent(userId: UUID, storeId: UUID, request: SelectObjectiveRequest): EventCreatedResponse
            + getEvent(userId: UUID, eventId: UUID): EventDetailResponse
            + getEvents(userId: UUID, status: EventStatus, search: String, objective: String, pageable: Pageable): Page<EventDetailResponse>
            + deleteEvent(userId: UUID, eventId: UUID): void
            + publishEvent(userId: UUID, eventId: UUID): void
            + endEvent(userId: UUID, eventId: UUID): void
            + updateEvent(userId: UUID, eventId: UUID, request: UpdateEventRequest): EventDetailResponse

            ' AI 추천 관리
            + requestAiRecommendations(userId: UUID, eventId: UUID, request: AiRecommendationRequest): JobAcceptedResponse
            + selectRecommendation(userId: UUID, eventId: UUID, request: SelectRecommendationRequest): void

            ' 이미지 관리
            + requestImageGeneration(userId: UUID, eventId: UUID, request: ImageGenerationRequest): ImageGenerationResponse
            + selectImage(userId: UUID, eventId: UUID, imageId: UUID, request: SelectImageRequest): void
            + editImage(userId: UUID, eventId: UUID, imageId: UUID, request: ImageEditRequest): ImageEditResponse

            ' 배포 채널 관리
            + selectChannels(userId: UUID, eventId: UUID, request: SelectChannelsRequest): void

            ' Helper Methods
            - mapToDetailResponse(event: Event): EventDetailResponse
        }

        class JobService {
            - jobRepository: JobRepository

            + createJob(eventId: UUID, jobType: JobType): Job
            + getJobStatus(jobId: UUID): JobStatusResponse
            + updateJobProgress(jobId: UUID, progress: int): void
            + completeJob(jobId: UUID, resultKey: String): void
            + failJob(jobId: UUID, errorMessage: String): void

            - mapToJobStatusResponse(job: Job): JobStatusResponse
        }
    }

    package "dto.request" {
        class SelectObjectiveRequest {
            - objective: String
        }

        class AiRecommendationRequest {
            - storeInfo: StoreInfo

            + StoreInfo {
                - storeName: String
                - category: String
                - description: String
            }
        }

        class SelectRecommendationRequest {
            - recommendationId: UUID
            - customizations: Customizations

            + Customizations {
                - eventName: String
                - description: String
                - startDate: LocalDate
                - endDate: LocalDate
            }
        }

        class ImageGenerationRequest {
            - styles: List<String>
            - platforms: List<String>
        }

        class SelectImageRequest {
            - imageId: UUID
            - imageUrl: String
        }

        class ImageEditRequest {
            - editInstructions: String
        }

        class SelectChannelsRequest {
            - channels: List<String>
        }

        class UpdateEventRequest {
            - eventName: String
            - description: String
            - startDate: LocalDate
            - endDate: LocalDate
        }
    }

    package "dto.response" {
        class EventCreatedResponse {
            - eventId: UUID
            - status: EventStatus
            - objective: String
            - createdAt: LocalDateTime
        }

        class EventDetailResponse {
            - eventId: UUID
            - userId: UUID
            - storeId: UUID
            - eventName: String
            - description: String
            - objective: String
            - startDate: LocalDate
            - endDate: LocalDate
            - status: EventStatus
            - selectedImageId: UUID
            - selectedImageUrl: String
            - generatedImages: List<GeneratedImageDto>
            - aiRecommendations: List<AiRecommendationDto>
            - channels: List<String>
            - createdAt: LocalDateTime
            - updatedAt: LocalDateTime

            + GeneratedImageDto {
                - imageId: UUID
                - imageUrl: String
                - style: String
                - platform: String
                - isSelected: boolean
                - createdAt: LocalDateTime
            }

            + AiRecommendationDto {
                - recommendationId: UUID
                - eventName: String
                - description: String
                - promotionType: String
                - targetAudience: String
                - isSelected: boolean
            }
        }

        class JobAcceptedResponse {
            - jobId: UUID
            - status: JobStatus
            - message: String
        }

        class JobStatusResponse {
            - jobId: UUID
            - jobType: JobType
            - status: JobStatus
            - progress: int
            - resultKey: String
            - errorMessage: String
            - createdAt: LocalDateTime
            - completedAt: LocalDateTime
        }

        class ImageGenerationResponse {
            - jobId: UUID
            - status: String
            - message: String
            - createdAt: LocalDateTime
        }

        class ImageEditResponse {
            - imageId: UUID
            - imageUrl: String
            - editedAt: LocalDateTime
        }
    }

    package "dto.kafka" {
        class AIEventGenerationJobMessage {
            - jobId: String
            - userId: Long
            - status: String
            - createdAt: LocalDateTime
            - errorMessage: String
        }

        class EventCreatedMessage {
            - eventId: String
            - userId: Long
            - storeId: Long
            - objective: String
            - status: String
            - createdAt: LocalDateTime
        }

        class ImageGenerationJobMessage {
            - jobId: String
            - eventId: String
            - styles: List<String>
            - platforms: List<String>
            - status: String
            - createdAt: LocalDateTime
        }
    }
}

' ==============================
' Infrastructure Layer (기술 구현)
' ==============================
package "com.kt.event.eventservice.infrastructure" {

    package "kafka" {
        class AIJobKafkaProducer {
            - kafkaTemplate: KafkaTemplate<String, Object>
            - aiEventGenerationJobTopic: String

            + publishAIGenerationJob(jobId: String, userId: Long, eventId: String, storeName: String, storeCategory: String, storeDescription: String, objective: String): void
            + publishMessage(message: AIEventGenerationJobMessage): void
        }

        class AIJobKafkaConsumer {
            - objectMapper: ObjectMapper

            + consumeAIEventGenerationJob(payload: String, partition: int, offset: long, acknowledgment: Acknowledgment): void
            - processAIEventGenerationJob(message: AIEventGenerationJobMessage): void
        }

        class ImageJobKafkaConsumer {
            - objectMapper: ObjectMapper

            + consumeImageGenerationJob(payload: String, partition: int, offset: long, acknowledgment: Acknowledgment): void
            - processImageGenerationJob(message: ImageGenerationJobMessage): void
        }

        class EventKafkaProducer {
            - kafkaTemplate: KafkaTemplate<String, Object>
            - eventCreatedTopic: String

            + publishEventCreated(event: Event): void
        }
    }

    package "client" {
        interface ContentServiceClient {
            + generateImages(request: ContentImageGenerationRequest): ContentJobResponse
        }
    }

    package "client.dto" {
        class ContentImageGenerationRequest {
            - eventDraftId: Long
            - eventTitle: String
            - eventDescription: String
            - styles: List<String>
            - platforms: List<String>
        }

        class ContentJobResponse {
            - id: String
            - status: String
            - createdAt: LocalDateTime
        }
    }

    package "config" {
        class RedisConfig {
            - host: String
            - port: int

            + redisConnectionFactory(): RedisConnectionFactory
            + redisTemplate(): RedisTemplate<String, Object>
        }
    }
}

' ==============================
' Presentation Layer (API 엔드포인트)
' ==============================
package "com.kt.event.eventservice.presentation" {

    package "controller" {
        class EventController {
            - eventService: EventService

            + selectObjective(request: SelectObjectiveRequest, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<EventCreatedResponse>>
            + getEvents(status: EventStatus, search: String, objective: String, page: int, size: int, sort: String, order: String, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<PageResponse<EventDetailResponse>>>
            + getEvent(eventId: UUID, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<EventDetailResponse>>
            + deleteEvent(eventId: UUID, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<Void>>
            + publishEvent(eventId: UUID, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<Void>>
            + endEvent(eventId: UUID, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<Void>>
            + requestImageGeneration(eventId: UUID, request: ImageGenerationRequest, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<ImageGenerationResponse>>
            + selectImage(eventId: UUID, imageId: UUID, request: SelectImageRequest, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<Void>>
            + requestAiRecommendations(eventId: UUID, request: AiRecommendationRequest, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<JobAcceptedResponse>>
            + selectRecommendation(eventId: UUID, request: SelectRecommendationRequest, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<Void>>
            + editImage(eventId: UUID, imageId: UUID, request: ImageEditRequest, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<ImageEditResponse>>
            + selectChannels(eventId: UUID, request: SelectChannelsRequest, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<Void>>
            + updateEvent(eventId: UUID, request: UpdateEventRequest, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<EventDetailResponse>>
        }

        class JobController {
            - jobService: JobService

            + getJobStatus(jobId: UUID): ResponseEntity<ApiResponse<JobStatusResponse>>
        }
    }
}

' ==============================
' Config Layer (설정)
' ==============================
package "com.kt.event.eventservice.config" {
    class SecurityConfig {
        + securityFilterChain(http: HttpSecurity): SecurityFilterChain
        + corsConfigurationSource(): CorsConfigurationSource
    }

    class KafkaConfig {
        + producerFactory(): ProducerFactory<String, Object>
        + kafkaTemplate(): KafkaTemplate<String, Object>
        + consumerFactory(): ConsumerFactory<String, String>
        + kafkaListenerContainerFactory(): ConcurrentKafkaListenerContainerFactory<String, String>
    }

    class DevAuthenticationFilter extends OncePerRequestFilter {
        + doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain): void
    }
}

' ==============================
' Common Layer (공통 컴포넌트)
' ==============================
package "com.kt.event.common" <<external>> {
    abstract class BaseTimeEntity {
        - createdAt: LocalDateTime
        - updatedAt: LocalDateTime
    }

    class "ApiResponse<T>" {
        - success: boolean
        - data: T
        - errorCode: String
        - message: String
        - timestamp: LocalDateTime
    }

    class "PageResponse<T>" {
        - content: List<T>
        - totalElements: long
        - totalPages: int
        - number: int
        - size: int
        - first: boolean
        - last: boolean
    }

    class BusinessException extends RuntimeException {
        - errorCode: ErrorCode
        - details: String
    }

    interface ErrorCode {
        + getCode(): String
        + getMessage(): String
    }
}

' ==============================
' 관계 정의
' ==============================

' Domain Layer Relationships
Event "1" *-- "many" GeneratedImage : contains >
Event "1" *-- "many" AiRecommendation : contains >
Event ..> EventStatus : uses
Job ..> JobType : uses
Job ..> JobStatus : uses

EventRepository ..> Event : manages
AiRecommendationRepository ..> AiRecommendation : manages
GeneratedImageRepository ..> GeneratedImage : manages
JobRepository ..> Job : manages

' Application Layer Relationships
EventService --> EventRepository : uses
EventService --> JobRepository : uses
EventService --> ContentServiceClient : uses
EventService --> AIJobKafkaProducer : uses
JobService --> JobRepository : uses

EventService ..> SelectObjectiveRequest : uses
EventService ..> AiRecommendationRequest : uses
EventService ..> SelectRecommendationRequest : uses
EventService ..> ImageGenerationRequest : uses
EventService ..> SelectImageRequest : uses
EventService ..> SelectChannelsRequest : uses
EventService ..> UpdateEventRequest : uses

EventService ..> EventCreatedResponse : creates
EventService ..> EventDetailResponse : creates
EventService ..> JobAcceptedResponse : creates
EventService ..> ImageGenerationResponse : creates
JobService ..> JobStatusResponse : creates

' Infrastructure Layer Relationships
AIJobKafkaProducer ..> AIEventGenerationJobMessage : publishes
AIJobKafkaConsumer ..> AIEventGenerationJobMessage : consumes
ImageJobKafkaConsumer ..> ImageGenerationJobMessage : consumes
EventKafkaProducer ..> EventCreatedMessage : publishes

ContentServiceClient ..> ContentImageGenerationRequest : uses
ContentServiceClient ..> ContentJobResponse : returns

' Presentation Layer Relationships
EventController --> EventService : uses
JobController --> JobService : uses

EventController ..> ApiResponse : uses
EventController ..> PageResponse : uses

' Common Layer Inheritance
Event --|> BaseTimeEntity
AiRecommendation --|> BaseTimeEntity
GeneratedImage --|> BaseTimeEntity
Job --|> BaseTimeEntity

' Notes
note top of Event
  **핵심 도메인 엔티티**
  - 이벤트 생명주기 관리 (DRAFT → PUBLISHED → ENDED)
  - 상태 머신 패턴 적용
  - 비즈니스 규칙 캡슐화
  - 불변성 보장 (수정 메서드를 통한 변경)
end note

note top of EventService
  **핵심 유스케이스 오케스트레이터**
  - 이벤트 전체 생명주기 조율
  - AI 서비스 연동 (Kafka)
  - Content 서비스 연동 (Feign)
  - 트랜잭션 경계 관리
end note

note top of AIJobKafkaProducer
  **비동기 작업 발행자**
  - AI 추천 생성 작업 발행
  - 장시간 작업의 비동기 처리
  - Kafka 메시지 발행
end note

note bottom of EventController
  **REST API 엔드포인트**
  - 이벤트 생성부터 배포까지 전체 API
  - 인증/인가 처리
  - 입력 검증
  - 표준 응답 포맷 (ApiResponse)
end note

@enduml
