package com.rj.scheduler;

import com.rj.config.AppConfig;
import com.rj.entity.AudioManagementSegments;
import com.rj.mapper.AudioManagementMapper;
import com.rj.mapper.AudioManagementSegmentsMapper;
import com.rj.service.IAudioManagementSegmentsService;
import com.rj.service.IAudioManagementStatisticsService;
import com.rj.service.MinIOService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 门店录音统计定时任务
 *
 * @author 李中华 ,spllzh
 * @since 2025-08-07
 */
@Slf4j
@Component
public class AudioStatisticsScheduler {

    @Autowired
    private IAudioManagementStatisticsService audioStatisticsService;

    @Autowired
    private AppConfig appConfig;

    @Autowired
    private IAudioManagementSegmentsService audioManagementSegmentsService;

    @Autowired
    private MinIOService minIOService;

    @Autowired
    private AudioManagementSegmentsMapper audioManagementSegmentsMapper;

    @Autowired
    private AudioManagementMapper audioManagementMapper;

    private static final Set<String> TRANSCRIPT_TEXT_SUFFIXES = Set.of(".txt", ".text");

    @Value("${app.audio.upload.yihangyi.txt-scan-dir:/home/lizh/java_env/AIDriverEEBackend/audio/yihangyi_txt}")
    private String yihangyiTxtScanDir;

    @Value("${app.audio.upload.yihangyi.txt-finish-dir:/home/lizh/java_env/AIDriverEEBackend/audio/yihangyi_txt_finish}")
    private String yihangyiTxtFinishDir;

    /**
     * 每 2 分钟：扫描目录中的转写文本，按「文件名去扩展名 + .mp3」匹配 {@code audio_file_original_name}，
     * 写入 {@code audio_management_segments.recording_text}，成功后移至完成目录并汇总回写 {@code audio_management}。
     */
    @Scheduled(fixedRate = 120000)
    public void syncYihangyiTranscriptTextFiles() {
        if (!appConfig.getScheduler().isStart()) {
            return;
        }
        if (System.getProperty("os.name", "").toLowerCase(Locale.ROOT).contains("win")) {
            log.info("syncYihangyiTranscriptTextFiles 跳过：Windows 环境（该任务仅在 Linux 服务器访问 yihangyi 目录）");
            return;
        }
        Path scanDir = Paths.get(yihangyiTxtScanDir);
        if (!Files.isDirectory(scanDir)) {
            log.error("yihangyi 转写文本扫描目录不存在或不是目录: {}", scanDir.toAbsolutePath());
            return;
        }
        List<Path> files;
        try (Stream<Path> stream = Files.list(scanDir)) {
            files = stream
                    .filter(Files::isRegularFile)
                    .filter(p -> {
                        String name = p.getFileName().toString();
                        int dot = name.lastIndexOf('.');
                        String suf = dot < 0 ? "" : name.substring(dot).toLowerCase(Locale.ROOT);
                        return TRANSCRIPT_TEXT_SUFFIXES.contains(suf);
                    })
                    .sorted(Comparator.comparing(p -> p.getFileName().toString()))
                    .collect(Collectors.toList());
        } catch (IOException e) {
            log.error("列出 yihangyi 转写文本目录失败: {}", scanDir.toAbsolutePath(), e);
            return;
        }
        log.info("yihangyi 转写文本本次扫描到文件数: {}", files.size());
        for (Path path : files) {
            try {
                syncOneTranscriptFile(path);
            } catch (Exception e) {
                log.error("处理 yihangyi 转写文本文件失败: {}", path.toAbsolutePath(), e);
            }
        }
    }

    private void syncOneTranscriptFile(Path path) throws IOException {
        String stem = fileNameWithoutExtension(path.getFileName().toString());
        String audioFileOriginalName = stem + ".mp3";
        String content = readTranscriptTextFile(path);
        log.info("执行 UPDATE audio_management_segments SET recording_text=? WHERE audio_file_original_name=? 参数长度={}, name={}",
                content != null ? content.length() : 0, audioFileOriginalName);
        int n = audioManagementSegmentsMapper.updateRecordingTextByAudioFileOriginalNameIgnoreTenant(
                content, audioFileOriginalName);
        if (n == 0) {
            log.warn("未找到 audio_file_original_name={} 的分段记录，跳过: {}", audioFileOriginalName, path.getFileName());
            return;
        }
        log.info("已更新 recording_text，audio_file_original_name={}，影响行数={}", audioFileOriginalName, n);
        String parentId = audioManagementSegmentsMapper.selectParentIdByAudioFileOriginalNameLimit1(audioFileOriginalName);
        Path finishDir = Paths.get(yihangyiTxtFinishDir);
        try {
            Files.createDirectories(finishDir);
            Path dest = finishDir.resolve(path.getFileName());
            Files.move(path, dest, StandardCopyOption.REPLACE_EXISTING);
            log.info("更新成功，已移动文件: {} -> {}", path.getFileName(), dest.toAbsolutePath());
        } catch (Exception e) {
            log.error("更新已成功但移动文件失败: {}", path.toAbsolutePath(), e);
            return;
        }
        if (StringUtils.hasText(parentId)) {
            try {
                refreshAudioManagementFromSegments(parentId);
            } catch (Exception e) {
                log.error("移动成功后汇总回写 audio_management 失败 parent_id={}", parentId, e);
            }
        }
    }

    private static String fileNameWithoutExtension(String fileName) {
        int dot = fileName.lastIndexOf('.');
        return dot < 0 ? fileName : fileName.substring(0, dot);
    }

    private static String readTranscriptTextFile(Path path) throws IOException {
        byte[] bytes = Files.readAllBytes(path);
        return StandardCharsets.UTF_8.newDecoder()
                .onMalformedInput(CodingErrorAction.REPLACE)
                .onUnmappableCharacter(CodingErrorAction.REPLACE)
                .decode(ByteBuffer.wrap(bytes))
                .toString();
    }

    private void refreshAudioManagementFromSegments(String parentId) {
        List<AudioManagementSegments> rows = audioManagementSegmentsMapper.listRecordingPartsByParentId(parentId);
        List<String> parts = new ArrayList<>();
        BigDecimal durationTotal = BigDecimal.ZERO;
        for (AudioManagementSegments row : rows) {
            String rt = row.getRecordingText();
            if (rt != null && !rt.isBlank()) {
                parts.add(rt);
            }
            if (row.getDuration() != null) {
                durationTotal = durationTotal.add(row.getDuration());
            }
        }
        String recordingTextTotal = String.join("\n", parts);
        log.info("汇总回写 audio_management：id={}，子段数={}", parentId, rows.size());
        int um = audioManagementMapper.updateRecordingTextAndDurationByIdIgnoreTenant(
                recordingTextTotal, durationTotal, parentId);
        log.info("已汇总回写 audio_management id={}，子段数={}，影响行数={}，duration_total={}",
                parentId, rows.size(), um, durationTotal);
    }

    /**
     * 每 5 分钟：将仅有本地路径、尚未写入 MinIO URL 的录音分段补传到 Minio 并回写 audio_file_url。
     */
    @Scheduled(fixedRate = 300000)
    public void uploadLocalSegmentFilesToMinio() {
        if (!appConfig.getScheduler().isStart()) {
            return;
        }
        if (System.getProperty("os.name", "").toLowerCase(Locale.ROOT).contains("win")) {
            log.info("uploadLocalSegmentFilesToMinio 跳过：Windows 环境（该任务仅在 Linux 服务器访问本地分段路径）");
            return;
        }
        try {
            List<AudioManagementSegments> pending = audioManagementSegmentsService.listSegmentsNeedingMinioUpload(50);
            if (pending.isEmpty()) {
                return;
            }
            for (AudioManagementSegments segment : pending) {
                uploadOneSegmentFileToMinio(segment);
            }
        } catch (Exception e) {
            log.error("录音分段补传 MinIO 定时任务执行失败", e);
        }
    }

    private void uploadOneSegmentFileToMinio(AudioManagementSegments segment) {
        String pathStr = segment.getAudioFilePath();
        Path path = Paths.get(pathStr);
        if (!Files.isRegularFile(path)) {
            log.warn("分段本地文件不存在，跳过: id={}, path={}", segment.getId(), pathStr);
            return;
        }
        String originalName = segment.getAudioFileOriginalName();
        if (!StringUtils.hasText(originalName)) {
            originalName = path.getFileName().toString();
        }
        String objectName = UUID.randomUUID().toString().replace("-", "") + originalName;
        String contentType;
        try {
            contentType = Files.probeContentType(path);
        } catch (Exception e) {
            contentType = null;
        }
        if (!StringUtils.hasText(contentType)) {
            contentType = "application/octet-stream";
        }
        try (FileInputStream in = new FileInputStream(path.toFile())) {
            String minioUrl = minIOService.uploadFile(in, objectName, contentType);
            boolean ok = audioManagementSegmentsService.updateAudioFileUrlByIdIgnoreTenant(segment.getId(), minioUrl);
            if (!ok) {
                log.warn("补传成功但更新 audio_file_url 未生效: id={}", segment.getId());
            } else {
                log.info("分段已补传 MinIO: id={}", segment.getId());
            }
        } catch (Exception e) {
            log.error("分段补传 MinIO 失败: id={}, path={}", segment.getId(), pathStr, e);
        }
    }

    /**
     * 每天凌晨2点执行门店录音统计
     * 统计昨天的录音数据
     */
    @Scheduled(cron = "0 0 2 * * ?")
    public void generateDailyAudioStatistics() {
        try {
            // 检查调度器是否启用
            if (!appConfig.getScheduler().isStart()) {
                log.info("调度器未启用，跳过门店录音统计任务执行");
                return;
            }

            log.info("开始执行门店录音统计任务...");

            LocalDate yesterday = LocalDate.now().minusDays(1);
            int count = audioStatisticsService.generateStatisticsByDate(yesterday);

            log.info("门店录音统计任务执行完成，统计日期：{}，生成记录数：{}", yesterday, count);
        } catch (Exception e) {
            log.error("门店录音统计任务执行失败", e);
        }
    }

    /**
     * 手动触发统计任务（用于测试或补录数据）
     * @param date 要统计的日期
     * @return 生成的记录数
     */
    public int manualGenerateStatistics(LocalDate date) {
        try {
            log.info("手动执行门店录音统计任务，统计日期：{}", date);
            int count = audioStatisticsService.generateStatisticsByDate(date);
            log.info("手动门店录音统计任务执行完成，统计日期：{}，生成记录数：{}", date, count);
            return count;
        } catch (Exception e) {
            log.error("手动门店录音统计任务执行失败，统计日期：{}", date, e);
            throw e;
        }
    }

}































































