package com.unicorn.hgzero.stt.service;

import com.unicorn.hgzero.common.exception.BusinessException;
import com.unicorn.hgzero.stt.dto.TranscriptionDto;
import com.unicorn.hgzero.stt.dto.TranscriptSegmentDto;
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 org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockMultipartFile;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

/**
 * 음성 변환 서비스 단위 테스트
 */
@ExtendWith(MockitoExtension.class)
@DisplayName("음성 변환 서비스 테스트")
class TranscriptionServiceTest {
    
    @Mock
    private TranscriptionRepository transcriptionRepository;
    
    @Mock
    private TranscriptSegmentRepository segmentRepository;
    
    @Mock
    private RecordingRepository recordingRepository;
    
    @Mock
    private EventPublisher eventPublisher;
    
    @InjectMocks
    private TranscriptionServiceImpl transcriptionService;
    
    private RecordingEntity recordingEntity;
    private TranscriptionDto.StreamRequest streamRequest;
    
    @BeforeEach
    void setUp() {
        recordingEntity = RecordingEntity.builder()
                .recordingId("REC-20250123-001")
                .meetingId("MEETING-001")
                .sessionId("SESSION-001")
                .build();
        
        streamRequest = TranscriptionDto.StreamRequest.builder()
                .recordingId("REC-20250123-001")
                .audioData("base64-encoded-audio-data")
                .timestamp(System.currentTimeMillis())
                .chunkIndex(1)
                .build();
    }
    
    @Test
    @DisplayName("실시간 음성 변환 성공")
    void processAudioStream_Success() {
        // Given
        when(recordingRepository.findById(anyString())).thenReturn(Optional.of(recordingEntity));
        when(segmentRepository.save(any(TranscriptSegmentEntity.class))).thenReturn(any());
        when(segmentRepository.getSpeakerStatisticsByRecording(anyString())).thenReturn(List.of());
        when(segmentRepository.countByRecordingId(anyString())).thenReturn(1L);
        when(recordingRepository.save(any(RecordingEntity.class))).thenReturn(recordingEntity);
        
        // When
        TranscriptSegmentDto.Response response = transcriptionService.processAudioStream(streamRequest);
        
        // Then
        assertThat(response).isNotNull();
        assertThat(response.getRecordingId()).isEqualTo("REC-20250123-001");
        assertThat(response.getText()).isNotEmpty();
        assertThat(response.getConfidence()).isGreaterThan(0.8);
        assertThat(response.getSpeakerId()).isNotEmpty();
        
        verify(recordingRepository).findById("REC-20250123-001");
        verify(segmentRepository).save(any(TranscriptSegmentEntity.class));
        verify(eventPublisher).publishAsync(eq("transcription-events"), any());
    }
    
    @Test
    @DisplayName("실시간 음성 변환 실패 - 녹음 없음")
    void processAudioStream_RecordingNotFound() {
        // Given
        when(recordingRepository.findById(anyString())).thenReturn(Optional.empty());
        
        // When & Then
        assertThatThrownBy(() -> transcriptionService.processAudioStream(streamRequest))
                .isInstanceOf(BusinessException.class)
                .hasMessageContaining("녹음을 찾을 수 없습니다");
        
        verify(recordingRepository).findById("REC-20250123-001");
        verify(segmentRepository, never()).save(any());
        verify(eventPublisher, never()).publishAsync(any(), any());
    }
    
    @Test
    @DisplayName("저신뢰도 세그먼트 경고 이벤트 발행")
    void processAudioStream_LowConfidenceWarning() {
        // Given
        when(recordingRepository.findById(anyString())).thenReturn(Optional.of(recordingEntity));
        when(segmentRepository.save(any(TranscriptSegmentEntity.class))).thenReturn(any());
        when(segmentRepository.getSpeakerStatisticsByRecording(anyString())).thenReturn(List.of());
        when(segmentRepository.countByRecordingId(anyString())).thenReturn(1L);
        when(recordingRepository.save(any(RecordingEntity.class))).thenReturn(recordingEntity);
        
        // When
        TranscriptSegmentDto.Response response = transcriptionService.processAudioStream(streamRequest);
        
        // Then
        // 저신뢰도인 경우 경고 이벤트도 발행되는지 확인
        if (response.getWarningFlag()) {
            verify(eventPublisher, times(2)).publishAsync(eq("transcription-events"), any());
        } else {
            verify(eventPublisher, times(1)).publishAsync(eq("transcription-events"), any());
        }
    }
    
    @Test
    @DisplayName("배치 음성 변환 작업 시작 성공")
    void transcribeAudioBatch_Success() {
        // Given
        TranscriptionDto.BatchRequest batchRequest = TranscriptionDto.BatchRequest.builder()
                .recordingId("REC-20250123-001")
                .callbackUrl("https://api.example.com/callback")
                .build();
        
        MockMultipartFile audioFile = new MockMultipartFile(
                "audioFile", "test.wav", "audio/wav", "test audio content".getBytes()
        );
        
        when(recordingRepository.findById(anyString())).thenReturn(Optional.of(recordingEntity));
        
        // When
        TranscriptionDto.BatchResponse response = transcriptionService.transcribeAudioBatch(batchRequest, audioFile);
        
        // Then
        assertThat(response).isNotNull();
        assertThat(response.getRecordingId()).isEqualTo("REC-20250123-001");
        assertThat(response.getStatus()).isEqualTo("PROCESSING");
        assertThat(response.getJobId()).isNotEmpty();
        assertThat(response.getCallbackUrl()).isEqualTo("https://api.example.com/callback");
        assertThat(response.getEstimatedCompletionTime()).isAfter(LocalDateTime.now());
        
        verify(recordingRepository).findById("REC-20250123-001");
    }
    
    @Test
    @DisplayName("배치 변환 완료 콜백 처리 성공")
    void processBatchCallback_Success() {
        // Given
        List<TranscriptSegmentDto.Detail> segments = List.of(
                TranscriptSegmentDto.Detail.builder()
                        .transcriptId("TRS-001")
                        .text("안녕하세요")
                        .speakerId("SPK-001")
                        .speakerName("화자-001")
                        .timestamp(System.currentTimeMillis())
                        .duration(2.5)
                        .confidence(0.95)
                        .build(),
                TranscriptSegmentDto.Detail.builder()
                        .transcriptId("TRS-002")
                        .text("회의를 시작하겠습니다")
                        .speakerId("SPK-002")
                        .speakerName("화자-002")
                        .timestamp(System.currentTimeMillis() + 3000)
                        .duration(3.2)
                        .confidence(0.92)
                        .build()
        );
        
        TranscriptionDto.BatchCallbackRequest callbackRequest = TranscriptionDto.BatchCallbackRequest.builder()
                .jobId("JOB-20250123-001")
                .status("COMPLETED")
                .segments(segments)
                .build();
        
        when(segmentRepository.save(any(TranscriptSegmentEntity.class))).thenReturn(any());
        when(transcriptionRepository.save(any(TranscriptionEntity.class))).thenReturn(any());
        
        // When
        TranscriptionDto.CompleteResponse response = transcriptionService.processBatchCallback(callbackRequest);
        
        // Then
        assertThat(response).isNotNull();
        assertThat(response.getJobId()).isEqualTo("JOB-20250123-001");
        assertThat(response.getStatus()).isEqualTo("COMPLETED");
        assertThat(response.getSegmentCount()).isEqualTo(2);
        assertThat(response.getTotalDuration()).isEqualTo(5); // 2.5 + 3.2 반올림
        assertThat(response.getAverageConfidence()).isEqualTo(0.935); // (0.95 + 0.92) / 2
        
        verify(segmentRepository, times(2)).save(any(TranscriptSegmentEntity.class));
        verify(transcriptionRepository).save(any(TranscriptionEntity.class));
        verify(eventPublisher).publishAsync(eq("transcription-events"), any());
    }
    
    @Test
    @DisplayName("변환 결과 조회 성공")
    void getTranscription_Success() {
        // Given
        String recordingId = "REC-20250123-001";
        
        TranscriptionEntity transcriptionEntity = TranscriptionEntity.builder()
                .transcriptId("TRS-FULL-001")
                .recordingId(recordingId)
                .fullText("안녕하세요, 회의를 시작하겠습니다.")
                .segmentCount(2)
                .totalDuration(300)
                .averageConfidence(0.92)
                .speakerCount(2)
                .build();
        
        when(transcriptionRepository.findByRecordingId(recordingId))
                .thenReturn(Optional.of(transcriptionEntity));
        
        // When
        TranscriptionDto.Response response = transcriptionService.getTranscription(recordingId, false, null);
        
        // Then
        assertThat(response).isNotNull();
        assertThat(response.getRecordingId()).isEqualTo(recordingId);
        assertThat(response.getFullText()).isEqualTo("안녕하세요, 회의를 시작하겠습니다.");
        assertThat(response.getSegmentCount()).isEqualTo(2);
        assertThat(response.getTotalDuration()).isEqualTo(300);
        assertThat(response.getAverageConfidence()).isEqualTo(0.92);
        assertThat(response.getSpeakerCount()).isEqualTo(2);
        assertThat(response.getSegments()).isNull(); // includeSegments = false
        
        verify(transcriptionRepository).findByRecordingId(recordingId);
    }
    
    @Test
    @DisplayName("세그먼트 포함 변환 결과 조회 성공")
    void getTranscription_WithSegments_Success() {
        // Given
        String recordingId = "REC-20250123-001";
        
        TranscriptionEntity transcriptionEntity = TranscriptionEntity.builder()
                .transcriptId("TRS-FULL-001")
                .recordingId(recordingId)
                .fullText("안녕하세요, 회의를 시작하겠습니다.")
                .segmentCount(2)
                .totalDuration(300)
                .averageConfidence(0.92)
                .speakerCount(2)
                .build();
        
        List<TranscriptSegmentEntity> segmentEntities = List.of(
                TranscriptSegmentEntity.builder()
                        .segmentId("SEG-001")
                        .recordingId(recordingId)
                        .text("안녕하세요")
                        .speakerId("SPK-001")
                        .speakerName("화자-001")
                        .timestamp(System.currentTimeMillis())
                        .duration(2.5)
                        .confidence(0.95)
                        .build()
        );
        
        when(transcriptionRepository.findByRecordingId(recordingId))
                .thenReturn(Optional.of(transcriptionEntity));
        when(segmentRepository.findByRecordingIdOrderByTimestamp(recordingId))
                .thenReturn(segmentEntities);
        
        // When
        TranscriptionDto.Response response = transcriptionService.getTranscription(recordingId, true, null);
        
        // Then
        assertThat(response).isNotNull();
        assertThat(response.getSegments()).isNotNull();
        assertThat(response.getSegments()).hasSize(1);
        assertThat(response.getSegments().get(0).getText()).isEqualTo("안녕하세요");
        
        verify(transcriptionRepository).findByRecordingId(recordingId);
        verify(segmentRepository).findByRecordingIdOrderByTimestamp(recordingId);
    }
    
    @Test
    @DisplayName("변환 결과 조회 실패 - 결과 없음")
    void getTranscription_NotFound() {
        // Given
        String recordingId = "REC-NOTFOUND-001";
        
        when(transcriptionRepository.findByRecordingId(recordingId)).thenReturn(Optional.empty());
        
        // When & Then
        assertThatThrownBy(() -> transcriptionService.getTranscription(recordingId, false, null))
                .isInstanceOf(BusinessException.class)
                .hasMessageContaining("변환 결과를 찾을 수 없습니다");
        
        verify(transcriptionRepository).findByRecordingId(recordingId);
    }
}