package study.datasched;

import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;

import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.OffsetDateTime;

/**
 * 有界教学任务集。每个 handler 都把「进入/结束/失败」写成 marker 文件，
 * run.py 用它和执行器数量核对真实执行次数，而不是只看 admin 的日志行。
 */
public final class LabJobs {

    private final Path markerDir;

    public LabJobs(Path markerDir) {
        this.markerDir = markerDir;
    }

    /** 记录型任务：写 marker 后成功；param 作为标签。 */
    @XxlJob("u85Record")
    public void record() throws IOException {
        String param = XxlJobHelper.getJobParam();
        append("record.log", line("record", param));
        XxlJobHelper.log("U85 record executed: " + param);
        XxlJobHelper.handleSuccess("record-ok:" + param);
    }

    /** 允许失败 N 次后成功；按 key 统计历史失败次数。 */
    @XxlJob("u85FailThenSucceed")
    public void failThenSucceed() throws IOException {
        String param = XxlJobHelper.getJobParam();
        String key = arg(param, "key", "default");
        int failTimes = Integer.parseInt(arg(param, "failTimes", "1"));
        int previous = incrementAndGet("failcount-" + key + ".txt");
        if (previous <= failTimes) {
            append("failure.log", line("fail", key + ":attempt" + previous));
            XxlJobHelper.handleFail("planned-failure attempt=" + previous + " key=" + key);
            return;
        }
        append("record.log", line("record", key + ":attempt" + previous));
        XxlJobHelper.handleSuccess("recovered-after-" + (previous - 1) + "-failures");
    }

    /** 长任务：sleep 指定毫秒；可被中断。 */
    @XxlJob("u85Sleep")
    public void sleep() throws Exception {
        long millis = Long.parseLong(arg(XxlJobHelper.getJobParam(), "millis", "3000"));
        append("start.log", line("start", XxlJobHelper.getJobParam()));
        try {
            Thread.sleep(millis);
            append("finish.log", line("finish", XxlJobHelper.getJobParam()));
            XxlJobHelper.handleSuccess("slept-" + millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            append("interrupted.log", line("interrupted", XxlJobHelper.getJobParam()));
            XxlJobHelper.handleFail("interrupted");
            throw e;
        }
    }

    /** 硬退出：模拟执行器进程崩溃（无回调、无注销）。 */
    @XxlJob("u85Crash")
    public void crash() throws IOException {
        append("crash.log", line("crash", XxlJobHelper.getJobParam()));
        System.out.println("U85OBSERVE executor-crash jobParam=" + XxlJobHelper.getJobParam());
        Runtime.getRuntime().halt(17);
    }

    private static String arg(String param, String name, String fallback) {
        if (param == null) {
            return fallback;
        }
        for (String part : param.split("&")) {
            String[] kv = part.split("=", 2);
            if (kv.length == 2 && kv[0].trim().equals(name)) {
                return kv[1].trim();
            }
        }
        return fallback;
    }

    private static String line(String kind, String detail) {
        return kind + "|" + OffsetDateTime.now() + "|jobId=" + XxlJobHelper.getJobId()
                + "|logId=" + XxlJobHelper.getLogId() + "|" + detail + "\n";
    }

    private void append(String file, String content) throws IOException {
        Files.writeString(markerDir.resolve(file), content,
                StandardOpenOption.CREATE, StandardOpenOption.APPEND);
    }

    private int incrementAndGet(String file) throws IOException {
        Path path = markerDir.resolve(file);
        try (FileChannel channel = FileChannel.open(path, StandardOpenOption.CREATE,
                StandardOpenOption.READ, StandardOpenOption.WRITE);
             FileLock ignored = channel.lock()) {
            String current = Files.readString(path).trim();
            int next = current.isEmpty() ? 1 : Integer.parseInt(current) + 1;
            channel.truncate(0);
            channel.write(java.nio.ByteBuffer.wrap(String.valueOf(next).getBytes()));
            return next;
        }
    }
}
