import java.util.*;
public class AudioProbe {
 static final String ROW_WORD="word", ROW_PHRASE="phrase", ROW_VARIANT="variant"; static final Object SYSTEM_OPERATOR=new Object();
 static class StringUtils { static boolean isBlank(String x){return x==null||x.isBlank();} }
 static class Log {void warn(String s,Object... x){} void info(String s,Object...x){}} Log log=new Log();
 static class TtsTaskStatus {String status="completed";boolean isRunning(){return "running".equals(status);} boolean isCompleted(){return "completed".equals(status);}String getStatus(){return status;}String getError(){return "x";}}
 static class Tts {boolean error;TtsTaskStatus queryTask(String id){if(error)throw new RuntimeException();return new TtsTaskStatus();}void cancelTask(String id){}}
 Tts internalTtsClient=new Tts();
 static class Audio {Long saveAudio(String task){return null;}} Audio englishAudioBizService=new Audio();
 static class Config {int getTtsAudioTaskBatchSize(){return 100;}long getTtsAudioTaskTimeoutSeconds(){return 1800;}} Config englishDictConfig=new Config();
 static class EnglishAudioBackfillResultBo {int scanned,running,failed,completed;int getScanned(){return scanned;}void setScanned(int v){scanned=v;} int getRunning(){return running;}void setRunning(int v){running=v;}int getFailed(){return failed;}void setFailed(int v){failed=v;}int getCompleted(){return completed;}void setCompleted(int v){completed=v;}}
 static class EnglishWordDo {Long audio;Long getId(){return 1L;}String getAudioTaskId(){return "old-task";}Long getAudioTaskTime(){return 1L;}Long getAudioResourceId(){return audio;}Integer getHasPublished(){return 1;}}
 static class EnglishPhraseDo extends EnglishWordDo {} static class EnglishPhraseVariantDo extends EnglishWordDo {Long getPhraseId(){return 2L;}}
 static class Words {EnglishWordDo row;List<EnglishWordDo> listAudioInFlight(int b){return row==null?List.of():List.of(row);}} Words englishWordCoreService=new Words();
 static class Phrases {List<EnglishPhraseDo> listAudioInFlight(int b){return List.of();}} Phrases englishPhraseCoreService=new Phrases();
 static class Variants {List<EnglishPhraseVariantDo> listAudioInFlight(int b){return List.of();}} Variants englishPhraseVariantCoreService=new Variants();
 static class Publish {int calls;void publishWord(Long id,Object op){calls++;}void publishPhrase(Long id,Object op){calls++;}} Publish englishPublishBizService=new Publish();
 int clears;void markCleared(String t,Long id){clears++;}boolean markCompleted(String t,Long id,Long resource){throw new AssertionError();}void publishReleasedPhrases(Set<Long> x){if(!x.isEmpty())throw new AssertionError();}long nowSeconds(){return 10000;}
    private boolean handleInFlight(String table, Long id, String taskId, Long submitTime,
                                   Long currentAudioResourceId, long now, long timeoutSeconds,
                                   EnglishAudioBackfillResultBo result) {
        if (id == null || StringUtils.isBlank(taskId)) {
            return false;
        }
        result.setScanned(result.getScanned() + 1);
        TtsTaskStatus status;
        try {
            status = internalTtsClient.queryTask(taskId);
        } catch (Exception e) {
            // 查状态失败按「还没好」处理：下轮再查，不在这里清任务号
            log.warn("音频任务状态查询失败, row={}:{}, taskId={}", table, id, taskId, e);
            result.setRunning(result.getRunning() + 1);
            return false;
        }

        if (status == null || status.isRunning()) {
            long elapsed = submitTime == null ? Long.MAX_VALUE : now - submitTime;
            if (elapsed >= timeoutSeconds) {
                // 平台丢任务或长时间不出结果：先取消平台任务，再清本地在途标记，否则该行永远挂着「生成中」
                internalTtsClient.cancelTask(taskId);
                markCleared(table, id);
                result.setFailed(result.getFailed() + 1);
                log.warn("音频任务超时清理, row={}:{}, taskId={}, elapsed={}s", table, id, taskId, elapsed);
            } else {
                result.setRunning(result.getRunning() + 1);
            }
            return false;
        }

        if (status.isCompleted()) {
            // 人工优先：在途期间运营可能已上传了音频，这时只清任务号，既不下载也不覆盖
            if (currentAudioResourceId != null && currentAudioResourceId > 0) {
                markCleared(table, id);
                result.setCompleted(result.getCompleted() + 1);
                log.info("音频任务完成但该行已有音频，保留人工音频并清任务号, row={}:{}", table, id);
                return true;
            }
            Long resourceId = englishAudioBizService.saveAudio(taskId);
            if (resourceId != null) {
                if (!markCompleted(table, id, resourceId)) {
                    // 条件更新未命中：写完前的一瞬间人工补了音频（并发窗口），同样只清任务号，不回写
                    markCleared(table, id);
                    log.info("音频回填时发现该行已被人工补齐，保留人工音频, row={}:{}", table, id);
                }
                result.setCompleted(result.getCompleted() + 1);
                return true;
            }
            // 下载或落库失败（未就绪/源文件过期/校验失败）：保留任务号，由超时清理最终收口，
            // 不在这里清掉——清掉就意味着这个词再也不会自动补音频
            result.setFailed(result.getFailed() + 1);
            log.warn("音频下载或保存失败，保留任务号待下轮, row={}:{}, taskId={}", table, id, taskId);
            return false;
        }

        // failed / cancelled：终态失败，清掉在途标记，音频留空（运营仍可手工上传）
        markCleared(table, id);
        result.setFailed(result.getFailed() + 1);
        log.warn("音频任务终态失败, row={}:{}, taskId={}, status={}, error={}",
            table, id, taskId, status.getStatus(), status.getError());
        return false;
    }
    private void queryInFlight(int batch, EnglishAudioBackfillResultBo result) {
        long now = nowSeconds();
        long timeoutSeconds = Math.max(60L, englishDictConfig.getTtsAudioTaskTimeoutSeconds());
        for (EnglishWordDo row : englishWordCoreService.listAudioInFlight(batch)) {
            boolean filled = handleInFlight(ROW_WORD, row.getId(), row.getAudioTaskId(),
                row.getAudioTaskTime(), row.getAudioResourceId(), now, timeoutSeconds, result);
            if (filled && isPublished(row.getHasPublished())) {
                publishReleased(ROW_WORD, row.getId());
            }
        }
        for (EnglishPhraseDo row : englishPhraseCoreService.listAudioInFlight(batch)) {
            boolean filled = handleInFlight(ROW_PHRASE, row.getId(), row.getAudioTaskId(),
                row.getAudioTaskTime(), row.getAudioResourceId(), now, timeoutSeconds, result);
            if (filled && isPublished(row.getHasPublished())) {
                publishReleased(ROW_PHRASE, row.getId());
            }
        }
        // 变形没有自己的发布入口（随短语发布）：先收集本轮回填成功的短语，最后一起去重后再发
        Set<Long> filledPhraseIds = new LinkedHashSet<>();
        for (EnglishPhraseVariantDo row : englishPhraseVariantCoreService.listAudioInFlight(batch)) {
            boolean filled = handleInFlight(ROW_VARIANT, row.getId(), row.getAudioTaskId(),
                row.getAudioTaskTime(), row.getAudioResourceId(), now, timeoutSeconds, result);
            if (filled && row.getPhraseId() != null) {
                filledPhraseIds.add(row.getPhraseId());
            }
        }
        publishReleasedPhrases(filledPhraseIds);
    }
    private void publishReleased(String table, Long id) {
        try {
            if (ROW_WORD.equals(table)) {
                englishPublishBizService.publishWord(id, SYSTEM_OPERATOR);
            } else if (ROW_PHRASE.equals(table)) {
                englishPublishBizService.publishPhrase(id, SYSTEM_OPERATOR);
            }
        } catch (Exception e) {
            // 补发布失败不回滚音频回填：音频已经在教研库，运营下次发布或再跑一轮仍会带上；
            // 这里只留痕，避免因为一次锁竞争把已成功的回填也标记成失败
            log.warn("音频回填后补发布失败, row={}:{}", table, id, e);
        }
    }
    private boolean isPublished(Integer hasPublished) {
        return Integer.valueOf(1).equals(hasPublished);
    }
 public static void main(String[] args){
 AudioProbe a=new AudioProbe(); EnglishAudioBackfillResultBo r=new EnglishAudioBackfillResultBo();
 boolean done=a.handleInFlight("word",1L,"task",1L,null,10000,1800,r);
 System.out.println("completed_download_null elapsed=9999 done="+done+" clears="+a.clears+" failed="+r.failed);
 if(done||a.clears!=0||r.failed!=1)throw new AssertionError();
 a.internalTtsClient.error=true;r=new EnglishAudioBackfillResultBo();a.handleInFlight("word",1L,"task",1L,null,10000,1800,r);
 System.out.println("query_exception elapsed=9999 clears="+a.clears+" running="+r.running);
 if(a.clears!=0||r.running!=1)throw new AssertionError();
 a.internalTtsClient.error=false;EnglishWordDo row=new EnglishWordDo();row.audio=88L;a.englishWordCoreService.row=row;
 a.queryInFlight(100,new EnglishAudioBackfillResultBo());
 System.out.println("manual_audio hasPublished=1 clears="+a.clears+" publishCalls="+a.englishPublishBizService.calls);
 if(a.clears!=1||a.englishPublishBizService.calls!=1)throw new AssertionError();
 }
}
