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.SpeakerDto;
import com.unicorn.hgzero.stt.event.SpeakerEvent;
import com.unicorn.hgzero.stt.event.publisher.EventPublisher;
import com.unicorn.hgzero.stt.repository.entity.SpeakerEntity;
import com.unicorn.hgzero.stt.repository.jpa.SpeakerRepository;
import com.unicorn.hgzero.stt.repository.jpa.TranscriptSegmentRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

/**
 * 화자 서비스 구현체
 * 화자 식별 및 관리 기능을 구현
 */
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
public class SpeakerServiceImpl implements SpeakerService {
    
    private final SpeakerRepository speakerRepository;
    private final TranscriptSegmentRepository segmentRepository;
    private final EventPublisher eventPublisher;
    
    @Override
    public SpeakerDto.IdentificationResponse identifySpeaker(SpeakerDto.IdentifyRequest request) {
        log.info("화자 식별 시작 - recordingId: {}", request.getRecordingId());
        
        // Azure Speaker Recognition API 호출 시뮬레이션
        String profileId = simulateAzureSpeakerRecognition(request.getAudioFrame());
        Double confidence = simulateIdentificationConfidence();
        
        // 기존 화자 조회
        SpeakerEntity speaker = speakerRepository.findByProfileId(profileId).orElse(null);
        boolean isNewSpeaker = false;
        
        if (speaker == null) {
            // 신규 화자 등록
            String speakerId = generateSpeakerId();
            String speakerName = "화자-" + speakerId.substring(4);
            
            speaker = SpeakerEntity.builder()
                    .speakerId(speakerId)
                    .speakerName(speakerName)
                    .profileId(profileId)
                    .totalSegments(0)
                    .totalDuration(0)
                    .averageConfidence(confidence)
                    .firstAppeared(LocalDateTime.now())
                    .lastAppeared(LocalDateTime.now())
                    .build();
            
            speakerRepository.save(speaker);
            isNewSpeaker = true;
            
            // 신규 화자 식별 이벤트 발행
            SpeakerEvent.NewSpeakerIdentified event = SpeakerEvent.NewSpeakerIdentified.of(
                    speakerId, speakerName, profileId, request.getRecordingId(), 
                    extractMeetingIdFromRecordingId(request.getRecordingId()), confidence
            );
            eventPublisher.publishAsync("speaker-events", event);
            
            log.info("신규 화자 등록 완료 - speakerId: {}, confidence: {}", speakerId, confidence);
        } else {
            log.debug("기존 화자 식별 - speakerId: {}, confidence: {}", speaker.getSpeakerId(), confidence);
        }
        
        return SpeakerDto.IdentificationResponse.builder()
                .speakerId(speaker.getSpeakerId())
                .speakerName(speaker.getSpeakerName())
                .confidence(confidence)
                .isNewSpeaker(isNewSpeaker)
                .profileId(profileId)
                .build();
    }
    
    @Override
    @Transactional(readOnly = true)
    public SpeakerDto.DetailResponse getSpeaker(String speakerId) {
        log.debug("화자 정보 조회 - speakerId: {}", speakerId);
        
        SpeakerEntity speaker = findSpeakerById(speakerId);
        
        return SpeakerDto.DetailResponse.builder()
                .speakerId(speaker.getSpeakerId())
                .speakerName(speaker.getSpeakerName())
                .profileId(speaker.getProfileId())
                .userId(speaker.getUserId())
                .totalSegments(speaker.getTotalSegments())
                .totalDuration(speaker.getTotalDuration())
                .averageConfidence(speaker.getAverageConfidence())
                .firstAppeared(speaker.getFirstAppeared())
                .lastAppeared(speaker.getLastAppeared())
                .build();
    }
    
    @Override
    public SpeakerDto.DetailResponse updateSpeaker(String speakerId, SpeakerDto.UpdateRequest request) {
        log.info("화자 정보 업데이트 - speakerId: {}", speakerId);
        
        SpeakerEntity speaker = findSpeakerById(speakerId);
        
        // 화자 정보 업데이트
        String oldSpeakerName = speaker.getSpeakerName();
        String oldUserId = speaker.getUserId();
        speaker.updateSpeakerInfo(request.getSpeakerName(), request.getUserId());
        SpeakerEntity savedSpeaker = speakerRepository.save(speaker);
        
        // 화자 정보 업데이트 이벤트 발행
        SpeakerEvent.SpeakerUpdated event = SpeakerEvent.SpeakerUpdated.of(
                speakerId, oldSpeakerName, request.getSpeakerName(),
                oldUserId, request.getUserId(), "SYSTEM"
        );
        eventPublisher.publishAsync("speaker-events", event);
        
        log.info("화자 정보 업데이트 완료 - speakerId: {}, speakerName: {}", 
                speakerId, request.getSpeakerName());
        
        return getSpeaker(speakerId);
    }
    
    @Override
    @Transactional(readOnly = true)
    public SpeakerDto.ListResponse getRecordingSpeakers(String recordingId) {
        log.debug("녹음 화자 목록 조회 - recordingId: {}", recordingId);
        
        // 녹음별 화자 통계 조회
        List<Object[]> speakerStats = segmentRepository.getSpeakerStatisticsByRecording(recordingId);
        
        List<SpeakerDto.SpeakerSummary> speakers = speakerStats.stream()
                .map(stat -> {
                    String speakerId = (String) stat[0];
                    Long segmentCount = (Long) stat[1];
                    Double totalDuration = (Double) stat[2];
                    Double averageConfidence = (Double) stat[3];
                    
                    // 화자 정보 조회
                    SpeakerEntity speaker = speakerRepository.findById(speakerId).orElse(null);
                    String speakerName = speaker != null ? speaker.getSpeakerName() : "알 수 없는 화자";
                    
                    // 발언 비율 계산 (전체 시간 대비)
                    Double speakingRatio = calculateSpeakingRatio(recordingId, totalDuration);
                    
                    return SpeakerDto.SpeakerSummary.builder()
                            .speakerId(speakerId)
                            .speakerName(speakerName)
                            .segmentCount(segmentCount.intValue())
                            .totalDuration(totalDuration.intValue())
                            .speakingRatio(speakingRatio)
                            .build();
                })
                .collect(Collectors.toList());
        
        return SpeakerDto.ListResponse.builder()
                .recordingId(recordingId)
                .speakerCount(speakers.size())
                .speakers(speakers)
                .build();
    }
    
    /**
     * 화자 ID로 엔티티 조회
     */
    private SpeakerEntity findSpeakerById(String speakerId) {
        return speakerRepository.findById(speakerId)
                .orElseThrow(() -> new BusinessException(ErrorCode.ENTITY_NOT_FOUND, "화자를 찾을 수 없습니다"));
    }
    
    /**
     * 화자 ID 생성
     */
    private String generateSpeakerId() {
        return "SPK-" + String.format("%03d", (int)(Math.random() * 999) + 1);
    }
    
    /**
     * Azure Speaker Recognition 시뮬레이션
     */
    private String simulateAzureSpeakerRecognition(String audioFrame) {
        // 실제로는 Azure Cognitive Services Speaker Recognition API 호출
        return "PROFILE-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
    }
    
    /**
     * 화자 식별 신뢰도 시뮬레이션
     */
    private Double simulateIdentificationConfidence() {
        // 실제로는 Azure Speaker Recognition API에서 반환
        return 0.90 + (Math.random() * 0.10); // 0.90 ~ 1.0
    }
    
    /**
     * 발언 비율 계산
     */
    private Double calculateSpeakingRatio(String recordingId, Double speakerDuration) {
        // 전체 녹음 시간 조회 (임시로 1800초로 설정)
        Double totalDuration = 1800.0;
        return speakerDuration / totalDuration;
    }
    
    /**
     * 녹음 ID에서 회의 ID 추출
     */
    private String extractMeetingIdFromRecordingId(String recordingId) {
        // 실제로는 Recording 엔티티에서 조회
        return "MEETING-" + recordingId.substring(4, 12);
    }
}