package com.unicorn.hgzero.stt.service;

import com.unicorn.hgzero.common.exception.BusinessException;
import com.unicorn.hgzero.stt.domain.Recording;
import com.unicorn.hgzero.stt.dto.RecordingDto;
import com.unicorn.hgzero.stt.event.publisher.EventPublisher;
import com.unicorn.hgzero.stt.repository.entity.RecordingEntity;
import com.unicorn.hgzero.stt.repository.jpa.RecordingRepository;
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 java.time.LocalDateTime;
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 RecordingServiceTest {
    
    @Mock
    private RecordingRepository recordingRepository;
    
    @Mock
    private EventPublisher eventPublisher;
    
    @InjectMocks
    private RecordingServiceImpl recordingService;
    
    private RecordingDto.PrepareRequest prepareRequest;
    private RecordingEntity recordingEntity;
    
    @BeforeEach
    void setUp() {
        prepareRequest = RecordingDto.PrepareRequest.builder()
                .meetingId("MEETING-001")
                .sessionId("SESSION-001")
                .language("ko-KR")
                .build();
        
        recordingEntity = RecordingEntity.builder()
                .recordingId("REC-20250123-001")
                .meetingId("MEETING-001")
                .sessionId("SESSION-001")
                .status(Recording.RecordingStatus.READY)
                .language("ko-KR")
                .speakerCount(0)
                .segmentCount(0)
                .storagePath("recordings/MEETING-001/SESSION-001.wav")
                .build();
    }
    
    @Test
    @DisplayName("녹음 준비 성공")
    void prepareRecording_Success() {
        // Given
        when(recordingRepository.existsActiveRecordingByMeetingId(anyString())).thenReturn(false);
        when(recordingRepository.save(any(RecordingEntity.class))).thenReturn(recordingEntity);
        
        // When
        RecordingDto.PrepareResponse response = recordingService.prepareRecording(prepareRequest);
        
        // Then
        assertThat(response).isNotNull();
        assertThat(response.getSessionId()).isEqualTo("SESSION-001");
        assertThat(response.getStatus()).isEqualTo("READY");
        assertThat(response.getStreamUrl()).contains("SESSION-001");
        
        verify(recordingRepository).existsActiveRecordingByMeetingId("MEETING-001");
        verify(recordingRepository).save(any(RecordingEntity.class));
    }
    
    @Test
    @DisplayName("중복 녹음 세션 예외")
    void prepareRecording_DuplicateSession() {
        // Given
        when(recordingRepository.existsActiveRecordingByMeetingId(anyString())).thenReturn(true);
        
        // When & Then
        assertThatThrownBy(() -> recordingService.prepareRecording(prepareRequest))
                .isInstanceOf(BusinessException.class)
                .hasMessageContaining("이미 진행 중인 녹음 세션이 있습니다");
        
        verify(recordingRepository).existsActiveRecordingByMeetingId("MEETING-001");
        verify(recordingRepository, never()).save(any());
    }
    
    @Test
    @DisplayName("녹음 시작 성공")
    void startRecording_Success() {
        // Given
        String recordingId = "REC-20250123-001";
        RecordingDto.StartRequest startRequest = RecordingDto.StartRequest.builder()
                .startedBy("user001")
                .build();
        
        when(recordingRepository.findById(recordingId)).thenReturn(Optional.of(recordingEntity));
        when(recordingRepository.save(any(RecordingEntity.class))).thenReturn(recordingEntity);
        
        // When
        RecordingDto.StatusResponse response = recordingService.startRecording(recordingId, startRequest);
        
        // Then
        assertThat(response).isNotNull();
        assertThat(response.getRecordingId()).isEqualTo(recordingId);
        assertThat(response.getStatus()).isEqualTo("RECORDING");
        assertThat(response.getDuration()).isEqualTo(0);
        
        verify(recordingRepository).findById(recordingId);
        verify(recordingRepository).save(any(RecordingEntity.class));
        verify(eventPublisher).publishAsync(eq("recording-events"), any());
    }
    
    @Test
    @DisplayName("녹음 시작 실패 - 잘못된 상태")
    void startRecording_InvalidStatus() {
        // Given
        String recordingId = "REC-20250123-001";
        RecordingDto.StartRequest startRequest = RecordingDto.StartRequest.builder()
                .startedBy("user001")
                .build();
        
        RecordingEntity recordingEntity = RecordingEntity.builder()
                .recordingId(recordingId)
                .status(Recording.RecordingStatus.RECORDING) // 이미 녹음 중
                .build();
        
        when(recordingRepository.findById(recordingId)).thenReturn(Optional.of(recordingEntity));
        
        // When & Then
        assertThatThrownBy(() -> recordingService.startRecording(recordingId, startRequest))
                .isInstanceOf(BusinessException.class)
                .hasMessageContaining("녹음을 시작할 수 없는 상태입니다");
        
        verify(recordingRepository).findById(recordingId);
        verify(recordingRepository, never()).save(any());
        verify(eventPublisher, never()).publishAsync(any(), any());
    }
    
    @Test
    @DisplayName("녹음 중지 성공")
    void stopRecording_Success() {
        // Given
        String recordingId = "REC-20250123-001";
        RecordingDto.StopRequest stopRequest = RecordingDto.StopRequest.builder()
                .stoppedBy("user001")
                .build();
        
        RecordingEntity recordingEntity = RecordingEntity.builder()
                .recordingId(recordingId)
                .meetingId("MEETING-001")
                .status(Recording.RecordingStatus.RECORDING)
                .startTime(LocalDateTime.now().minusMinutes(30))
                .build();
        
        when(recordingRepository.findById(recordingId)).thenReturn(Optional.of(recordingEntity));
        when(recordingRepository.save(any(RecordingEntity.class))).thenReturn(recordingEntity);
        
        // When
        RecordingDto.StatusResponse response = recordingService.stopRecording(recordingId, stopRequest);
        
        // Then
        assertThat(response).isNotNull();
        assertThat(response.getRecordingId()).isEqualTo(recordingId);
        assertThat(response.getStatus()).isEqualTo("STOPPED");
        assertThat(response.getDuration()).isEqualTo(1800);
        assertThat(response.getFileSize()).isEqualTo(172800000L);
        
        verify(recordingRepository).findById(recordingId);
        verify(recordingRepository).save(any(RecordingEntity.class));
        verify(eventPublisher).publishAsync(eq("recording-events"), any());
    }
    
    @Test
    @DisplayName("녹음 정보 조회 성공")
    void getRecording_Success() {
        // Given
        String recordingId = "REC-20250123-001";
        
        when(recordingRepository.findById(recordingId)).thenReturn(Optional.of(recordingEntity));
        
        // When
        RecordingDto.DetailResponse response = recordingService.getRecording(recordingId);
        
        // Then
        assertThat(response).isNotNull();
        assertThat(response.getRecordingId()).isEqualTo(recordingId);
        assertThat(response.getMeetingId()).isEqualTo("MEETING-001");
        assertThat(response.getSessionId()).isEqualTo("SESSION-001");
        assertThat(response.getStatus()).isEqualTo("READY");
        assertThat(response.getLanguage()).isEqualTo("ko-KR");
        
        verify(recordingRepository).findById(recordingId);
    }
    
    @Test
    @DisplayName("존재하지 않는 녹음 조회 예외")
    void getRecording_NotFound() {
        // Given
        String recordingId = "REC-NOTFOUND-001";
        
        when(recordingRepository.findById(recordingId)).thenReturn(Optional.empty());
        
        // When & Then
        assertThatThrownBy(() -> recordingService.getRecording(recordingId))
                .isInstanceOf(BusinessException.class)
                .hasMessageContaining("녹음을 찾을 수 없습니다");
        
        verify(recordingRepository).findById(recordingId);
    }
}