package com.easyagents.scheduler;

import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;

/**
 * 与具体调度实现无关的任务定义。
 *
 * @param id 稳定任务标识
 * @param handlerCode 任务处理器代码
 * @param plan 调度计划
 * @param misfirePolicy 错过触发后的处理策略
 * @param concurrencyPolicy 同一任务的并发策略
 * @param recoverOnNodeFailure 执行节点故障后是否请求恢复执行
 * @param parameters 只包含字符串的不可变任务参数
 * @param description 可选任务说明
 */
public record ScheduleDefinition(
    ScheduleId id,
    String handlerCode,
    SchedulePlan plan,
    MisfirePolicy misfirePolicy,
    ConcurrencyPolicy concurrencyPolicy,
    boolean recoverOnNodeFailure,
    Map<String, String> parameters,
    String description
) {

    /** 参数键和值合计允许的默认最大 UTF-8 字节数。 */
    public static final int MAX_PARAMETER_BYTES = 16 * 1024;

    /** Handler Code 最大长度。 */
    public static final int MAX_HANDLER_CODE_LENGTH = 190;

    /** 任务说明最大长度，与 Quartz 官方表结构保持一致。 */
    public static final int MAX_DESCRIPTION_LENGTH = 250;

    /**
     * 校验并创建任务定义。
     *
     * @throws ScheduleException 参数不合法时抛出
     * @throws IllegalArgumentException Handler 或说明不合法时抛出
     * @throws NullPointerException 必填模型为空时抛出
     */
    public ScheduleDefinition {
        id = Objects.requireNonNull(id, "id must not be null");
        plan = Objects.requireNonNull(plan, "plan must not be null");
        misfirePolicy = Objects.requireNonNull(misfirePolicy, "misfirePolicy must not be null");
        concurrencyPolicy = Objects.requireNonNull(concurrencyPolicy, "concurrencyPolicy must not be null");
        handlerCode = normalizeRequired(handlerCode, "handlerCode", MAX_HANDLER_CODE_LENGTH);
        description = description == null ? "" : description.trim();
        if (description.length() > MAX_DESCRIPTION_LENGTH) {
            throw new IllegalArgumentException(
                "description length must not exceed " + MAX_DESCRIPTION_LENGTH
            );
        }
        parameters = copyAndValidateParameters(parameters);
    }

    /**
     * 防御复制并校验字符串参数。
     *
     * @param parameters 待校验参数，可为空
     * @return 不可变参数副本
     * @throws ScheduleException 参数键为空、值为空或总大小超限时抛出
     */
    public static Map<String, String> copyAndValidateParameters(Map<String, String> parameters) {
        if (parameters == null || parameters.isEmpty()) {
            return Map.of();
        }
        int totalBytes = 0;
        for (Map.Entry<String, String> entry : parameters.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            if (key == null || key.isBlank()) {
                throw invalidParameters("parameter key must not be blank");
            }
            if (value == null) {
                throw invalidParameters("parameter value must not be null: " + key);
            }
            totalBytes += key.getBytes(StandardCharsets.UTF_8).length;
            totalBytes += value.getBytes(StandardCharsets.UTF_8).length;
            if (totalBytes > MAX_PARAMETER_BYTES) {
                throw invalidParameters(
                    "parameter size must not exceed " + MAX_PARAMETER_BYTES + " UTF-8 bytes"
                );
            }
        }
        return Map.copyOf(parameters);
    }

    /**
     * 构造参数非法异常。
     *
     * @param message 非法原因
     * @return 携带稳定错误码的异常
     */
    private static ScheduleException invalidParameters(String message) {
        return new ScheduleException(ScheduleErrorCode.INVALID_DEFINITION, message);
    }

    /**
     * 规范化必填字符串。
     *
     * @param value 原始值
     * @param field 字段名
     * @param maxLength 最大长度
     * @return 规范化值
     */
    private static String normalizeRequired(String value, String field, int maxLength) {
        if (value == null || value.isBlank()) {
            throw new IllegalArgumentException(field + " must not be blank");
        }
        String normalized = value.trim();
        if (normalized.length() > maxLength) {
            throw new IllegalArgumentException(field + " length must not exceed " + maxLength);
        }
        return normalized;
    }
}
