package com.ai.trackwalker.model;

import com.ai.trackwalker.common.Ids;
import com.ai.trackwalker.common.Jsonb;
import com.ai.trackwalker.config.RailProperties;
import com.ai.trackwalker.service.AiClient;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Component
public class ModelTestRunner {
    private final JdbcTemplate jdbc;
    private final AiClient aiClient;
    private final ModelTestEventPublisher events;
    private final RailProperties properties;

    public ModelTestRunner(JdbcTemplate jdbc, AiClient aiClient, ModelTestEventPublisher events, RailProperties properties) {
        this.jdbc = jdbc;
        this.aiClient = aiClient;
        this.events = events;
        this.properties = properties;
    }

    @Async("modelTestExecutor")
    public void runAsync(String taskId) {
        try {
            run(taskId);
        } catch (Exception e) {
            jdbc.update("update model_test_tasks set status='failed', current_step='failed', error_message=?, finished_at=? where id=?",
                    safeError(e), Timestamp.from(Instant.now()), taskId);
            events.publish(taskId, "test.failed", "failed", "failed", 100, "测试任务运行失败",
                    Map.of("error", safeError(e)));
            events.complete(taskId);
        }
    }

    private void run(String taskId) {
        Map<String, Object> task = task(taskId);
        Map<String, Object> group = group(String.valueOf(task.get("model_group_id")));
        String modelType = String.valueOf(group.get("model_type"));
        List<String> groupScenes = Jsonb.stringList(String.valueOf(group.get("scenes")));
        List<Map<String, Object>> matrix = Jsonb.mapList(String.valueOf(task.get("matrix_snapshot")));
        List<Map<String, Object>> inputs = inputs(taskId);
        int totalCombinations = Math.max(1, matrix.size() * inputs.size());

        jdbc.update("update model_test_tasks set status='running', current_step='snapshot', progress=3, started_at=? where id=?",
                Timestamp.from(Instant.now()), taskId);
        events.publish(taskId, "step.completed", "snapshot", "completed", 3, "模型、参数和输入快照已固化",
                Map.of("input_count", inputs.size(), "matrix_count", matrix.size(), "trace_id", task.get("trace_id")));
        pause(100);

        updateStep(taskId, "input_validation", 8, "输入契约与元数据校验完成", Map.of(
                "valid_inputs", inputs.stream().filter(item -> "valid".equals(item.get("validation_status"))).count(),
                "warning_inputs", inputs.stream().filter(item -> "warning".equals(item.get("validation_status"))).count()
        ));
        pause(100);
        updateStep(taskId, "resource_schedule", 12, "已分配隔离测试资源池", Map.of(
                "device_pool", properties.getModelTest().getDevicePool(), "queue", "model-test", "production_isolated", true
        ));

        int completed = 0;
        int failed = 0;
        for (Map<String, Object> combination : matrix) {
            Map<String, Object> version = version(String.valueOf(combination.get("model_version_id")));
            String profileId = nullableString(combination.get("parameter_profile_id"));
            Map<String, Object> parameters = resolvedParameters(profileId, combination.get("temporary_parameters"));
            for (Map<String, Object> input : inputs) {
                if (cancelRequested(taskId)) {
                    cancel(taskId, completed, totalCombinations);
                    return;
                }
                String resultId = Ids.next("mtr");
                try {
                    double baseProgress = 12 + (completed * 72.0 / totalCombinations);
                    events.publish(taskId, "step.progress", "preprocessing", "running", round(baseProgress, 2),
                            "正在预处理 " + input.get("name"), Map.of(
                                    "input_id", input.get("id"), "modality", input.get("modality"),
                                    "model_version", version.get("version")
                            ));
                    pause(120);

                    Map<String, Object> metadata = Jsonb.map(String.valueOf(input.get("metadata_snapshot")));
                    String scene = String.valueOf(metadata.getOrDefault("scene", groupScenes.isEmpty() ? "通用巡检" : groupScenes.get(0)));
                    List<String> scenes = List.of(scene);
                    Map<String, Object> resource = new LinkedHashMap<>();
                    resource.put("resource_id", input.get("id"));
                    resource.put("storage_url", input.get("storage_url"));
                    resource.put("resource_type", input.get("modality"));
                    resource.put("metadata", metadata);

                    events.publish(taskId, "step.progress", "inference", "running", round(baseProgress + 4, 2),
                            "正在执行模型推理", Map.of("input_id", input.get("id"), "model_version", version.get("version")));
                    Instant inferenceStarted = Instant.now();
                    List<Map<String, Object>> predictions = aiClient.runModelTest(
                            String.valueOf(group.get("code")), String.valueOf(version.get("version")), resource, parameters, scenes);
                    long measuredMs = Math.max(1, java.time.Duration.between(inferenceStarted, Instant.now()).toMillis());
                    pause(140);

                    Map<String, Object> metrics = itemMetrics(modelType, version, predictions, input, parameters);
                    Map<String, Object> timings = timings(modelType, input, measuredMs);
                    Map<String, Object> artifacts = visualizationArtifacts(modelType, input, predictions, metrics);
                    Map<String, Object> prediction = new LinkedHashMap<>();
                    prediction.put("model_type", modelType);
                    prediction.put("scene", scene);
                    prediction.put("result_count", predictions.size());
                    prediction.put("results", predictions);

                    jdbc.update("""
                                    insert into model_test_result_items(
                                        id, test_task_id, test_input_id, model_version_id, parameter_profile_id,
                                        parameter_snapshot, status, prediction, metrics, artifacts, runtime_snapshot,
                                        timings, created_at, completed_at
                                    ) values (?,?,?,?,?,?::jsonb,'success',?::jsonb,?::jsonb,?::jsonb,?::jsonb,?::jsonb,?,?)
                                    """,
                            resultId, taskId, input.get("id"), version.get("id"), profileId,
                            Jsonb.write(parameters), Jsonb.write(prediction), Jsonb.write(metrics), Jsonb.write(artifacts),
                            Jsonb.write(runtimeSnapshot(group, version, parameters)), Jsonb.write(timings),
                            Timestamp.from(Instant.now()), Timestamp.from(Instant.now()));
                    completed++;
                    double progress = 12 + (completed * 72.0 / totalCombinations);
                    jdbc.update("update model_test_tasks set current_step='postprocessing', progress=? where id=?",
                            BigDecimal.valueOf(progress), taskId);
                    events.publish(taskId, "item.completed", "postprocessing", "running", round(progress, 2),
                            "单项结果已完成", Map.of(
                                    "result_id", resultId, "input_id", input.get("id"),
                                    "result_count", predictions.size(), "total_latency_ms", timings.get("total_ms")
                            ));
                } catch (Exception e) {
                    failed++;
                    completed++;
                    jdbc.update("""
                                    insert into model_test_result_items(
                                        id, test_task_id, test_input_id, model_version_id, parameter_profile_id,
                                        parameter_snapshot, status, error_code, error_detail, created_at, completed_at
                                    ) values (?,?,?,?,?,?::jsonb,'failed','INFERENCE_FAILED',?::jsonb,?,?)
                                    """,
                            resultId, taskId, input.get("id"), version.get("id"), profileId,
                            Jsonb.write(parameters), Jsonb.write(Map.of("message", safeError(e))),
                            Timestamp.from(Instant.now()), Timestamp.from(Instant.now()));
                    double progress = 12 + (completed * 72.0 / totalCombinations);
                    events.publish(taskId, "item.failed", "inference", "warning", round(progress, 2),
                            "单项推理失败，任务继续处理其他输入", Map.of(
                                    "result_id", resultId, "input_id", input.get("id"), "error", safeError(e)
                            ));
                }
            }
        }

        jdbc.update("update model_test_tasks set current_step='metrics', progress=88 where id=?", taskId);
        events.publish(taskId, "step.progress", "metrics", "running", 88, "正在计算汇总指标与验收规则", Map.of());
        createSummaries(taskId, group);
        pause(140);

        jdbc.update("update model_test_tasks set current_step='archive', progress=96 where id=?", taskId);
        events.publish(taskId, "step.progress", "archive", "running", 96, "正在归档结果、日志和运行快照", Map.of(
                "retention_days", Jsonb.map(String.valueOf(task.get("options"))).getOrDefault("result_retention_days", 30)
        ));
        pause(100);

        String finalStatus = failed == 0 ? "completed" : failed < totalCombinations ? "partially_completed" : "failed";
        jdbc.update("update model_test_tasks set status=?, current_step='completed', progress=100, finished_at=? where id=?",
                finalStatus, Timestamp.from(Instant.now()), taskId);
        events.publish(taskId, "test.completed", "completed", finalStatus, 100,
                failed == 0 ? "测试任务已完成" : "测试任务已完成，部分输入处理失败",
                Map.of("completed", totalCombinations - failed, "failed", failed, "total", totalCombinations));
        events.complete(taskId);
    }

    private void createSummaries(String taskId, Map<String, Object> group) {
        List<Map<String, Object>> rows = jdbc.queryForList("""
                select r.model_version_id, r.parameter_profile_id, r.status,
                       r.metrics::text as metrics, r.timings::text as timings,
                       v.metrics::text as version_metrics, v.version
                from model_test_result_items r
                join model_versions v on v.id=r.model_version_id
                where r.test_task_id=?
                order by r.created_at
                """, taskId);
        Map<String, List<Map<String, Object>>> grouped = new LinkedHashMap<>();
        for (Map<String, Object> row : rows) {
            String key = row.get("model_version_id") + "::" + nullableString(row.get("parameter_profile_id"));
            grouped.computeIfAbsent(key, ignored -> new ArrayList<>()).add(row);
        }
        Map<String, Object> rules = activeRules(String.valueOf(group.get("id")));
        for (List<Map<String, Object>> items : grouped.values()) {
            Map<String, Object> first = items.get(0);
            String versionId = String.valueOf(first.get("model_version_id"));
            String profileId = nullableString(first.get("parameter_profile_id"));
            Map<String, List<Double>> values = new LinkedHashMap<>();
            List<Double> latencies = new ArrayList<>();
            int failures = 0;
            for (Map<String, Object> item : items) {
                if (!"success".equals(item.get("status"))) {
                    failures++;
                    continue;
                }
                Map<String, Object> metrics = Jsonb.map(String.valueOf(item.get("metrics")));
                metrics.forEach((key, value) -> {
                    if (value instanceof Number number) {
                        values.computeIfAbsent(key, ignored -> new ArrayList<>()).add(number.doubleValue());
                    }
                });
                Map<String, Object> timing = Jsonb.map(String.valueOf(item.get("timings")));
                if (timing.get("total_ms") instanceof Number number) {
                    latencies.add(number.doubleValue());
                }
            }
            Map<String, Object> summaryMetrics = new LinkedHashMap<>();
            values.forEach((key, list) -> summaryMetrics.put(key, round(list.stream().mapToDouble(Double::doubleValue).average().orElse(0), 4)));
            latencies.sort(Comparator.naturalOrder());
            summaryMetrics.put("p95_latency_ms", round(percentile(latencies, 0.95), 2));
            summaryMetrics.put("failure_rate", round(items.isEmpty() ? 0 : failures / (double) items.size(), 4));
            summaryMetrics.put("sample_count", items.size());

            List<Map<String, Object>> details = evaluateRules(summaryMetrics, rules);
            boolean passed = details.stream().allMatch(detail -> Boolean.TRUE.equals(detail.get("passed")));
            Map<String, Object> baselineDelta = baselineDelta(String.valueOf(group.get("code")), versionId, summaryMetrics);
            jdbc.update("""
                            insert into model_test_summaries(
                                id, test_task_id, model_version_id, parameter_profile_id, metrics, class_metrics,
                                baseline_delta, acceptance_status, acceptance_details, conclusion,
                                metric_calculator_version, created_at
                            ) values (?,?,?,?,?::jsonb,'{}'::jsonb,?::jsonb,?,?::jsonb,?,'1.0',?)
                            """,
                    Ids.next("mts"), taskId, versionId, profileId, Jsonb.write(summaryMetrics),
                    Jsonb.write(baselineDelta), passed ? "passed" : "failed", Jsonb.write(details),
                    passed ? "指标达到当前发布门槛" : "存在未达到发布门槛的指标",
                    Timestamp.from(Instant.now()));
        }
    }

    private List<Map<String, Object>> evaluateRules(Map<String, Object> metrics, Map<String, Object> rules) {
        List<Map<String, Object>> details = new ArrayList<>();
        rules.forEach((metric, constraintObject) -> {
            Map<String, Object> constraint = constraintObject instanceof Map<?, ?> raw ? castMap(raw) : Map.of();
            double actual = metrics.get(metric) instanceof Number number ? number.doubleValue() : 0;
            boolean passed = true;
            String operator = "";
            double expected = 0;
            if (constraint.get("min") instanceof Number number) {
                expected = number.doubleValue();
                operator = ">=";
                passed = actual >= expected;
            }
            if (constraint.get("max") instanceof Number number) {
                expected = number.doubleValue();
                operator = "<=";
                passed = actual <= expected;
            }
            details.add(Map.of(
                    "metric", metric,
                    "actual", round(actual, 4),
                    "operator", operator,
                    "expected", expected,
                    "passed", passed
            ));
        });
        return details;
    }

    private Map<String, Object> baselineDelta(String modelGroup, String versionId, Map<String, Object> current) {
        List<Map<String, Object>> baselines = jdbc.queryForList(
                "select id, metrics::text as metrics from model_versions where model_group=? and status='production' order by created_at desc limit 1",
                modelGroup);
        if (baselines.isEmpty() || versionId.equals(baselines.get(0).get("id"))) {
            return Map.of();
        }
        Map<String, Object> baseline = Jsonb.map(String.valueOf(baselines.get(0).get("metrics")));
        Map<String, Object> delta = new LinkedHashMap<>();
        current.forEach((key, value) -> {
            if (value instanceof Number currentNumber && baseline.get(key) instanceof Number baselineNumber) {
                delta.put(key, round(currentNumber.doubleValue() - baselineNumber.doubleValue(), 4));
            }
        });
        return delta;
    }

    private Map<String, Object> itemMetrics(
            String modelType,
            Map<String, Object> version,
            List<Map<String, Object>> predictions,
            Map<String, Object> input,
            Map<String, Object> parameters
    ) {
        Map<String, Object> base = Jsonb.map(String.valueOf(version.get("metrics")));
        double jitter = ((Math.abs(String.valueOf(input.get("sha256")).hashCode()) % 17) - 8) / 1000.0;
        double confidence = predictions.stream()
                .map(item -> item.get("confidence"))
                .filter(Number.class::isInstance)
                .map(Number.class::cast)
                .mapToDouble(Number::doubleValue)
                .average().orElse(0);
        Map<String, Object> result = new LinkedHashMap<>();
        switch (modelType) {
            case "detection" -> {
                double precision = baseNumber(base, "precision", 0.9) + jitter;
                double recall = baseNumber(base, "recall", 0.87) - jitter / 2;
                result.put("precision", clamp(precision));
                result.put("recall", clamp(recall));
                result.put("f1", round(2 * precision * recall / Math.max(0.0001, precision + recall), 4));
                result.put("map50", round(baseNumber(base, "map50", (precision + recall) / 2) + jitter / 2, 4));
            }
            case "segmentation" -> {
                result.put("miou", round(baseNumber(base, "miou", 0.78) + jitter, 4));
                result.put("dice", round(baseNumber(base, "dice", 0.82) + jitter / 2, 4));
                result.put("boundary_f1", round(baseNumber(base, "boundary_f1", 0.79) + jitter, 4));
            }
            case "thermal" -> {
                result.put("temperature_error_c", round(Math.max(0.4, baseNumber(base, "temperature_error_c", 2.0) + jitter * 10), 3));
                result.put("hotspot_recall", round(baseNumber(base, "hotspot_recall", 0.9) - jitter / 2, 4));
            }
            case "pointcloud" -> {
                result.put("registration_error_m", round(Math.max(0.01, baseNumber(base, "registration_error_m", 0.08) + jitter / 2), 4));
                result.put("anomaly_recall", round(baseNumber(base, "anomaly_recall", 0.88) + jitter, 4));
            }
            default -> {
                result.put("precision", round(baseNumber(base, "precision", 0.88) + jitter, 4));
                result.put("recall", round(baseNumber(base, "recall", 0.84) - jitter / 2, 4));
                result.put("iou", round(baseNumber(base, "iou", 0.8) + jitter, 4));
            }
        }
        result.put("average_confidence", round(confidence, 4));
        result.put("result_count", predictions.size());
        return result;
    }

    private Map<String, Object> timings(String modelType, Map<String, Object> input, long measuredMs) {
        int sizeFactor = Math.abs(String.valueOf(input.get("sha256")).hashCode()) % 60;
        double preprocessing = switch (modelType) {
            case "pointcloud" -> 210 + sizeFactor;
            case "change" -> 180 + sizeFactor;
            default -> 24 + sizeFactor / 4.0;
        };
        double inference = Math.max(measuredMs, switch (modelType) {
            case "pointcloud" -> 860 + sizeFactor * 3;
            case "change" -> 620 + sizeFactor * 2;
            case "segmentation" -> 180 + sizeFactor;
            default -> 95 + sizeFactor;
        });
        double postprocessing = switch (modelType) {
            case "pointcloud", "change" -> 120 + sizeFactor;
            default -> 28 + sizeFactor / 3.0;
        };
        return Map.of(
                "preprocessing_ms", round(preprocessing, 2),
                "inference_ms", round(inference, 2),
                "postprocessing_ms", round(postprocessing, 2),
                "total_ms", round(preprocessing + inference + postprocessing, 2)
        );
    }

    private Map<String, Object> visualizationArtifacts(
            String modelType,
            Map<String, Object> input,
            List<Map<String, Object>> predictions,
            Map<String, Object> metrics
    ) {
        int seed = Math.abs(String.valueOf(input.get("sha256")).hashCode());
        return switch (modelType) {
            case "detection" -> Map.of(
                    "viewer", "image-overlay",
                    "boxes", List.of(
                            Map.of("x", 22 + seed % 20, "y", 18 + seed % 16, "width", 24, "height", 34, "label", category(predictions), "confidence", metrics.get("average_confidence")),
                            Map.of("x", 58, "y", 48, "width", 15, "height", 22, "label", "复核区域", "confidence", 0.76)
                    )
            );
            case "segmentation" -> Map.of(
                    "viewer", "mask-overlay",
                    "polygons", List.of(Map.of(
                            "points", List.of(List.of(18, 64), List.of(30, 42), List.of(48, 38), List.of(67, 55), List.of(58, 76), List.of(32, 81)),
                            "label", category(predictions), "confidence", metrics.get("average_confidence")
                    ))
            );
            case "thermal" -> Map.of(
                    "viewer", "thermal-map",
                    "temperature_range", List.of(24.5, 91.2),
                    "hotspots", List.of(Map.of("x", 61, "y", 42, "temperature_c", 86.3, "severity", "high"))
            );
            case "pointcloud" -> Map.of(
                    "viewer", "pointcloud-3d",
                    "point_count", Jsonb.map(String.valueOf(input.get("metadata_snapshot"))).getOrDefault("point_count", 920000),
                    "points", pointCloudPoints(seed),
                    "measurements", Map.of("registration_error_m", metrics.get("registration_error_m"), "max_deformation_m", 0.32)
            );
            default -> Map.of(
                    "viewer", "raster-difference",
                    "regions", List.of(Map.of("x", 26, "y", 31, "width", 28, "height", 24, "change", 0.74), Map.of("x", 62, "y", 57, "width", 14, "height", 18, "change", 0.51)),
                    "baseline_date", "2026-06-20"
            );
        };
    }

    private List<Map<String, Object>> pointCloudPoints(int seed) {
        List<Map<String, Object>> points = new ArrayList<>();
        for (int index = 0; index < 96; index++) {
            double angle = index * 0.37;
            double radius = 3.5 + (index % 13) * 0.26;
            points.add(Map.of(
                    "x", round(Math.cos(angle) * radius, 3),
                    "y", round(Math.sin(angle) * radius, 3),
                    "z", round((index % 17) * 0.19 + (seed % 7) * 0.03, 3),
                    "value", round((index % 11) / 10.0, 2)
            ));
        }
        return points;
    }

    private Map<String, Object> runtimeSnapshot(Map<String, Object> group, Map<String, Object> version, Map<String, Object> parameters) {
        return Map.of(
                "model_group", group.get("code"),
                "model_version", version.get("version"),
                "artifact_sha256", version.get("artifact_sha256") == null ? "unregistered" : version.get("artifact_sha256"),
                "container_image", "rail-model-runtime:2026.07",
                "runtime", properties.getModelTest().getRuntime(),
                "device", properties.getModelTest().getDevicePool(),
                "precision", parameters.getOrDefault("precision", "FP32")
        );
    }

    private Map<String, Object> resolvedParameters(String profileId, Object temporaryObject) {
        Map<String, Object> parameters = new LinkedHashMap<>();
        if (profileId != null) {
            List<String> rows = jdbc.queryForList("select parameters::text from model_parameter_profiles where id=?", String.class, profileId);
            if (!rows.isEmpty()) {
                parameters.putAll(Jsonb.map(rows.get(0)));
            }
        }
        if (temporaryObject instanceof Map<?, ?> raw) {
            parameters.putAll(castMap(raw));
        }
        return parameters;
    }

    private Map<String, Object> task(String taskId) {
        return jdbc.queryForMap("""
                select id, model_group_id, matrix_snapshot::text as matrix_snapshot,
                       input_snapshot::text as input_snapshot, options::text as options, trace_id
                from model_test_tasks where id=?
                """, taskId);
    }

    private Map<String, Object> group(String groupId) {
        return jdbc.queryForMap("""
                select mg.id, mg.code, mg.name, mg.model_type,
                       coalesce((select scenes::text from model_versions where model_group=mg.code and status='production' limit 1), '[]') as scenes
                from model_groups mg where mg.id=?
                """, groupId);
    }

    private Map<String, Object> version(String versionId) {
        return jdbc.queryForMap("""
                select v.id, v.model_group, v.version, v.status, v.metrics::text as metrics,
                       (select a.sha256 from model_artifacts a where a.model_version_id=v.id order by a.created_at desc limit 1) as artifact_sha256
                from model_versions v where v.id=?
                """, versionId);
    }

    private List<Map<String, Object>> inputs(String taskId) {
        return jdbc.queryForList("""
                select id, source_type, sample_id, upload_file_id, name, storage_url, sha256, modality,
                       metadata_snapshot::text as metadata_snapshot, ground_truth_snapshot::text as ground_truth_snapshot,
                       validation_status
                from model_test_inputs where test_task_id=? order by created_at, id
                """, taskId);
    }

    private Map<String, Object> activeRules(String groupId) {
        List<String> rows = jdbc.queryForList("""
                select rules::text from model_acceptance_rules
                where model_group_id=? and active=true order by created_at desc limit 1
                """, String.class, groupId);
        return rows.isEmpty() ? Map.of() : Jsonb.map(rows.get(0));
    }

    private void updateStep(String taskId, String step, double progress, String message, Map<String, Object> payload) {
        jdbc.update("update model_test_tasks set current_step=?, progress=? where id=?", step, BigDecimal.valueOf(progress), taskId);
        events.publish(taskId, "step.completed", step, "completed", progress, message, payload);
    }

    private boolean cancelRequested(String taskId) {
        Boolean value = jdbc.queryForObject("select cancel_requested from model_test_tasks where id=?", Boolean.class, taskId);
        return Boolean.TRUE.equals(value);
    }

    private void cancel(String taskId, int completed, int total) {
        jdbc.update("update model_test_tasks set status='cancelled', current_step='cancelled', finished_at=? where id=?",
                Timestamp.from(Instant.now()), taskId);
        events.publish(taskId, "test.cancelled", "cancelled", "cancelled", round(completed * 100.0 / Math.max(1, total), 2),
                "测试任务已取消，已完成结果予以保留", Map.of("completed", completed, "total", total));
        events.complete(taskId);
    }

    private String category(List<Map<String, Object>> predictions) {
        return predictions.isEmpty() ? "未检出" : String.valueOf(predictions.get(0).getOrDefault("category", "目标"));
    }

    private double baseNumber(Map<String, Object> map, String key, double fallback) {
        return map.get(key) instanceof Number number ? number.doubleValue() : fallback;
    }

    private double clamp(double value) {
        return round(Math.max(0, Math.min(1, value)), 4);
    }

    private double percentile(List<Double> sorted, double percentile) {
        if (sorted.isEmpty()) {
            return 0;
        }
        int index = (int) Math.ceil(percentile * sorted.size()) - 1;
        return sorted.get(Math.max(0, Math.min(index, sorted.size() - 1)));
    }

    private double round(double value, int scale) {
        return BigDecimal.valueOf(value).setScale(scale, RoundingMode.HALF_UP).doubleValue();
    }

    private String nullableString(Object value) {
        return value == null || String.valueOf(value).isBlank() || "null".equals(String.valueOf(value)) ? null : String.valueOf(value);
    }

    private Map<String, Object> castMap(Map<?, ?> raw) {
        Map<String, Object> converted = new LinkedHashMap<>();
        raw.forEach((key, value) -> converted.put(String.valueOf(key), value));
        return converted;
    }

    private String safeError(Exception error) {
        String message = error.getMessage();
        return message == null || message.isBlank() ? error.getClass().getSimpleName() : message.substring(0, Math.min(500, message.length()));
    }

    private void pause(long milliseconds) {
        try {
            Thread.sleep(milliseconds);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new IllegalStateException("测试任务线程被中断", e);
        }
    }
}
