package com.unicorn.hgzero.stt.service;

import com.unicorn.hgzero.common.exception.BusinessException;
import com.unicorn.hgzero.common.exception.ErrorCode;
import com.unicorn.hgzero.stt.dto.TranscriptionDto;
import com.unicorn.hgzero.stt.dto.TranscriptSegmentDto;
import com.unicorn.hgzero.stt.event.TranscriptionEvent;
import com.unicorn.hgzero.stt.event.publisher.EventPublisher;
import com.unicorn.hgzero.stt.repository.entity.RecordingEntity;
import com.unicorn.hgzero.stt.repository.entity.TranscriptSegmentEntity;
import com.unicorn.hgzero.stt.repository.entity.TranscriptionEntity;
import com.unicorn.hgzero.stt.repository.jpa.RecordingRepository;
import com.unicorn.hgzero.stt.repository.jpa.TranscriptSegmentRepository;
import com.unicorn.hgzero.stt.repository.jpa.TranscriptionRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * 음성 변환 서비스 구현체
 * 음성-텍스트 변환 기능을 구현
 */
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
public class TranscriptionServiceImpl implements TranscriptionService {
    
    private final TranscriptionRepository transcriptionRepository;
    private final TranscriptSegmentRepository segmentRepository;
    private final RecordingRepository recordingRepository;
    private final EventPublisher eventPublisher;
    
    @Override
    public TranscriptSegmentDto.Response processAudioStream(TranscriptionDto.StreamRequest request) {
        log.info("실시간 음성 변환 처리 - recordingId: {}, timestamp: {}", 
                request.getRecordingId(), request.getTimestamp());
        
        // 녹음 존재 확인
        RecordingEntity recording = findRecordingById(request.getRecordingId());
        
        // 실시간 STT 처리 (Azure Speech Service 호출)
        // 실제로는 Azure Speech SDK를 사용해야 함
        String recognizedText = simulateAzureSpeechRecognition(request.getAudioData());
        String speakerId = simulateSpeakerIdentification(request.getAudioData());
        Double confidence = simulateConfidenceScore();
        
        // 세그먼트 ID 생성
        String segmentId = generateSegmentId();
        
        // 신뢰도 경고 플래그 설정
        Boolean warningFlag = confidence < 0.6;
        
        // 세그먼트 저장
        TranscriptSegmentEntity segment = TranscriptSegmentEntity.builder()
                .segmentId(segmentId)
                .recordingId(request.getRecordingId())
                .text(recognizedText)
                .speakerId(speakerId)
                .speakerName("화자-" + speakerId.substring(4)) // 임시 화자명
                .timestamp(request.getTimestamp())
                .duration(3.5) // 임시 발언 시간
                .confidence(confidence)
                .warningFlag(warningFlag)
                .chunkIndex(request.getChunkIndex())
                .build();
        
        segmentRepository.save(segment);
        
        // 녹음 통계 업데이트
        updateRecordingStatistics(request.getRecordingId());
        
        // 세그먼트 생성 이벤트 발행
        TranscriptionEvent.SegmentCreated event = TranscriptionEvent.SegmentCreated.of(
                segmentId, request.getRecordingId(), recording.getMeetingId(),
                recognizedText, speakerId, "화자-" + speakerId.substring(4),
                request.getTimestamp(), 3.5, confidence, warningFlag
        );
        eventPublisher.publishAsync("transcription-events", event);
        
        // 저신뢰도 경고 이벤트 발행
        if (warningFlag) {
            TranscriptionEvent.LowConfidenceWarning warningEvent = TranscriptionEvent.LowConfidenceWarning.of(
                    segmentId, request.getRecordingId(), recording.getMeetingId(),
                    recognizedText, speakerId, confidence
            );
            eventPublisher.publishAsync("transcription-events", warningEvent);
        }
        
        log.info("실시간 음성 변환 완료 - segmentId: {}, confidence: {}", segmentId, confidence);
        
        return TranscriptSegmentDto.Response.builder()
                .transcriptId(segmentId)
                .recordingId(request.getRecordingId())
                .text(recognizedText)
                .speakerId(speakerId)
                .speakerName("화자-" + speakerId.substring(4))
                .timestamp(request.getTimestamp())
                .duration(3.5)
                .confidence(confidence)
                .warningFlag(warningFlag)
                .build();
    }
    
    @Override
    public TranscriptionDto.BatchResponse transcribeAudioBatch(TranscriptionDto.BatchRequest request, MultipartFile audioFile) {
        log.info("배치 음성 변환 시작 - recordingId: {}, fileSize: {}", 
                request.getRecordingId(), audioFile.getSize());
        
        // 녹음 존재 확인
        RecordingEntity recording = findRecordingById(request.getRecordingId());
        
        // 배치 작업 ID 생성
        String jobId = generateJobId();
        
        // Azure Batch Transcription Job 생성 (실제로는 Azure Speech SDK 사용)
        // 여기서는 시뮬레이션
        
        log.info("배치 음성 변환 작업 생성 완료 - jobId: {}", jobId);
        
        return TranscriptionDto.BatchResponse.builder()
                .jobId(jobId)
                .recordingId(request.getRecordingId())
                .status("PROCESSING")
                .estimatedCompletionTime(LocalDateTime.now().plusSeconds(30))
                .callbackUrl(request.getCallbackUrl())
                .build();
    }
    
    @Override
    public TranscriptionDto.CompleteResponse processBatchCallback(TranscriptionDto.BatchCallbackRequest request) {
        log.info("배치 변환 콜백 처리 - jobId: {}, status: {}", request.getJobId(), request.getStatus());
        
        if ("COMPLETED".equals(request.getStatus()) && request.getSegments() != null) {
            // 세그먼트 저장
            for (TranscriptSegmentDto.Detail segmentDto : request.getSegments()) {
                String segmentId = generateSegmentId();
                
                TranscriptSegmentEntity segment = TranscriptSegmentEntity.builder()
                        .segmentId(segmentId)
                        .transcriptId(segmentDto.getTranscriptId())
                        .text(segmentDto.getText())
                        .speakerId(segmentDto.getSpeakerId())
                        .speakerName(segmentDto.getSpeakerName())
                        .timestamp(segmentDto.getTimestamp())
                        .duration(segmentDto.getDuration())
                        .confidence(segmentDto.getConfidence())
                        .warningFlag(segmentDto.getConfidence() < 0.6)
                        .build();
                
                segmentRepository.save(segment);
            }
            
            // 전체 텍스트 통합 및 변환 결과 저장
            String recordingId = extractRecordingIdFromJobId(request.getJobId());
            aggregateAndSaveTranscription(recordingId, request.getSegments());
        }
        
        return TranscriptionDto.CompleteResponse.builder()
                .jobId(request.getJobId())
                .status(request.getStatus())
                .segmentCount(request.getSegments() != null ? request.getSegments().size() : 0)
                .totalDuration(calculateTotalDuration(request.getSegments()))
                .averageConfidence(calculateAverageConfidence(request.getSegments()))
                .build();
    }
    
    @Override
    @Transactional(readOnly = true)
    public TranscriptionDto.Response getTranscription(String recordingId, Boolean includeSegments, String speakerId) {
        log.debug("변환 텍스트 조회 - recordingId: {}, includeSegments: {}", recordingId, includeSegments);
        
        // 변환 결과 조회
        TranscriptionEntity transcription = transcriptionRepository.findByRecordingId(recordingId)
                .orElseThrow(() -> new BusinessException(ErrorCode.ENTITY_NOT_FOUND, "변환 결과를 찾을 수 없습니다"));
        
        List<TranscriptSegmentDto.Detail> segments = null;
        
        if (Boolean.TRUE.equals(includeSegments)) {
            List<TranscriptSegmentEntity> segmentEntities;
            
            if (speakerId != null) {
                segmentEntities = segmentRepository.findByRecordingIdAndSpeakerIdOrderByTimestamp(recordingId, speakerId);
            } else {
                segmentEntities = segmentRepository.findByRecordingIdOrderByTimestamp(recordingId);
            }
            
            segments = segmentEntities.stream()
                    .map(this::convertToSegmentDetail)
                    .collect(Collectors.toList());
        }
        
        return TranscriptionDto.Response.builder()
                .recordingId(recordingId)
                .fullText(transcription.getFullText())
                .segmentCount(transcription.getSegmentCount())
                .totalDuration(transcription.getTotalDuration())
                .averageConfidence(transcription.getAverageConfidence())
                .speakerCount(transcription.getSpeakerCount())
                .segments(segments)
                .build();
    }
    
    /**
     * 녹음 ID로 엔티티 조회
     */
    private RecordingEntity findRecordingById(String recordingId) {
        return recordingRepository.findById(recordingId)
                .orElseThrow(() -> new BusinessException(ErrorCode.ENTITY_NOT_FOUND, "녹음을 찾을 수 없습니다"));
    }
    
    /**
     * 세그먼트 ID 생성
     */
    private String generateSegmentId() {
        return "TRS-SEG-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
    }
    
    /**
     * 작업 ID 생성
     */
    private String generateJobId() {
        return "JOB-" + LocalDateTime.now().toString().substring(0, 10).replace("-", "") + "-" + 
               String.format("%03d", (int)(Math.random() * 1000));
    }
    
    /**
     * Azure Speech 인식 시뮬레이션
     */
    private String simulateAzureSpeechRecognition(String audioData) {
        // 실제로는 Azure Speech Service SDK 호출
        return "안녕하세요, 오늘 회의를 시작하겠습니다.";
    }
    
    /**
     * 화자 식별 시뮬레이션
     */
    private String simulateSpeakerIdentification(String audioData) {
        // 실제로는 Azure Speaker Recognition API 호출
        return "SPK-" + String.format("%03d", (int)(Math.random() * 999) + 1);
    }
    
    /**
     * 신뢰도 점수 시뮬레이션
     */
    private Double simulateConfidenceScore() {
        // 실제로는 Azure Speech Service에서 반환
        return 0.85 + (Math.random() * 0.15); // 0.85 ~ 1.0
    }
    
    /**
     * 녹음 통계 업데이트
     */
    private void updateRecordingStatistics(String recordingId) {
        List<Object[]> stats = segmentRepository.getSpeakerStatisticsByRecording(recordingId);
        Long segmentCount = segmentRepository.countByRecordingId(recordingId);
        
        RecordingEntity recording = findRecordingById(recordingId);
        recording.updateSpeakerCount(stats.size());
        recording.updateSegmentCount(segmentCount.intValue());
        
        recordingRepository.save(recording);
    }
    
    /**
     * 전체 텍스트 통합 및 저장
     */
    private void aggregateAndSaveTranscription(String recordingId, List<TranscriptSegmentDto.Detail> segments) {
        // 전체 텍스트 생성
        String fullText = segments.stream()
                .map(segment -> segment.getSpeakerName() + ": " + segment.getText())
                .collect(Collectors.joining("\n"));
        
        // 통계 계산
        Double averageConfidence = segments.stream()
                .mapToDouble(TranscriptSegmentDto.Detail::getConfidence)
                .average()
                .orElse(0.0);
        
        Integer totalDuration = segments.stream()
                .mapToInt(segment -> segment.getDuration().intValue())
                .sum();
        
        Integer speakerCount = (int) segments.stream()
                .map(TranscriptSegmentDto.Detail::getSpeakerId)
                .distinct()
                .count();
        
        // 변환 결과 저장
        TranscriptionEntity transcription = TranscriptionEntity.builder()
                .transcriptId(generateTranscriptId())
                .recordingId(recordingId)
                .fullText(fullText)
                .segmentCount(segments.size())
                .totalDuration(totalDuration)
                .averageConfidence(averageConfidence)
                .speakerCount(speakerCount)
                .completedAt(LocalDateTime.now())
                .build();
        
        transcriptionRepository.save(transcription);
        
        // 변환 완료 이벤트 발행
        TranscriptionEvent.TranscriptionCompleted event = TranscriptionEvent.TranscriptionCompleted.of(
                transcription.getTranscriptId(), recordingId, extractMeetingIdFromRecordingId(recordingId),
                fullText, segments.size(), totalDuration, averageConfidence, speakerCount
        );
        eventPublisher.publishAsync("transcription-events", event);
    }
    
    /**
     * 변환 ID 생성
     */
    private String generateTranscriptId() {
        return "TRS-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
    }
    
    /**
     * 작업 ID에서 녹음 ID 추출
     */
    private String extractRecordingIdFromJobId(String jobId) {
        // 실제로는 작업 테이블에서 조회
        return "REC-20250123-001";
    }
    
    /**
     * 녹음 ID에서 회의 ID 추출
     */
    private String extractMeetingIdFromRecordingId(String recordingId) {
        try {
            RecordingEntity recording = recordingRepository.findById(recordingId).orElse(null);
            return recording != null ? recording.getMeetingId() : "MEETING-DEFAULT";
        } catch (Exception e) {
            return "MEETING-DEFAULT";
        }
    }
    
    /**
     * 총 시간 계산
     */
    private Integer calculateTotalDuration(List<TranscriptSegmentDto.Detail> segments) {
        if (segments == null) return 0;
        return segments.stream()
                .mapToInt(segment -> segment.getDuration().intValue())
                .sum();
    }
    
    /**
     * 평균 신뢰도 계산
     */
    private Double calculateAverageConfidence(List<TranscriptSegmentDto.Detail> segments) {
        if (segments == null || segments.isEmpty()) return 0.0;
        return segments.stream()
                .mapToDouble(TranscriptSegmentDto.Detail::getConfidence)
                .average()
                .orElse(0.0);
    }
    
    /**
     * 세그먼트 엔티티를 DTO로 변환
     */
    private TranscriptSegmentDto.Detail convertToSegmentDetail(TranscriptSegmentEntity entity) {
        return TranscriptSegmentDto.Detail.builder()
                .transcriptId(entity.getTranscriptId())
                .text(entity.getText())
                .speakerId(entity.getSpeakerId())
                .speakerName(entity.getSpeakerName())
                .timestamp(entity.getTimestamp())
                .duration(entity.getDuration())
                .confidence(entity.getConfidence())
                .build();
    }
}