import java.util.*;
public class AudioRegressionProbe {
 static final String ROW_WORD="word",ROW_PHRASE="phrase",ROW_VARIANT="variant";
 static class StringUtils{static boolean isBlank(String s){return s==null||s.isBlank();}}
 static class Log {void warn(String s,Object...v){}void info(String s,Object...v){}} Log log=new Log();
 static class TtsTaskStatus{String s;TtsTaskStatus(String x){s=x;}boolean isRunning(){return s.equals("running");}boolean isCompleted(){return s.equals("completed");}String getStatus(){return s;}String getError(){return "failure";}}
 static class Tts {boolean error,cancelError;String state="completed";int queries;TtsTaskStatus queryTask(String id){queries++;if(error)throw new RuntimeException("query");return new TtsTaskStatus(state);}void cancelTask(String id){if(cancelError)throw new RuntimeException("cancel");}}
 static class Audio{Long resource;Long saveAudio(String s){return resource;}}
 Tts internalTtsClient=new Tts();Audio englishAudioBizService=new Audio();
 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;}}
 int cleared,failCount;void markCleared(String t,Long id){cleared++;}boolean markCompleted(String t,Long id,Long res){return true;}void increaseFailCount(String k){failCount++;}void clearFailCount(String k){failCount=0;}
    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);
        String rowKey = table + ":" + id;
        long elapsed = submitTime == null ? Long.MAX_VALUE : Math.max(0L, now - submitTime);
        if (elapsed >= timeoutSeconds) {
            // 总生命周期预算适用于查询异常、completed 下载失败等全部分支；取消失败也必须本地收口。
            try { internalTtsClient.cancelTask(taskId); }
            catch (Exception e) { log.warn("取消超时音频任务失败, taskId={}", taskId, e); }
            increaseFailCount(rowKey);
            markCleared(table, id);
            result.setFailed(result.getFailed() + 1);
            return false;
        }
        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()) {
            result.setRunning(result.getRunning() + 1);
            return false;
        }

        if (status.isCompleted()) {
            // 人工优先：在途期间运营可能已上传了音频，这时只清任务号，既不下载也不覆盖
            if (currentAudioResourceId != null && currentAudioResourceId > 0) {
                markCleared(table, id);
                clearFailCount(rowKey);
                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);
                }
                clearFailCount(rowKey);
                result.setCompleted(result.getCompleted() + 1);
                return true;
            }
            // 下载或落库失败（未就绪/源文件过期/校验失败）：保留任务号，由超时清理最终收口，
            // 不在这里清掉——清掉就意味着这个词再也不会自动补音频
            result.setFailed(result.getFailed() + 1);
            log.warn("音频下载或保存失败，保留任务号待下轮, row={}:{}, taskId={}", table, id, taskId);
            return false;
        }

        // 整个尝试失败才累计，成功 submit 不重置计数。
        increaseFailCount(rowKey);
        markCleared(table, id);
        result.setFailed(result.getFailed() + 1);
        log.warn("音频任务终态失败, row={}:{}, taskId={}, status={}, error={}",
            table, id, taskId, status.getStatus(), status.getError());
        return false;
    }

static int failures;static void check(boolean ok,String name){System.out.println((ok?"PASS ":"FAIL ")+name);if(!ok)failures++;}
 public static void main(String[]x){
 for(String mode:List.of("completed","query-error","cancel-error","running")){
 var a=new AudioRegressionProbe();a.internalTtsClient.error=mode.equals("query-error");a.internalTtsClient.cancelError=mode.equals("cancel-error");if(mode.equals("running"))a.internalTtsClient.state="running";
 var r=new EnglishAudioBackfillResultBo();try{a.handleInFlight("word",1L,"task",1L,null,10000,1800,r);}catch(Exception e){}
 check(a.cleared==1&&a.failCount==1&&r.failed==1,"timeout all branches "+mode);
 }
 var a=new AudioRegressionProbe();a.internalTtsClient.state="failed";var r=new EnglishAudioBackfillResultBo();a.handleInFlight("word",1L,"task",9999L,null,10000,1800,r);check(a.failCount==1&&a.cleared==1,"terminal failure counts lifecycle");
 a=new AudioRegressionProbe();a.failCount=2;a.englishAudioBizService.resource=88L;r=new EnglishAudioBackfillResultBo();check(a.handleInFlight("word",1L,"task",9999L,null,10000,1800,r)&&a.failCount==0,"completion resets lifecycle failure count");
 a=new AudioRegressionProbe();r=new EnglishAudioBackfillResultBo();a.handleInFlight("word",1L,"task",9999L,null,10000,1800,r);check(a.cleared==0,"temporary completed download failure retains task within budget");
 if(failures>0)System.exit(1);
 }}