package com.unicorn.hgzero.meeting.infra.controller;

import com.unicorn.hgzero.common.dto.ApiResponse;
import com.unicorn.hgzero.common.exception.BusinessException;
import com.unicorn.hgzero.meeting.biz.domain.Meeting;
import com.unicorn.hgzero.meeting.biz.domain.Minutes;
import com.unicorn.hgzero.meeting.biz.domain.MinutesSection;
import com.unicorn.hgzero.meeting.biz.domain.Todo;
import com.unicorn.hgzero.meeting.biz.domain.AgendaSection;
import com.unicorn.hgzero.meeting.biz.dto.MinutesDTO;
import com.unicorn.hgzero.meeting.biz.service.MeetingService;
import com.unicorn.hgzero.meeting.biz.service.MinutesService;
import com.unicorn.hgzero.meeting.biz.service.MinutesSectionService;
import com.unicorn.hgzero.meeting.biz.service.TodoService;
import com.unicorn.hgzero.meeting.biz.service.AgendaSectionService;
import com.unicorn.hgzero.meeting.infra.dto.request.UpdateMinutesRequest;
import com.unicorn.hgzero.meeting.infra.dto.response.MinutesDetailResponse;
import com.unicorn.hgzero.meeting.infra.dto.response.MinutesListResponse;
import com.unicorn.hgzero.meeting.infra.cache.CacheService;
import com.unicorn.hgzero.meeting.infra.event.publisher.EventPublisher;
import com.unicorn.hgzero.meeting.infra.gateway.AiServiceGateway;
import com.unicorn.hgzero.meeting.biz.dto.AiAnalysisDTO;
import com.unicorn.hgzero.meeting.infra.event.dto.MinutesAnalysisRequestEvent;
import com.unicorn.hgzero.meeting.infra.event.dto.MinutesFinalizedEvent;
import com.unicorn.hgzero.meeting.infra.event.dto.MinutesSectionDTO;
import com.unicorn.hgzero.meeting.biz.usecase.out.ParticipantReader;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import jakarta.validation.Valid;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 회의록 관리 API Controller
 * 회의록 조회, 수정, 확정, 섹션 관리 기능
 */
@RestController
@RequestMapping("/api/meetings/minutes")
@RequiredArgsConstructor
@Slf4j
@Tag(name = "Minutes", description = "회의록 관리 API")
public class MinutesController {

    private final MinutesService minutesService;
    private final MinutesSectionService minutesSectionService;
    private final CacheService cacheService;
    private final EventPublisher eventPublisher;
    private final MeetingService meetingService;
    private final TodoService todoService;
    private final AiServiceGateway aiServiceGateway;
    private final AgendaSectionService agendaSectionService;
    private final ParticipantReader participantReader;
    private final com.unicorn.hgzero.meeting.biz.usecase.out.TodoReader todoReader;

    /**
     * 회의록 목록 조회
     * GET /api/minutes
     */
    @GetMapping
    @Operation(summary = "회의록 목록 조회", description = "사용자의 회의록 목록을 조회합니다")
    @ApiResponses(value = {
            @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공"),
            @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청"),
            @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증 실패")
    })
    public ResponseEntity<ApiResponse<MinutesListResponse>> getMinutesList(
            @RequestHeader("X-User-Id") String userId,
            @RequestHeader("X-User-Name") String userName,
            @Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page,
            @Parameter(description = "페이지 크기") @RequestParam(defaultValue = "10") int size,
            @Parameter(description = "정렬 기준 (modified, meeting, title)") @RequestParam(defaultValue = "modified") String sortBy,
            @Parameter(description = "정렬 방향 (asc, desc)") @RequestParam(defaultValue = "desc") String sortDir,
            @Parameter(description = "상태 필터 (all, draft, complete)") @RequestParam(defaultValue = "all") String status,
            @Parameter(description = "참여 유형 (attended, created)") @RequestParam(required = false) String participationType,
            @Parameter(description = "검색 키워드") @RequestParam(required = false) String search) {

        log.info("회의록 목록 조회 요청 - userId: {}, page: {}, size: {}, status: {}, participationType: {}, search: {}", 
                userId, page, size, status, participationType, search);

        try {
            // 정렬 및 페이징 설정
            Sort sort = createSort(sortBy, sortDir);
            Pageable pageable = PageRequest.of(page, size, sort);

            // 실제 데이터 조회
            Page<MinutesDTO> minutesPage = minutesService.getMinutesListByUserId(userId, pageable);
            
            // DTO를 Response 형식으로 변환
            List<MinutesListResponse.MinutesItem> minutesList = minutesPage.getContent().stream()
                    .map(this::convertToMinutesItem)
                    .collect(Collectors.toList());
            
            // 필터링 적용 (상태별)
            List<MinutesListResponse.MinutesItem> filteredMinutes = minutesList.stream()
                    .filter(item -> filterByStatus(item, status))
                    .filter(item -> filterBySearch(item, search))
                    .collect(Collectors.toList());

            // 통계 계산 (전체 데이터 기준)
            MinutesListResponse.Statistics stats = calculateRealStatistics(userId, participationType);

            MinutesListResponse response = MinutesListResponse.builder()
                    .minutesList(filteredMinutes)
                    .totalCount((int) minutesPage.getTotalElements())
                    .currentPage(page)
                    .totalPages(minutesPage.getTotalPages())
                    .statistics(stats)
                    .build();

            log.info("회의록 목록 조회 성공 - userId: {}, total: {}, filtered: {}", 
                    userId, minutesPage.getTotalElements(), filteredMinutes.size());
            return ResponseEntity.ok(ApiResponse.success(response));

        } catch (Exception e) {
            log.error("회의록 목록 조회 실패 - userId: {}", userId, e);
            return ResponseEntity.badRequest()
                    .body(ApiResponse.errorWithType("회의록 목록 조회에 실패했습니다"));
        }
    }

    /**
     * 회의록 상세 조회
     * GET /api/minutes/{minutesId}
     */
    @GetMapping("/{minutesId}")
    @Operation(summary = "회의록 상세 조회", description = "회의록 상세 정보를 조회합니다")
    public ResponseEntity<ApiResponse<MinutesDetailResponse>> getMinutesDetail(
            @RequestHeader("X-User-Id") String userId,
            @RequestHeader("X-User-Name") String userName,
            @Parameter(description = "회의록 ID") @PathVariable String minutesId) {

        log.info("회의록 상세 조회 요청 - userId: {}, minutesId: {}", userId, minutesId);

        try {
            // 캐시에서 먼저 조회 시도
            MinutesDetailResponse cachedResponse = cacheService.getCachedMinutesDetail(minutesId);
            if (cachedResponse != null) {
                log.debug("회의록 상세 캐시 히트 - minutesId: {}", minutesId);
                return ResponseEntity.ok(ApiResponse.success(cachedResponse));
            }

            // 실제 데이터 조회
            MinutesDTO minutesDTO = minutesService.getMinutesById(minutesId);
            MinutesDetailResponse response = convertToMinutesDetailResponse(minutesDTO);

            // AI 분석 결과 포함 (비동기 처리)
            enhanceWithAiAnalysis(response, minutesDTO, userId, userName);

            // 캐시 저장
            cacheService.cacheMinutesDetail(minutesId, response);

            log.info("회의록 상세 조회 성공 - minutesId: {}", minutesId);
            return ResponseEntity.ok(ApiResponse.success(response));

        } catch (Exception e) {
            log.error("회의록 상세 조회 실패 - minutesId: {}", minutesId, e);
            return ResponseEntity.badRequest()
                    .body(ApiResponse.errorWithType("회의록 상세 조회에 실패했습니다"));
        }
    }

    /**
     * 회의록 수정
     * PATCH /api/minutes/{minutesId}
     */
    @PatchMapping("/{minutesId}")
    @Operation(summary = "회의록 수정", description = "회의록 제목과 메모를 수정합니다")
    public ResponseEntity<ApiResponse<MinutesDetailResponse>> updateMinutes(
            @RequestHeader("X-User-Id") String userId,
            @RequestHeader("X-User-Name") String userName,
            @Parameter(description = "회의록 ID") @PathVariable String minutesId,
            @Valid @RequestBody UpdateMinutesRequest request) {

        log.info("회의록 수정 요청 - userId: {}, minutesId: {}, title: {}", 
                userId, minutesId, request.getTitle());

        try {
            // 회의록 수정
            MinutesDTO updatedMinutes = minutesService.updateMinutes(minutesId, request.getTitle(), 
                    request.getMemo(), userId);
            
            // 응답 DTO 생성
            MinutesDetailResponse response = convertToMinutesDetailResponse(updatedMinutes);

            // 캐시 무효화
            cacheService.evictCacheMinutesDetail(minutesId);
            cacheService.evictCacheMinutesList(userId);

            log.info("회의록 수정 성공 - minutesId: {}, version: {}", 
                    minutesId, updatedMinutes.getVersion());
            return ResponseEntity.ok(ApiResponse.success(response));

        } catch (Exception e) {
            log.error("회의록 수정 실패 - minutesId: {}", minutesId, e);
            return ResponseEntity.badRequest()
                    .body(ApiResponse.errorWithType("회의록 수정에 실패했습니다"));
        }
    }

    /**
     * 회의록 확정
     * POST /api/minutes/{minutesId}/finalize
     */
    @PostMapping("/{minutesId}/finalize")
    @Operation(summary = "회의록 확정", description = "회의록을 확정 상태로 변경합니다")
    public ResponseEntity<ApiResponse<MinutesDetailResponse>> finalizeMinutes(
            @RequestHeader("X-User-Id") String userId,
            @RequestHeader("X-User-Name") String userName,
            @Parameter(description = "회의록 ID") @PathVariable String minutesId) {

        log.info("회의록 확정 요청 - userId: {}, minutesId: {}", userId, minutesId);

        try {
            // 회의록 확정
            MinutesDTO finalizedMinutes = minutesService.finalizeMinutesDTO(minutesId, userId);

            // 응답 DTO 생성
            MinutesDetailResponse response = convertToMinutesDetailResponse(finalizedMinutes);

            // 캐시 저장 (TTL: 10분)
            try {
                cacheService.cacheMinutesDetail(minutesId, response);
                log.debug("캐시에 확정된 회의록 저장 완료 - minutesId: {}", minutesId);
            } catch (Exception cacheEx) {
                log.warn("회의록 캐시 저장 실패 - minutesId: {}", minutesId, cacheEx);
                // 캐시 저장 실패는 무시하고 진행
            }

            // 캐시 무효화 (목록 캐시)
            cacheService.evictCacheMinutesList(userId);

            // 회의록 확정 이벤트 발행 (알림용 - 기존)
            eventPublisher.publishMinutesFinalized(minutesId, finalizedMinutes.getTitle(), userId, userName);

            // RAG 서비스용 회의록 확정 이벤트 발행 (완전한 데이터 포함)
            try {
                // 회의 정보 조회
                Meeting meeting = meetingService.getMeeting(finalizedMinutes.getMeetingId());

                // 섹션 정보 변환
                List<MinutesSectionDTO> sectionDTOs = finalizedMinutes.getSections().stream()
                        .map(section -> MinutesSectionDTO.builder()
                                .sectionId(section.getSectionId())
                                .type(section.getSectionType())
                                .title(section.getTitle())
                                .content(section.getContent())
                                .order(section.getSectionOrder())
                                .verified(section.getIsVerified())
                                .build())
                        .collect(Collectors.toList());

                // MinutesFinalizedEvent 생성
                MinutesFinalizedEvent ragEvent = MinutesFinalizedEvent.builder()
                        .timestamp(LocalDateTime.now())
                        .data(MinutesFinalizedEvent.MinutesData.builder()
                                // Meeting 정보
                                .meetingId(meeting.getMeetingId())
                                .title(meeting.getTitle())
                                .purpose(meeting.getPurpose())
                                .description(meeting.getDescription())
                                .scheduledAt(meeting.getScheduledAt())
                                .location(meeting.getLocation())
                                .organizerId(meeting.getOrganizerId())
                                // Minutes 정보
                                .minutesId(finalizedMinutes.getMinutesId())
                                .status(finalizedMinutes.getStatus())
                                .version(finalizedMinutes.getVersion())
                                .createdBy(finalizedMinutes.getCreatedBy())
                                .finalizedBy(userId)
                                .finalizedAt(finalizedMinutes.getFinalizedAt())
                                // 섹션 정보
                                .sections(sectionDTOs)
                                .build())
                        .build();

                // RAG용 이벤트 발행
                eventPublisher.publishMinutesFinalizedForRAG(ragEvent);
                log.info("RAG용 회의록 확정 이벤트 발행 완료 - minutesId: {}, meetingId: {}",
                        minutesId, meeting.getMeetingId());
            } catch (Exception e) {
                log.error("RAG용 회의록 확정 이벤트 발행 실패 - minutesId: {}, error: {}",
                        minutesId, e.getMessage(), e);
                // RAG 이벤트 발행 실패는 무시하고 진행 (회의록 확정 자체는 성공)
            }

            log.info("회의록 확정 성공 - minutesId: {}", minutesId);
            return ResponseEntity.ok(ApiResponse.success(response));

        } catch (BusinessException e) {
            log.error("회의록 확정 비즈니스 오류 - minutesId: {}, error: {}", minutesId, e.getMessage());
            return ResponseEntity.status(e.getErrorCode().getHttpStatus())
                    .body(ApiResponse.errorWithType(e.getMessage()));
        } catch (Exception e) {
            log.error("회의록 확정 실패 - minutesId: {}", minutesId, e);
            return ResponseEntity.badRequest()
                    .body(ApiResponse.errorWithType("회의록 확정에 실패했습니다"));
        }
    }

    /**
     * 섹션 검증 완료
     * POST /api/minutes/{minutesId}/sections/{sectionId}/verify
     */
    @PostMapping("/{minutesId}/sections/{sectionId}/verify")
    @Operation(summary = "섹션 검증 완료", description = "회의록 섹션 검증을 완료합니다")
    public ResponseEntity<ApiResponse<String>> verifySectionComplete(
            @RequestHeader("X-User-Id") String userId,
            @RequestHeader("X-User-Name") String userName,
            @Parameter(description = "회의록 ID") @PathVariable String minutesId,
            @Parameter(description = "섹션 ID") @PathVariable String sectionId) {

        log.info("섹션 검증 완료 요청 - userId: {}, minutesId: {}, sectionId: {}", 
                userId, minutesId, sectionId);

        try {
            // 섹션 검증 완료
            minutesSectionService.verifySectionComplete(sectionId, userId);

            // 캐시 무효화
            cacheService.evictCacheMinutesDetail(minutesId);

            log.info("섹션 검증 완료 성공 - sectionId: {}", sectionId);
            return ResponseEntity.ok(ApiResponse.success("섹션 검증이 완료되었습니다"));

        } catch (Exception e) {
            log.error("섹션 검증 완료 실패 - sectionId: {}", sectionId, e);
            return ResponseEntity.badRequest()
                    .body(ApiResponse.errorWithType("섹션 검증 완료에 실패했습니다"));
        }
    }

    /**
     * 섹션 잠금
     * POST /api/minutes/{minutesId}/sections/{sectionId}/lock
     */
    @PostMapping("/{minutesId}/sections/{sectionId}/lock")
    @Operation(summary = "섹션 잠금", description = "회의록 섹션을 잠금합니다")
    public ResponseEntity<ApiResponse<String>> lockSection(
            @RequestHeader("X-User-Id") String userId,
            @RequestHeader("X-User-Name") String userName,
            @Parameter(description = "회의록 ID") @PathVariable String minutesId,
            @Parameter(description = "섹션 ID") @PathVariable String sectionId) {

        log.info("섹션 잠금 요청 - userId: {}, minutesId: {}, sectionId: {}", 
                userId, minutesId, sectionId);

        try {
            // 섹션 잠금
            minutesSectionService.lockSection(sectionId, userId);

            // 캐시 무효화
            cacheService.evictCacheMinutesDetail(minutesId);

            log.info("섹션 잠금 성공 - sectionId: {}", sectionId);
            return ResponseEntity.ok(ApiResponse.success("섹션이 잠금되었습니다"));

        } catch (Exception e) {
            log.error("섹션 잠금 실패 - sectionId: {}", sectionId, e);
            return ResponseEntity.badRequest()
                    .body(ApiResponse.errorWithType("섹션 잠금에 실패했습니다"));
        }
    }

    /**
     * 섹션 잠금 해제
     * DELETE /api/minutes/{minutesId}/sections/{sectionId}/lock
     */
    @DeleteMapping("/{minutesId}/sections/{sectionId}/lock")
    @Operation(summary = "섹션 잠금 해제", description = "회의록 섹션 잠금을 해제합니다")
    public ResponseEntity<ApiResponse<String>> unlockSection(
            @RequestHeader("X-User-Id") String userId,
            @RequestHeader("X-User-Name") String userName,
            @Parameter(description = "회의록 ID") @PathVariable String minutesId,
            @Parameter(description = "섹션 ID") @PathVariable String sectionId) {

        log.info("섹션 잠금 해제 요청 - userId: {}, minutesId: {}, sectionId: {}", 
                userId, minutesId, sectionId);

        try {
            // 섹션 잠금 해제
            minutesSectionService.unlockSection(sectionId, userId);

            // 캐시 무효화
            cacheService.evictCacheMinutesDetail(minutesId);

            log.info("섹션 잠금 해제 성공 - sectionId: {}", sectionId);
            return ResponseEntity.ok(ApiResponse.success("섹션 잠금이 해제되었습니다"));

        } catch (Exception e) {
            log.error("섹션 잠금 해제 실패 - sectionId: {}", sectionId, e);
            return ResponseEntity.badRequest()
                    .body(ApiResponse.errorWithType("섹션 잠금 해제에 실패했습니다"));
        }
    }

    // Helper methods
    
    /**
     * 정렬 옵션 생성
     */
    private Sort createSort(String sortBy, String sortDir) {
        Sort.Direction direction = "asc".equalsIgnoreCase(sortDir) ? Sort.Direction.ASC : Sort.Direction.DESC;
        
        switch (sortBy) {
            case "title":
                return Sort.by(direction, "title");
            case "meeting":
                return Sort.by(direction, "createdAt"); // 회의 일시로 정렬 (임시로 생성일시 사용)
            case "modified":
            default:
                return Sort.by(direction, "lastModifiedAt");
        }
    }
    
    /**
     * 실제 통계 계산
     */
    private MinutesListResponse.Statistics calculateRealStatistics(String userId, String participationType) {
        try {
            // 전체 회의록 조회 (작성자 기준)
            List<Minutes> allMinutes = minutesService.getMinutesByCreator(userId);
            
            long totalCount = allMinutes.size();
            long draftCount = allMinutes.stream()
                    .filter(m -> "DRAFT".equals(m.getStatus()))
                    .count();
            long completeCount = allMinutes.stream()
                    .filter(m -> "FINALIZED".equals(m.getStatus()))
                    .count();
            
            return MinutesListResponse.Statistics.builder()
                    .totalCount(totalCount)
                    .draftCount(draftCount)
                    .completeCount(completeCount)
                    .build();
        } catch (Exception e) {
            log.warn("통계 계산 실패, 기본값 반환 - userId: {}", userId, e);
            return MinutesListResponse.Statistics.builder()
                    .totalCount(0L)
                    .draftCount(0L)
                    .completeCount(0L)
                    .build();
        }
    }

    private MinutesListResponse.MinutesItem convertToMinutesItem(MinutesDTO minutesDTO) {
        // 완료율 계산
        int completionRate = minutesDTO.getTodoCount() > 0 ? 
                (minutesDTO.getCompletedTodoCount() * 100) / minutesDTO.getTodoCount() : 100;
        
        return MinutesListResponse.MinutesItem.builder()
                .minutesId(minutesDTO.getMinutesId())
                .title(minutesDTO.getTitle())
                .meetingTitle(minutesDTO.getMeetingTitle())
                .status(minutesDTO.getStatus())
                .version(minutesDTO.getVersion())
                .createdAt(minutesDTO.getCreatedAt())
                .lastModifiedAt(minutesDTO.getLastModifiedAt())
                .meetingDate(minutesDTO.getCreatedAt()) // 임시로 생성일시 사용
                .createdBy(minutesDTO.getCreatedBy())
                .lastModifiedBy(minutesDTO.getLastModifiedBy())
                .participantCount(minutesDTO.getParticipantCount() != null ? minutesDTO.getParticipantCount() : 0)
                .todoCount(minutesDTO.getTodoCount())
                .completedTodoCount(minutesDTO.getCompletedTodoCount())
                .completionRate(completionRate)
                .isCreatedByUser(true) // 현재는 작성자 기준으로만 조회하므로 true
                .build();
    }
    
    /**
     * 상태별 필터링
     */
    private boolean filterByStatus(MinutesListResponse.MinutesItem item, String status) {
        if ("all".equals(status)) {
            return true;
        }
        if ("draft".equals(status)) {
            return "DRAFT".equals(item.getStatus());
        }
        if ("complete".equals(status)) {
            return "FINALIZED".equals(item.getStatus());
        }
        return true;
    }
    
    /**
     * 참여 유형별 필터링 - 현재는 사용하지 않음 (작성자 기준으로만 조회)
     */
    private boolean filterByParticipationType(MinutesListResponse.MinutesItem item, String participationType, String userId) {
        // 현재는 작성자 기준으로만 조회하므로 항상 true 반환
        return true;
    }
    
    /**
     * 검색어 필터링
     */
    private boolean filterBySearch(MinutesListResponse.MinutesItem item, String search) {
        if (search == null || search.trim().isEmpty()) {
            return true;
        }
        String searchLower = search.toLowerCase();
        return item.getTitle().toLowerCase().contains(searchLower) ||
               item.getMeetingTitle().toLowerCase().contains(searchLower);
    }
    
    /**
     * 정렬 적용
     */
    private void applySorting(List<MinutesListResponse.MinutesItem> items, String sortBy, String sortDir) {
        boolean ascending = "asc".equalsIgnoreCase(sortDir);
        
        switch (sortBy) {
            case "title":
                items.sort((a, b) -> ascending ? 
                    a.getTitle().compareTo(b.getTitle()) : 
                    b.getTitle().compareTo(a.getTitle()));
                break;
            case "meeting":
                items.sort((a, b) -> ascending ? 
                    a.getMeetingDate().compareTo(b.getMeetingDate()) : 
                    b.getMeetingDate().compareTo(a.getMeetingDate()));
                break;
            case "modified":
            default:
                items.sort((a, b) -> ascending ? 
                    a.getLastModifiedAt().compareTo(b.getLastModifiedAt()) : 
                    b.getLastModifiedAt().compareTo(a.getLastModifiedAt()));
                break;
        }
    }
    
    /**
     * 통계 계산
     */
    private MinutesListResponse.Statistics calculateStatistics(List<MinutesListResponse.MinutesItem> allItems, 
                                                              String participationType, String userId) {
        List<MinutesListResponse.MinutesItem> filteredItems = allItems.stream()
                .filter(item -> filterByParticipationType(item, participationType, userId))
                .collect(Collectors.toList());
        
        long totalCount = filteredItems.size();
        long draftCount = filteredItems.stream()
                .filter(item -> "DRAFT".equals(item.getStatus()))
                .count();
        long completeCount = filteredItems.stream()
                .filter(item -> "FINALIZED".equals(item.getStatus()))
                .count();
        
        return MinutesListResponse.Statistics.builder()
                .totalCount(totalCount)
                .draftCount(draftCount)
                .completeCount(completeCount)
                .build();
    }


    /**
     * Mock 관련회의록 생성 (프로토타입 기반)
     */
    private List<MinutesDetailResponse.RelatedMinutes> createMockRelatedMinutes() {
        return List.of(
            MinutesDetailResponse.RelatedMinutes.builder()
                .minutesId("minutes-related-001")
                .title("AI 기능 개선 회의")
                .meetingDate(LocalDateTime.of(2025, 10, 23, 15, 0))
                .author("이준호")
                .relevancePercentage(92)
                .relevanceLevel("HIGH")
                .summary("AI 요약 정확도 개선 방안 논의. BERT 모델 도입 및 학습 데이터 확보 계획 수립.")
                .build(),
            MinutesDetailResponse.RelatedMinutes.builder()
                .minutesId("minutes-related-002")
                .title("개발 리소스 계획 회의")
                .meetingDate(LocalDateTime.of(2025, 10, 22, 11, 0))
                .author("김민준")
                .relevancePercentage(88)
                .relevanceLevel("MEDIUM")
                .summary("Q4 개발 리소스 현황 및 배분 계획. 신규 프로젝트 우선순위 협의.")
                .build(),
            MinutesDetailResponse.RelatedMinutes.builder()
                .minutesId("minutes-related-003")
                .title("경쟁사 분석 회의")
                .meetingDate(LocalDateTime.of(2025, 10, 20, 10, 0))
                .author("박서연")
                .relevancePercentage(78)
                .relevanceLevel("MEDIUM")
                .summary("경쟁사 A, B, C 분석 결과. 우리의 차별점은 실시간 협업 및 검증 기능.")
                .build()
        );
    }

    private MinutesDetailResponse convertToMinutesDetailResponse(MinutesDTO minutesDTO) {
        try {
            // 실제 회의 정보 조회
            MinutesDetailResponse.MeetingInfo meetingInfo = buildMeetingInfo(minutesDTO);
            
            // 실제 안건 정보 조회
            List<MinutesDetailResponse.AgendaInfo> agendas = buildAgendaInfoList(minutesDTO.getMinutesId());
            
            // 실제 Todo 정보 조회
            MinutesDetailResponse.TodoProgress todoProgress = buildTodoProgress(minutesDTO.getMinutesId());
            
            // 실제 대시보드 정보 구성
            MinutesDetailResponse.DashboardInfo dashboardInfo = buildDashboardInfo(minutesDTO, agendas, todoProgress);
            
            return MinutesDetailResponse.builder()
                    .minutesId(minutesDTO.getMinutesId())
                    .title(minutesDTO.getTitle())
                    .memo(minutesDTO.getMemo() != null ? minutesDTO.getMemo() : "")
                    .status(minutesDTO.getStatus())
                    .version(minutesDTO.getVersion())
                    .createdAt(minutesDTO.getCreatedAt())
                    .lastModifiedAt(minutesDTO.getLastModifiedAt())
                    .createdBy(minutesDTO.getCreatedBy())
                    .lastModifiedBy(minutesDTO.getLastModifiedBy())
                    .meeting(meetingInfo)
                    .dashboard(dashboardInfo)
                    .agendas(agendas)
                    .build();
                    
        } catch (Exception e) {
            log.error("실제 데이터 조회 실패 - minutesId: {}", minutesDTO.getMinutesId(), e);
            throw new RuntimeException("회의록 상세 정보 조회에 실패했습니다", e);
        }
    }

    private int calculateProgressPercentage(Integer totalCount, Integer completedCount) {
        if (totalCount == null || totalCount == 0) {
            return 100;
        }
        if (completedCount == null) {
            return 0;
        }
        return (completedCount * 100) / totalCount;
    }

    /**
     * 회의 정보 구성
     */
    private MinutesDetailResponse.MeetingInfo buildMeetingInfo(MinutesDTO minutesDTO) {
        try {
            // 실제 회의 정보 조회
            var meeting = meetingService.getMeeting(minutesDTO.getMeetingId());
            
            return MinutesDetailResponse.MeetingInfo.builder()
                    .meetingId(minutesDTO.getMeetingId())
                    .title(minutesDTO.getMeetingTitle())
                    .scheduledAt(meeting.getScheduledAt())
                    .startedAt(meeting.getStartedAt())
                    .endedAt(meeting.getEndedAt())
                    .organizerId(meeting.getOrganizerId())
                    .organizerName("주최자") // TODO: 실제 주최자 이름 조회 필요
                    .location(meeting.getLocation() != null ? meeting.getLocation() : "온라인 회의")
                    .durationMinutes(calculateActualDuration(meeting))
                    .participants(buildParticipantList(minutesDTO.getMeetingId()))
                    .build();
        } catch (Exception e) {
            log.warn("회의 정보 조회 실패 - meetingId: {}", minutesDTO.getMeetingId(), e);
            return buildDefaultMeetingInfo(minutesDTO);
        }
    }

    /**
     * 참석자 목록 구성
     */
    private List<MinutesDetailResponse.Participant> buildParticipantList(String meetingId) {
        try {
            // 실제 참석자 조회
            List<String> participantIds = participantReader.findParticipantsByMeetingId(meetingId);

            if (participantIds.isEmpty()) {
                log.info("참석자가 없습니다 - meetingId: {}", meetingId);
                return new ArrayList<>();
            }
            
            // 참석자 정보를 MinutesDetailResponse.Participant로 변환
            return participantIds.stream()
                    .map(userId -> MinutesDetailResponse.Participant.builder()
                            .userId(userId)
                            .name(getUserName(userId))  // TODO: User 서비스에서 실제 이름 조회
                            .role(determineRole(userId, meetingId))  // 역할 결정
                            .avatarColor(generateAvatarColor(userId))  // 아바타 색상 생성
                            .build())
                    .collect(Collectors.toList());
        } catch (Exception e) {
            log.error("참석자 정보 조회 실패 - meetingId: {}", meetingId, e);
            return new ArrayList<>();
        }
    }

    /**
     * 사용자 이름 조회 (임시 구현)
     * TODO: User 서비스에서 실제 사용자 정보 조회
     */
    private String getUserName(String userId) {
        // 임시로 userId를 그대로 반환
        // 실제로는 User 서비스에서 조회해야 함
        return userId;
    }

    /**
     * 참석자 역할 결정
     */
    private String determineRole(String userId, String meetingId) {
        try {
            // 회의 정보를 조회해서 주최자인지 확인
            var meeting = meetingService.getMeeting(meetingId);
            if (meeting.getOrganizerId().equals(userId)) {
                return "주최자";
            }
        } catch (Exception e) {
            log.warn("회의 정보 조회 실패로 역할 확인 불가 - meetingId: {}", meetingId, e);
        }
        return "참석자";
    }

    /**
     * 아바타 색상 생성
     */
    private String generateAvatarColor(String userId) {
        String[] colors = {"avatar-blue", "avatar-green", "avatar-purple", "avatar-orange", "avatar-red"};
        int hash = userId.hashCode();
        int index = Math.abs(hash) % colors.length;
        return colors[index];
    }

    /**
     * 안건 정보 목록 구성
     */
    private List<MinutesDetailResponse.AgendaInfo> buildAgendaInfoList(String minutesId) {
        try {
            // agenda_sections 테이블에서 실제 안건 조회
            var agendaSections = agendaSectionService.getAgendaSectionsByMinutesId(minutesId);

            if (!agendaSections.isEmpty()) {
                // agenda_sections 데이터가 있으면 이를 사용
                return agendaSections.stream()
                    .map(this::convertAgendaSectionToAgendaInfo)
                    .collect(Collectors.toList());
            } else {
                // agenda_sections 데이터가 없으면 기존 minutes_sections 데이터 사용
                var sections = minutesSectionService.getSectionsByMinutes(minutesId);
                return sections.stream()
                    .map(this::convertToAgendaInfo)
                    .collect(Collectors.toList());
            }
        } catch (Exception e) {
            log.error("안건 정보 조회 실패 - minutesId: {}", minutesId, e);
            return new ArrayList<>();
        }
    }

    /**
     * Todo 진행상황 구성
     */
    private MinutesDetailResponse.TodoProgress buildTodoProgress(String minutesId) {
        try {
            // 실제 Todo 목록 조회 (상태 관계없이 모든 todos)
            List<com.unicorn.hgzero.meeting.biz.domain.Todo> todos = todoReader.findByMinutesId(minutesId);
            
            int totalCount = todos.size();
            int completedCount = (int) todos.stream()
                .filter(todo -> "COMPLETED".equals(todo.getStatus()))
                .count();
            
            List<MinutesDetailResponse.SimpleTodo> simpleTodos = todos.stream()
                .map(todo -> MinutesDetailResponse.SimpleTodo.builder()
                        .todoId(todo.getTodoId())
                        .title(todo.getTitle())
                        .assigneeName(todo.getAssigneeId())  // 담당자 ID를 그대로 사용
                        .dueDate(todo.getDueDate() != null ?
                                todo.getDueDate().atStartOfDay() :
                                null)  // LocalDate를 LocalDateTime으로 변환
                        .build())
                .collect(Collectors.toList());
            
            log.info("Todo 조회 성공 - minutesId: {}, totalCount: {}", minutesId, totalCount);

            return MinutesDetailResponse.TodoProgress.builder()
                    .totalCount(totalCount)
                    .completedCount(completedCount)
                    .progressPercentage(calculateProgressPercentage(totalCount, completedCount))
                    .todos(simpleTodos)
                    .build();
        } catch (Exception e) {
            log.error("Todo 정보 조회 실패 - minutesId: {}", minutesId, e);
            return MinutesDetailResponse.TodoProgress.builder()
                    .totalCount(0)
                    .completedCount(0)
                    .progressPercentage(0)
                    .todos(new ArrayList<>())
                    .build();
        }
    }

    /**
     * 대시보드 정보 구성
     */
    private MinutesDetailResponse.DashboardInfo buildDashboardInfo(
            MinutesDTO minutesDTO, 
            List<MinutesDetailResponse.AgendaInfo> agendas,
            MinutesDetailResponse.TodoProgress todoProgress) {
        
        // 핵심내용 추출 (안건별 AI 요약에서)
        List<MinutesDetailResponse.KeyPoint> keyPoints = extractKeyPoints(agendas);
        
        // 키워드 추출
        List<String> keywords = extractKeywords(agendas);
        
        // 실제 회의 시간 계산
        int actualDurationMinutes = calculateActualMeetingDuration(minutesDTO.getMeetingId());
        
        // 통계 정보
        MinutesDetailResponse.Statistics stats = MinutesDetailResponse.Statistics.builder()
                .participantCount(minutesDTO.getParticipantCount() != null ? minutesDTO.getParticipantCount() : 0)
                .durationMinutes(actualDurationMinutes)
                .agendaCount(agendas.size())
                .todoCount(todoProgress.getTotalCount())
                .build();
        
        // 결정사항 추출
        List<MinutesDetailResponse.Decision> decisions = extractDecisions(agendas);
        
        // AI 기반 관련회의록 조회 (캐시 우선)
        List<MinutesDetailResponse.RelatedMinutes> relatedMinutes = getRelatedMinutesFromAI(minutesDTO.getMinutesId());
        
        return MinutesDetailResponse.DashboardInfo.builder()
                .keyPoints(keyPoints)
                .keywords(keywords)
                .stats(stats)
                .decisions(decisions)
                .todoProgress(todoProgress)
                .relatedMinutes(relatedMinutes)
                .build();
    }


    // === 헬퍼 메소드들 ===
    
    private MinutesDetailResponse.MeetingInfo buildDefaultMeetingInfo(MinutesDTO minutesDTO) {
        return MinutesDetailResponse.MeetingInfo.builder()
                .meetingId(minutesDTO.getMeetingId())
                .title(minutesDTO.getMeetingTitle())
                .location("회의실 정보 없음")
                .durationMinutes(90)
                .participants(buildParticipantList(minutesDTO.getMeetingId()))
                .build();
    }

    private int calculateActualDuration(Object meeting) {
        // TODO: 실제 회의 시간 계산 로직 구현
        // Meeting 객체에서 startedAt, endedAt을 사용하여 계산
        return 90;
    }

    private MinutesDetailResponse.AgendaInfo convertToAgendaInfo(Object section) {
        if (!(section instanceof MinutesSection)) {
            log.warn("MinutesSection이 아닌 객체가 전달됨: {}", section.getClass().getSimpleName());
            return null;
        }
        
        MinutesSection minutesSection = (MinutesSection) section;
        
        // AI 요약 정보 구성 (현재는 기본값 사용)
        MinutesDetailResponse.AiSummary aiSummary = MinutesDetailResponse.AiSummary.builder()
                .content(minutesSection.getContent() != null ? minutesSection.getContent() : "AI 요약 정보 없음")
                .generatedAt(LocalDateTime.now().minusMinutes(30))
                .modifiedAt(LocalDateTime.now().minusMinutes(10))
                .build();
        
        // 안건 상세 내용 구성
        MinutesDetailResponse.AgendaDetails details = MinutesDetailResponse.AgendaDetails.builder()
                .discussions(parseDiscussions(minutesSection.getContent()))
                .decisions(parseDecisions(minutesSection.getContent()))
                .build();
        
        return MinutesDetailResponse.AgendaInfo.builder()
                .agendaId(minutesSection.getSectionId())
                .title(minutesSection.getTitle() != null ? minutesSection.getTitle() : "제목 없음")
                .orderIndex(minutesSection.getOrder() != null ? minutesSection.getOrder() : 1)
                .isVerified(minutesSection.isVerified())
                .verifiedBy(minutesSection.isVerified() ? "시스템" : null)
                .verifiedAt(minutesSection.isVerified() ? LocalDateTime.now().minusHours(1) : null)
                .aiSummary(aiSummary)
                .details(details)
                .relatedMinutes(new ArrayList<>()) // 관련 회의록은 별도 로직 필요
                .build();
    }

    /**
     * AgendaSection을 AgendaInfo로 변환
     */
    private MinutesDetailResponse.AgendaInfo convertAgendaSectionToAgendaInfo(AgendaSection agendaSection) {
        // AI 요약 정보 구성
        MinutesDetailResponse.AiSummary aiSummary = MinutesDetailResponse.AiSummary.builder()
                .content(agendaSection.getAiSummaryShort() != null ? agendaSection.getAiSummaryShort() : "")
                .generatedAt(agendaSection.getCreatedAt() != null ? agendaSection.getCreatedAt() : LocalDateTime.now())
                .modifiedAt(agendaSection.getUpdatedAt() != null ? agendaSection.getUpdatedAt() : LocalDateTime.now())
                .build();

        // 안건 상세 내용 구성 - JSON 파싱
        List<String> discussionsList = parseJsonToList(agendaSection.getDiscussions());
        List<String> decisionsList = parseJsonToList(agendaSection.getDecisions());

        MinutesDetailResponse.AgendaDetails details = MinutesDetailResponse.AgendaDetails.builder()
                .discussions(discussionsList)
                .decisions(decisionsList)
                .build();

        return MinutesDetailResponse.AgendaInfo.builder()
                .agendaId(agendaSection.getId())
                .title(agendaSection.getAgendaTitle() != null ? agendaSection.getAgendaTitle() : "제목 없음")
                .orderIndex(agendaSection.getAgendaNumber() != null ? agendaSection.getAgendaNumber() : 1)
                .isVerified(true) // agenda_sections는 기본적으로 검증된 데이터
                .verifiedBy("AI")
                .verifiedAt(agendaSection.getCreatedAt())
                .aiSummary(aiSummary)
                .details(details)
                .relatedMinutes(new ArrayList<>()) // 관련 회의록은 별도 로직 필요
                .build();
    }

    /**
     * JSON 문자열을 List<String>으로 파싱
     */
    private List<String> parseJsonToList(String json) {
        if (json == null || json.isEmpty()) {
            return new ArrayList<>();
        }

        try {
            // 간단한 JSON 배열 파싱
            json = json.trim();
            if (json.startsWith("[") && json.endsWith("]")) {
                json = json.substring(1, json.length() - 1);
                if (json.isEmpty()) {
                    return new ArrayList<>();
                }

                // 쉼표로 분리하고 따옴표 제거
                return Arrays.stream(json.split(","))
                        .map(s -> s.trim())
                        .map(s -> s.startsWith("\"") && s.endsWith("\"") ? s.substring(1, s.length() - 1) : s)
                        .filter(s -> !s.isEmpty())
                        .collect(Collectors.toList());
            }

            // JSON 형식이 아니면 그대로 한 줄로 반환
            return List.of(json);
        } catch (Exception e) {
            log.warn("JSON 파싱 실패: {}", json, e);
            return List.of(json); // 파싱 실패시 원본 텍스트를 그대로 반환
        }
    }


    private MinutesDetailResponse.SimpleTodo convertToSimpleTodo(Object todo) {
        if (!(todo instanceof Todo)) {
            log.warn("Todo가 아닌 객체가 전달됨: {}", todo.getClass().getSimpleName());
            return MinutesDetailResponse.SimpleTodo.builder()
                    .todoId("unknown-todo")
                    .title("변환 실패 Todo")
                    .assigneeName("알 수 없음")
                    .status("PENDING")
                    .priority("LOW")
                    .dueDate(LocalDateTime.now().plusDays(7))
                    .dueDayStatus("D-7")
                    .build();
        }
        
        Todo todoEntity = (Todo) todo;
        
        // 담당자 이름 조회 (현재는 기본값 사용, 실제로는 User 서비스에서 조회 필요)
        String assigneeName = getAssigneeName(todoEntity.getAssigneeId());
        
        // 마감일 상태 계산
        String dueDayStatus = calculateDueDayStatus(todoEntity.getDueDate(), todoEntity.getStatus());
        
        return MinutesDetailResponse.SimpleTodo.builder()
                .todoId(todoEntity.getTodoId())
                .title(todoEntity.getTitle() != null ? todoEntity.getTitle() : "제목 없음")
                .assigneeName(assigneeName)
                .status(todoEntity.getStatus() != null ? todoEntity.getStatus() : "PENDING")
                .priority(todoEntity.getPriority() != null ? todoEntity.getPriority() : "MEDIUM")
                .dueDate(todoEntity.getDueDate() != null ? todoEntity.getDueDate().atStartOfDay() : null)
                .dueDayStatus(dueDayStatus)
                .build();
    }

    private List<MinutesDetailResponse.KeyPoint> extractKeyPoints(List<MinutesDetailResponse.AgendaInfo> agendas) {
        // 안건별 AI 요약에서 핵심내용 추출
        List<MinutesDetailResponse.KeyPoint> keyPoints = new ArrayList<>();
        for (int i = 0; i < agendas.size() && i < 4; i++) {
            MinutesDetailResponse.AgendaInfo agenda = agendas.get(i);
            if (agenda.getAiSummary() != null) {
                keyPoints.add(MinutesDetailResponse.KeyPoint.builder()
                        .index(i + 1)
                        .content(agenda.getAiSummary().getContent())
                        .build());
            }
        }
        
        // 샘플 데이터로 보완
        if (keyPoints.isEmpty()) {
            keyPoints = createSampleKeyPoints();
        }
        
        return keyPoints;
    }

    /**
     * 회의록 섹션 내용에서 논의사항 추출
     */
    private List<String> parseDiscussions(String content) {
        if (content == null || content.trim().isEmpty()) {
            return List.of("논의 내용 없음");
        }
        
        // 간단한 패턴으로 논의사항 추출 (실제로는 AI 파싱 필요)
        Pattern pattern = Pattern.compile("논의[:：]\\s*(.+?)(?=결정|\\n|$)", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(content);
        List<String> discussions = new ArrayList<>();
        
        while (matcher.find()) {
            discussions.add(matcher.group(1).trim());
        }
        
        if (discussions.isEmpty()) {
            // 전체 내용을 논의사항으로 처리
            discussions.add(content.length() > 100 ? content.substring(0, 100) + "..." : content);
        }
        
        return discussions;
    }

    /**
     * 회의록 섹션 내용에서 결정사항 추출
     */
    private List<String> parseDecisions(String content) {
        if (content == null || content.trim().isEmpty()) {
            return new ArrayList<>();
        }
        
        // 간단한 패턴으로 결정사항 추출 (실제로는 AI 파싱 필요)
        Pattern pattern = Pattern.compile("결정[:：]\\s*(.+?)(?=논의|\\n|$)", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(content);
        List<String> decisions = new ArrayList<>();
        
        while (matcher.find()) {
            decisions.add(matcher.group(1).trim());
        }
        
        return decisions;
    }

    /**
     * 담당자 이름 조회 (실제로는 User 서비스에서 조회 필요)
     */
    private String getAssigneeName(String assigneeId) {
        if (assigneeId == null) {
            return "미지정";
        }
        
        // TODO: 실제 User 서비스에서 사용자 정보 조회
        // 현재는 간단한 매핑 사용
        switch (assigneeId) {
            case "user1":
                return "김민준";
            case "user2":
                return "박서연";
            case "user3":
                return "이준호";
            default:
                return "사용자" + assigneeId;
        }
    }

    /**
     * 마감일 상태 계산
     */
    private String calculateDueDayStatus(LocalDate dueDate, String status) {
        if (dueDate == null) {
            return "마감일 없음";
        }
        
        if ("COMPLETED".equals(status)) {
            return "완료";
        }
        
        LocalDate today = LocalDate.now();
        long daysDiff = ChronoUnit.DAYS.between(today, dueDate);
        
        if (daysDiff < 0) {
            return "D+" + Math.abs(daysDiff); // 마감일 지남
        } else if (daysDiff == 0) {
            return "D-Day";
        } else {
            return "D-" + daysDiff;
        }
    }

    private List<String> extractKeywords(List<MinutesDetailResponse.AgendaInfo> agendas) {
        // TODO: AI를 통한 키워드 추출 로직 구현
        return List.of("#AI회의록", "#음성인식", "#협업도구", "#스타트업", "#베타출시");
    }

    private List<MinutesDetailResponse.Decision> extractDecisions(List<MinutesDetailResponse.AgendaInfo> agendas) {
        List<MinutesDetailResponse.Decision> decisions = new ArrayList<>();
        
        for (MinutesDetailResponse.AgendaInfo agenda : agendas) {
            if (agenda.getDetails() != null && agenda.getDetails().getDecisions() != null) {
                for (String decision : agenda.getDetails().getDecisions()) {
                    decisions.add(MinutesDetailResponse.Decision.builder()
                            .content(decision)
                            .decidedBy("김민준")
                            .decidedAt(LocalDateTime.now().minusHours(2))
                            .background("안건 논의 결과")
                            .build());
                }
            }
        }
        
        // 실제 데이터만 사용
        
        return decisions;
    }

    // === 샘플 데이터 생성 메소드들 ===

    private List<MinutesDetailResponse.KeyPoint> createSampleKeyPoints() {
        return List.of(
            MinutesDetailResponse.KeyPoint.builder()
                .index(1)
                .content("AI 기반 회의록 자동화 서비스 출시 결정. 타겟은 중소기업 및 스타트업.")
                .build(),
            MinutesDetailResponse.KeyPoint.builder()
                .index(2)
                .content("주요 기능: 음성인식, AI 요약, Todo 자동 추출, 실시간 검증 및 협업.")
                .build(),
            MinutesDetailResponse.KeyPoint.builder()
                .index(3)
                .content("개발 기간 3개월 (Phase 1-3), 베타 출시일 2025년 12월 1일.")
                .build(),
            MinutesDetailResponse.KeyPoint.builder()
                .index(4)
                .content("프리 런칭 캠페인 11월 진행, 초기 100팀 무료 제공 후 유료 전환.")
                .build()
        );
    }





    /**
     * AI 분석 결과로 응답 데이터 향상
     */
    private void enhanceWithAiAnalysis(MinutesDetailResponse response, MinutesDTO minutesDTO, 
                                     String userId, String userName) {
        try {
            // 1. 캐시된 AI 분석 결과 조회 시도
            Optional<AiAnalysisDTO> aiAnalysis = aiServiceGateway.getAiAnalysis(
                    minutesDTO.getMinutesId(), 
                    extractContentForAiAnalysis(minutesDTO)
            );

            if (aiAnalysis.isPresent()) {
                // AI 분석 결과가 있으면 대시보드 정보 업데이트
                updateDashboardWithAiAnalysis(response, aiAnalysis.get());
                log.debug("AI 분석 결과로 대시보드 정보 업데이트 완료 - minutesId: {}", 
                        minutesDTO.getMinutesId());
            } else {
                // AI 분석 결과가 없으면 비동기 분석 요청 이벤트 발행
                publishAiAnalysisRequest(minutesDTO, userId, userName);
                log.debug("AI 분석 요청 이벤트 발행 완료 - minutesId: {}", 
                        minutesDTO.getMinutesId());
            }

        } catch (Exception e) {
            log.warn("AI 분석 처리 중 오류 - minutesId: {}", minutesDTO.getMinutesId(), e);
            // AI 분석 실패는 응답에 영향주지 않음
        }
    }

    /**
     * AI 분석용 컨텐츠 추출
     */
    private String extractContentForAiAnalysis(MinutesDTO minutesDTO) {
        StringBuilder content = new StringBuilder();
        
        // 회의록 제목과 메모
        content.append("제목: ").append(minutesDTO.getTitle()).append("\n");
        if (minutesDTO.getMemo() != null && !minutesDTO.getMemo().trim().isEmpty()) {
            content.append("메모: ").append(minutesDTO.getMemo()).append("\n");
        }
        
        // 안건별 내용 추가
        try {
            var sections = minutesSectionService.getSectionsByMinutes(minutesDTO.getMinutesId());
            for (var section : sections) {
                if (section instanceof MinutesSection) {
                    MinutesSection minutesSection = (MinutesSection) section;
                    content.append("\n안건: ").append(minutesSection.getTitle()).append("\n");
                    if (minutesSection.getContent() != null) {
                        content.append(minutesSection.getContent()).append("\n");
                    }
                }
            }
        } catch (Exception e) {
            log.warn("안건 내용 추출 실패 - minutesId: {}", minutesDTO.getMinutesId(), e);
        }
        
        return content.toString();
    }

    /**
     * AI 분석 결과로 대시보드 정보 업데이트
     */
    private MinutesDetailResponse updateDashboardWithAiAnalysis(MinutesDetailResponse response, AiAnalysisDTO aiAnalysis) {
        if (response.getDashboard() == null || aiAnalysis.getResult() == null) {
            return response;
        }

        AiAnalysisDTO.AnalysisResult result = aiAnalysis.getResult();
        MinutesDetailResponse.DashboardInfo dashboard = response.getDashboard();

        // 핵심내용 업데이트
        if (result.getKeyPoints() != null && !result.getKeyPoints().isEmpty()) {
            List<MinutesDetailResponse.KeyPoint> keyPoints = result.getKeyPoints().stream()
                    .map(kp -> MinutesDetailResponse.KeyPoint.builder()
                            .index(kp.getIndex())
                            .content(kp.getContent())
                            .build())
                    .collect(Collectors.toList());
            
            // DashboardInfo를 새로 빌드 (불변 객체이므로)
            MinutesDetailResponse.DashboardInfo updatedDashboard = MinutesDetailResponse.DashboardInfo.builder()
                    .keyPoints(keyPoints)
                    .keywords(result.getKeywords() != null ? result.getKeywords() : dashboard.getKeywords())
                    .stats(dashboard.getStats())
                    .decisions(convertAiDecisions(result.getDecisions()))
                    .todoProgress(dashboard.getTodoProgress())
                    .relatedMinutes(convertAiRelatedMinutes(result.getRelatedMinutes()))
                    .build();
            
            // Response 객체를 새로 빌드 (toBuilder 없이 직접 빌드)
            MinutesDetailResponse updatedResponse = MinutesDetailResponse.builder()
                    .minutesId(response.getMinutesId())
                    .title(response.getTitle())
                    .memo(response.getMemo())
                    .status(response.getStatus())
                    .version(response.getVersion())
                    .createdAt(response.getCreatedAt())
                    .lastModifiedAt(response.getLastModifiedAt())
                    .createdBy(response.getCreatedBy())
                    .lastModifiedBy(response.getLastModifiedBy())
                    .meeting(response.getMeeting())
                    .dashboard(updatedDashboard) // AI 분석 결과로 업데이트된 대시보드
                    .agendas(response.getAgendas())
                    .build();
            
            // AI 분석 결과가 적용된 응답 반환
            return updatedResponse;
        }
        
        // AI 분석 결과가 없으면 원본 응답 반환
        return response;
    }

    /**
     * AI 결정사항을 Response 형식으로 변환
     */
    private List<MinutesDetailResponse.Decision> convertAiDecisions(List<AiAnalysisDTO.Decision> aiDecisions) {
        if (aiDecisions == null) {
            return new ArrayList<>();
        }
        
        return aiDecisions.stream()
                .map(decision -> MinutesDetailResponse.Decision.builder()
                        .content(decision.getContent())
                        .decidedBy("AI 분석")
                        .decidedAt(LocalDateTime.now())
                        .background("AI가 회의록에서 추출한 결정사항")
                        .build())
                .collect(Collectors.toList());
    }

    /**
     * AI 관련회의록을 Response 형식으로 변환
     */
    private List<MinutesDetailResponse.RelatedMinutes> convertAiRelatedMinutes(List<AiAnalysisDTO.RelatedMinutes> aiRelated) {
        if (aiRelated == null) {
            return new ArrayList<>();
        }
        
        return aiRelated.stream()
                .map(related -> MinutesDetailResponse.RelatedMinutes.builder()
                        .minutesId(related.getMinutesId())
                        .title(related.getTitle())
                        .meetingDate(related.getMeetingDate())
                        .author("시스템")
                        .relevancePercentage((int)(related.getRelevanceScore() * 100))
                        .relevanceLevel(related.getRelevanceScore() > 0.8 ? "HIGH" : 
                                       related.getRelevanceScore() > 0.5 ? "MEDIUM" : "LOW")
                        .summary(related.getReason())
                        .build())
                .collect(Collectors.toList());
    }

    /**
     * Response 객체 필드 복사 (불변 객체 업데이트용)
     */

    /**
     * AI 분석 요청 이벤트 발행
     */
    private void publishAiAnalysisRequest(MinutesDTO minutesDTO, String requesterId, String requesterName) {
        try {
            // 회의 메타정보 구성
            MinutesAnalysisRequestEvent.MeetingMeta meetingMeta = MinutesAnalysisRequestEvent.MeetingMeta.builder()
                    .title(minutesDTO.getMeetingTitle())
                    .meetingDate(minutesDTO.getCreatedAt())
                    .participantCount(minutesDTO.getParticipantCount() != null ? minutesDTO.getParticipantCount() : 1)
                    .durationMinutes(90) // 기본값
                    .organizerId(minutesDTO.getCreatedBy())
                    .participantIds(new String[]{requesterId}) // 기본값
                    .build();

            // AI 분석 요청 이벤트 생성
            MinutesAnalysisRequestEvent requestEvent = MinutesAnalysisRequestEvent.create(
                    minutesDTO.getMinutesId(),
                    minutesDTO.getMeetingId(),
                    requesterId,
                    requesterName,
                    extractContentForAiAnalysis(minutesDTO),
                    meetingMeta
            );

            // 이벤트 발행
            eventPublisher.publishMinutesAnalysisRequest(requestEvent);
            
            log.info("AI 분석 요청 이벤트 발행 완료 - minutesId: {}, eventId: {}", 
                    minutesDTO.getMinutesId(), requestEvent.getEventId());

        } catch (Exception e) {
            log.error("AI 분석 요청 이벤트 발행 실패 - minutesId: {}", minutesDTO.getMinutesId(), e);
        }
    }

    /**
     * 실제 회의 시간 계산
     */
    private int calculateActualMeetingDuration(String meetingId) {
        try {
            var meeting = meetingService.getMeeting(meetingId);
            if (meeting.getStartedAt() != null && meeting.getEndedAt() != null) {
                long minutes = Duration.between(meeting.getStartedAt(), meeting.getEndedAt()).toMinutes();
                return (int) Math.max(minutes, 0);
            }
            // 시작/종료 시간이 없으면 기본값 반환
            return 90; // 기본 90분
        } catch (Exception e) {
            log.warn("회의 시간 계산 실패 - meetingId: {}", meetingId, e);
            return 90;
        }
    }

    /**
     * AI 기반 관련회의록 조회
     */
    private List<MinutesDetailResponse.RelatedMinutes> getRelatedMinutesFromAI(String minutesId) {
        try {
            // 캐시된 AI 분석 결과에서 관련회의록 조회
            Optional<AiAnalysisDTO> aiAnalysis = cacheService.getAiAnalysis(minutesId);
            if (aiAnalysis.isPresent() && aiAnalysis.get().getResult() != null) {
                return convertAiRelatedMinutes(aiAnalysis.get().getResult().getRelatedMinutes());
            }
            
            // AI 분석 결과가 없으면 빈 목록 반환
            return new ArrayList<>();
        } catch (Exception e) {
            log.warn("AI 관련회의록 조회 실패 - minutesId: {}", minutesId, e);
            return new ArrayList<>();
        }
    }

    /**
     * 실제 회의 시간 계산 (Meeting 객체 사용)
     */
    private int calculateActualDuration(Meeting meeting) {
        try {
            if (meeting.getStartedAt() != null && meeting.getEndedAt() != null) {
                long minutes = Duration.between(meeting.getStartedAt(), meeting.getEndedAt()).toMinutes();
                return (int) Math.max(minutes, 0);
            }
            // 시작/종료 시간이 없으면 예정 시간으로 추정
            return 90; // 기본 90분
        } catch (Exception e) {
            log.warn("회의 시간 계산 실패", e);
            return 90;
        }
    }
    

}