package com.ai.trackwalker.model;

import com.ai.trackwalker.common.Ids;
import com.ai.trackwalker.common.Jsonb;
import com.ai.trackwalker.storage.ModelArtifactStorageService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpStatus;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException;

import java.net.URI;
import java.sql.Timestamp;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;

@Service
public class ModelArtifactLifecycleService {
    private static final long MAX_MODEL_BYTES = 4L * 1024 * 1024 * 1024;
    private static final long MAX_LABEL_BYTES = 5L * 1024 * 1024;

    private final JdbcTemplate jdbc;
    private final ModelArtifactStorageService storage;
    private final ModelArtifactInstallerClient installer;
    private final ModelArtifactEventPublisher events;
    private final ModelRuntimeDeploymentService runtimes;
    private final RestTemplateBuilder restTemplateBuilder;
    private final ThreadPoolTaskExecutor executor;

    public ModelArtifactLifecycleService(
            JdbcTemplate jdbc,
            ModelArtifactStorageService storage,
            ModelArtifactInstallerClient installer,
            ModelArtifactEventPublisher events,
            ModelRuntimeDeploymentService runtimes,
            RestTemplateBuilder restTemplateBuilder,
            @Qualifier("modelArtifactTaskExecutor") ThreadPoolTaskExecutor executor
    ) {
        this.jdbc = jdbc;
        this.storage = storage;
        this.installer = installer;
        this.events = events;
        this.runtimes = runtimes;
        this.restTemplateBuilder = restTemplateBuilder;
        this.executor = executor;
    }

    public Map<String, Object> runtimeOverview() {
        List<Map<String, Object>> rows = jdbc.queryForList("""
                select d.id as runtime_deployment_id, d.name as runtime_deployment_name,
                       d.environment, d.active, d.status as runtime_status, d.health_status,
                       d.health_message, d.connectivity_status, d.runtime_engine,
                       d.execution_provider, d.device, d.precision, d.service_url,
                       d.desired_artifact_id, d.installed_artifact_id, d.artifact_sync_status,
                       mg.id as model_group_id, mg.code as model_group_code, mg.name as model_group_name,
                       v.id as model_version_id, v.version as model_version,
                       a.id as artifact_id, a.artifact_type, a.package_format,
                       a.source_type, a.lifecycle_status, a.validation_status,
                       a.validation_report::text as validation_report, a.size_bytes as artifact_size_bytes,
                       coalesce((select count(*) from model_artifact_files af where af.artifact_id=a.id), 0) as artifact_file_count,
                       i.id as installation_id, i.status as installation_status, i.loaded,
                       i.install_path, i.installed_at, i.loaded_at, i.last_verified_at,
                       i.error_code as installation_error_code, i.error_detail::text as installation_error_detail,
                       j.id as job_id, j.job_type, j.status as job_status, j.progress as job_progress,
                       j.current_stage as job_stage, j.error_message as job_error_message
                from model_runtime_deployments d
                join model_groups mg on mg.id=d.model_group_id
                join model_versions v on v.id=d.model_version_id
                left join model_artifacts a on a.id=coalesce(d.desired_artifact_id, d.installed_artifact_id)
                left join lateral (
                    select installation.* from model_artifact_installations installation
                    where installation.runtime_deployment_id=d.id
                    order by installation.updated_at desc limit 1
                ) i on true
                left join lateral (
                    select job.* from model_artifact_jobs job
                    where job.runtime_deployment_id=d.id
                    order by job.created_at desc limit 1
                ) j on true
                order by mg.name, d.active desc, d.updated_at desc
                """);
        List<Map<String, Object>> deployments = rows.stream()
                .map(row -> parse(row, "validation_report", "installation_error_detail"))
                .toList();
        return Map.of(
                "deployments", deployments,
                "installer", installerStatus(),
                "artifact_store_available", storage.isAvailable()
        );
    }

    public Map<String, Object> artifact(String artifactId) {
        List<Map<String, Object>> rows = jdbc.queryForList("""
                select a.*, a.runtime_constraints::text as runtime_constraints_json,
                       a.validation_report::text as validation_report_json,
                       a.manifest::text as manifest_json, v.version as model_version,
                       mg.id as model_group_id, mg.code as model_group_code, mg.name as model_group_name
                from model_artifacts a
                join model_versions v on v.id=a.model_version_id
                join model_groups mg on mg.code=v.model_group
                where a.id=?
                """, artifactId);
        if (rows.isEmpty()) {
            throw notFound("模型制品不存在");
        }
        Map<String, Object> result = parse(rows.get(0), "runtime_constraints_json", "validation_report_json", "manifest_json");
        result.put("files", artifactFiles(artifactId));
        return result;
    }

    @Transactional
    public Map<String, Object> upload(
            String deploymentId,
            MultipartFile primary,
            MultipartFile secondary,
            MultipartFile labels,
            String operator
    ) {
        Map<String, Object> deployment = deployment(deploymentId);
        String group = text(deployment.get("model_group_code"));
        List<String> required = requiredComponents(group);
        if (required.isEmpty()) {
            throw conflict("该模型使用内置运行组件，不需要上传制品");
        }
        validateUpload("primary", primary, MAX_MODEL_BYTES, ".onnx", true);
        validateUpload("secondary", secondary, MAX_MODEL_BYTES, ".onnx", required.contains("secondary"));
        validateUpload("labels", labels, MAX_LABEL_BYTES, ".txt", true);

        String artifactId = findOrCreateArtifact(deployment, operator);
        List<Map<String, Object>> oldFiles = artifactFiles(artifactId);
        List<FileInput> inputs = new ArrayList<>();
        inputs.add(new FileInput("primary", primary));
        if (secondary != null && !secondary.isEmpty()) {
            inputs.add(new FileInput("secondary", secondary));
        }
        inputs.add(new FileInput("labels", labels));

        List<Map<String, Object>> manifestFiles = new ArrayList<>();
        for (FileInput input : inputs) {
            MultipartFile file = input.file();
            String safeName = safeFileName(file.getOriginalFilename(), input.component() + defaultExtension(input.component()));
            String objectKey = "released/" + safeSegment(group) + "/"
                    + safeSegment(text(deployment.get("model_version"))) + "/" + artifactId + "/"
                    + input.component() + "-" + safeName;
            ModelArtifactStorageService.StoredArtifact stored = storage.store(objectKey, file);
            Map<String, Object> item = new LinkedHashMap<>();
            item.put("component_type", input.component());
            item.put("file_name", safeName);
            item.put("object_key", stored.objectKey());
            item.put("storage_url", stored.storageUrl());
            item.put("sha256", stored.sha256());
            item.put("size_bytes", stored.sizeBytes());
            item.put("content_type", stored.contentType());
            manifestFiles.add(item);
        }

        jdbc.update("delete from model_artifact_files where artifact_id=?", artifactId);
        for (Map<String, Object> file : manifestFiles) {
            jdbc.update("""
                    insert into model_artifact_files(
                        id, artifact_id, component_type, file_name, object_key, storage_url,
                        sha256, size_bytes, content_type, validation_status, validation_report
                    ) values (?,?,?,?,?,?,?,?,?,'uploaded','{}'::jsonb)
                    """, Ids.next("maf"), artifactId, file.get("component_type"), file.get("file_name"),
                    file.get("object_key"), file.get("storage_url"), file.get("sha256"),
                    file.get("size_bytes"), file.get("content_type"));
        }
        long totalSize = manifestFiles.stream().mapToLong(item -> ((Number) item.get("size_bytes")).longValue()).sum();
        String combinedSha = manifestFiles.stream().map(item -> text(item.get("sha256"))).reduce("", String::concat);
        String primaryObjectKey = text(manifestFiles.get(0).get("object_key"));
        Map<String, Object> manifest = Map.of("files", manifestFiles, "component_count", manifestFiles.size());
        jdbc.update("""
                update model_artifacts set object_key=?, sha256=encode(sha256(?::bytea),'hex'),
                    size_bytes=?, package_format='onnx-package', manifest=?::jsonb,
                    source_type='upload', lifecycle_status='uploaded', created_by=?,
                    validation_status='pending', validation_report='{}'::jsonb, recycled_at=null
                where id=?
                """, primaryObjectKey, combinedSha, totalSize, Jsonb.write(manifest), operator(operator), artifactId);
        jdbc.update("""
                update model_runtime_deployments set desired_artifact_id=?, artifact_sync_status='pending', updated_at=? where id=?
                """, artifactId, Timestamp.from(Instant.now()), deploymentId);
        audit("model_artifact", artifactId, "uploaded", operator, Map.of("previous_files", oldFiles), manifest,
                "上传模型制品");
        return artifact(artifactId);
    }

    public Map<String, Object> createInstallJob(String deploymentId, Map<String, Object> request) {
        Map<String, Object> deployment = deployment(deploymentId);
        String artifactId = requestText(request, "artifact_id", text(deployment.get("desired_artifact_id")));
        if (artifactId == null) {
            throw badRequest("请先上传模型制品");
        }
        Map<String, Object> artifact = artifact(artifactId);
        if (!Objects.equals(artifact.get("model_version_id"), deployment.get("model_version_id"))) {
            throw badRequest("模型制品与运行配置版本不匹配");
        }
        List<String> required = requiredComponents(text(deployment.get("model_group_code")));
        List<String> supplied = artifactFiles(artifactId).stream().map(item -> text(item.get("component_type"))).toList();
        if (!supplied.containsAll(required)) {
            throw badRequest("模型制品组件不完整，需要: " + String.join("、", required));
        }
        if ("production".equals(deployment.get("environment")) && Boolean.TRUE.equals(deployment.get("active"))) {
            throw conflict("生产环境不能直接覆盖生效中的模型，请在未生效运行配置上安装并完成切换");
        }
        requireApproval(deployment, request);
        String installationId = upsertInstallation(artifactId, deploymentId);
        return submitJob("install", deploymentId, artifactId, installationId, request);
    }

    public Map<String, Object> createActionJob(String installationId, String action, Map<String, Object> request) {
        if (!List.of("load", "unload").contains(action)) {
            throw badRequest("不支持的制品动作");
        }
        Map<String, Object> installation = installation(installationId);
        if ("load".equals(action) && !List.of("ready", "unloaded").contains(text(installation.get("status")))) {
            throw conflict("只有安装完成的制品可以加载");
        }
        if ("unload".equals(action) && !Boolean.TRUE.equals(installation.get("loaded"))) {
            throw conflict("模型当前未加载");
        }
        return submitJob(action, text(installation.get("runtime_deployment_id")),
                text(installation.get("artifact_id")), installationId, request);
    }

    public Map<String, Object> uninstallImpact(String installationId) {
        Map<String, Object> installation = installation(installationId);
        String deploymentId = text(installation.get("runtime_deployment_id"));
        String groupId = text(installation.get("model_group_id"));
        String versionId = text(installation.get("model_version_id"));
        List<Map<String, Object>> blockers = new ArrayList<>();
        if (Boolean.TRUE.equals(installation.get("active"))) {
            blockers.add(Map.of("type", "active_deployment", "message", "该制品属于当前生效的运行配置"));
        }
        Integer runningTests = jdbc.queryForObject("""
                select count(*) from model_test_tasks where model_group_id=? and status in ('queued','running')
                """, Integer.class, groupId);
        if (runningTests != null && runningTests > 0) {
            blockers.add(Map.of("type", "running_tests", "count", runningTests, "message", "存在运行中的模型测试任务"));
        }
        Integer releases = jdbc.queryForObject("""
                select count(*) from model_release_records
                where model_version_id=? and status in ('requested','approved','gray','effective')
                """, Integer.class, versionId);
        if (releases != null && releases > 0) {
            blockers.add(Map.of("type", "release_reference", "count", releases, "message", "存在未结束的发布记录引用"));
        }
        return Map.of(
                "installation_id", installationId,
                "runtime_deployment_id", deploymentId,
                "allowed", blockers.isEmpty(),
                "loaded", Boolean.TRUE.equals(installation.get("loaded")),
                "blockers", blockers
        );
    }

    public Map<String, Object> createUninstallJob(String installationId, String operator, String reason) {
        Map<String, Object> impact = uninstallImpact(installationId);
        if (!Boolean.TRUE.equals(impact.get("allowed"))) {
            throw conflict("制品仍被运行配置或业务任务引用，不能卸载");
        }
        Map<String, Object> installation = installation(installationId);
        Map<String, Object> request = new LinkedHashMap<>();
        request.put("operator", operator(operator));
        request.put("reason", reason == null ? "卸载模型制品" : reason);
        return submitJob("uninstall", text(installation.get("runtime_deployment_id")),
                text(installation.get("artifact_id")), installationId, request);
    }

    public Map<String, Object> job(String jobId) {
        List<Map<String, Object>> rows = jdbc.queryForList("""
                select j.*, j.options::text as options_json, mg.code as model_group_code,
                       mg.name as model_group_name, v.version as model_version
                from model_artifact_jobs j
                join model_runtime_deployments d on d.id=j.runtime_deployment_id
                join model_groups mg on mg.id=d.model_group_id
                join model_versions v on v.id=d.model_version_id
                where j.id=?
                """, jobId);
        if (rows.isEmpty()) {
            throw notFound("制品任务不存在");
        }
        Map<String, Object> result = parse(rows.get(0), "options_json");
        result.put("events", events.history(jobId, 0));
        return result;
    }

    public List<Map<String, Object>> jobs(String deploymentId) {
        List<Map<String, Object>> rows = jdbc.queryForList("""
                select id, job_type, artifact_id, runtime_deployment_id, installation_id,
                       status, progress, current_stage, operator_name, error_code,
                       error_message, created_at, started_at, finished_at
                from model_artifact_jobs where (? is null or runtime_deployment_id=?)
                order by created_at desc limit 100
                """, deploymentId, deploymentId);
        return rows;
    }

    private Map<String, Object> submitJob(
            String jobType,
            String deploymentId,
            String artifactId,
            String installationId,
            Map<String, Object> request
    ) {
        String idempotencyKey = requestText(request, "idempotency_key", null);
        if (idempotencyKey != null) {
            List<Map<String, Object>> existing = jdbc.queryForList(
                    "select id from model_artifact_jobs where idempotency_key=?", idempotencyKey);
            if (!existing.isEmpty()) {
                return job(text(existing.get(0).get("id")));
            }
        }
        Integer activeJobs = jdbc.queryForObject("""
                select count(*) from model_artifact_jobs
                where runtime_deployment_id=? and status in ('queued','running')
                """, Integer.class, deploymentId);
        if (activeJobs != null && activeJobs > 0) {
            throw conflict("该运行配置已有制品任务正在执行");
        }
        String jobId = Ids.next("maj");
        String traceId = Ids.next("trace");
        Instant now = Instant.now();
        jdbc.update("""
                insert into model_artifact_jobs(
                    id, job_type, artifact_id, runtime_deployment_id, installation_id,
                    status, progress, current_stage, idempotency_key, approval_ticket,
                    options, trace_id, operator_name, reason, created_at, updated_at
                ) values (?,?,?,?,?,'queued',0,'queued',?,?,?::jsonb,?,?,?,?,?)
                """, jobId, jobType, artifactId, deploymentId, installationId, idempotencyKey,
                requestText(request, "approval_ticket", null), Jsonb.write(request), traceId,
                operator(requestText(request, "operator", null)), requestText(request, "reason", null),
                Timestamp.from(now), Timestamp.from(now));
        events.publish(jobId, "queued", "queued", 0, jobLabel(jobType) + "任务已进入队列", Map.of());
        executor.execute(() -> runJob(jobId));
        return job(jobId);
    }

    private void runJob(String jobId) {
        Map<String, Object> current = job(jobId);
        String type = text(current.get("job_type"));
        try {
            jdbc.update("update model_artifact_jobs set status='running', started_at=?, updated_at=? where id=?",
                    Timestamp.from(Instant.now()), Timestamp.from(Instant.now()), jobId);
            switch (type) {
                case "install" -> runInstall(current);
                case "load" -> runLoad(current);
                case "unload" -> runUnload(current);
                case "uninstall" -> runUninstall(current);
                default -> throw new IllegalStateException("未知制品任务类型: " + type);
            }
            finish(jobId, "completed", null, null);
        } catch (Exception e) {
            String message = concise(e);
            jdbc.update("""
                    update model_artifact_installations set status=case when ?='install' then 'install_failed' else status end,
                        error_code=?, error_detail=?::jsonb, updated_at=? where id=?
                    """, type, e.getClass().getSimpleName(), Jsonb.write(Map.of("message", message)),
                    Timestamp.from(Instant.now()), current.get("installation_id"));
            events.publish(jobId, "failed", "failed", number(current.get("progress")), message,
                    Map.of("error_type", e.getClass().getSimpleName()));
            finish(jobId, "failed", e.getClass().getSimpleName(), message);
        }
    }

    private void runInstall(Map<String, Object> job) {
        String jobId = text(job.get("id"));
        String artifactId = text(job.get("artifact_id"));
        String installationId = text(job.get("installation_id"));
        Map<String, Object> deployment = deployment(text(job.get("runtime_deployment_id")));
        events.publish(jobId, "prepare", "running", 8, "正在核对制品清单和安装目标", Map.of());
        jdbc.update("update model_artifact_installations set status='installing', updated_at=? where id=?",
                Timestamp.from(Instant.now()), installationId);
        List<Map<String, Object>> files = artifactFiles(artifactId);
        events.publish(jobId, "download", "running", 22, "正在下载并校验模型组件", Map.of("file_count", files.size()));
        Map<String, Object> result = installer.install(jobId, text(deployment.get("model_group_code")),
                text(deployment.get("model_version")), files);
        events.publish(jobId, "verify", "running", 76, "模型文件已发布，正在写入安装清单", Map.of());
        Instant now = Instant.now();
        jdbc.update("""
                update model_artifact_installations set status='ready', install_path=?, installed_manifest=?::jsonb,
                    installed_at=?, last_verified_at=?, error_code=null, error_detail='{}'::jsonb, updated_at=? where id=?
                """, text(result.get("install_root")), Jsonb.write(result), Timestamp.from(now), Timestamp.from(now),
                Timestamp.from(now), installationId);
        jdbc.update("""
                update model_artifacts set validation_status='passed', validation_report=?::jsonb,
                    lifecycle_status='released', released_at=coalesce(released_at,?) where id=?
                """, Jsonb.write(result), Timestamp.from(now), artifactId);
        jdbc.update("""
                update model_runtime_deployments set installed_artifact_id=?, artifact_sync_status='ready', updated_at=? where id=?
                """, artifactId, Timestamp.from(now), deployment.get("id"));
        if (booleanOption(job, "load_after_install", true)) {
            events.publish(jobId, "load", "running", 88, "正在创建运行时推理会话", Map.of());
            runtimeAction(deployment, "load");
            jdbc.update("""
                    update model_artifact_installations set loaded=true, loaded_at=?, status='ready', updated_at=? where id=?
                    """, Timestamp.from(now), Timestamp.from(now), installationId);
        }
        events.publish(jobId, "health_check", "running", 96, "正在执行运行端点健康检查", Map.of());
        runtimes.checkHealth(text(deployment.get("id")));
        events.publish(jobId, "completed", "completed", 100, "模型制品安装并校验完成", Map.of());
        audit("model_artifact_installation", installationId, "installed", text(job.get("operator_name")),
                Map.of(), result, "安装模型制品");
    }

    private void runLoad(Map<String, Object> job) {
        String jobId = text(job.get("id"));
        Map<String, Object> deployment = deployment(text(job.get("runtime_deployment_id")));
        events.publish(jobId, "load", "running", 35, "正在加载模型并创建推理会话", Map.of());
        Map<String, Object> response = runtimeAction(deployment, "load");
        Instant now = Instant.now();
        jdbc.update("""
                update model_artifact_installations set loaded=true, loaded_at=?, status='ready',
                    last_verified_at=?, error_code=null, error_detail='{}'::jsonb, updated_at=? where id=?
                """, Timestamp.from(now), Timestamp.from(now), Timestamp.from(now), job.get("installation_id"));
        runtimes.checkHealth(text(deployment.get("id")));
        events.publish(jobId, "completed", "completed", 100, "模型已加载到运行时", response);
    }

    private void runUnload(Map<String, Object> job) {
        String jobId = text(job.get("id"));
        Map<String, Object> deployment = deployment(text(job.get("runtime_deployment_id")));
        events.publish(jobId, "drain", "running", 28, "正在停止新推理请求并释放会话", Map.of());
        Map<String, Object> response = runtimeAction(deployment, "unload");
        jdbc.update("""
                update model_artifact_installations set loaded=false, status='unloaded', updated_at=? where id=?
                """, Timestamp.from(Instant.now()), job.get("installation_id"));
        events.publish(jobId, "completed", "completed", 100, "模型运行时资源已释放", response);
    }

    private void runUninstall(Map<String, Object> job) {
        String jobId = text(job.get("id"));
        Map<String, Object> deployment = deployment(text(job.get("runtime_deployment_id")));
        String installationId = text(job.get("installation_id"));
        events.publish(jobId, "unload", "running", 20, "正在释放模型运行时资源", Map.of());
        runtimeAction(deployment, "unload");
        events.publish(jobId, "remove", "running", 55, "正在删除受管模型文件", Map.of());
        Map<String, Object> response = installer.uninstall(jobId, text(deployment.get("model_group_code")),
                text(deployment.get("model_version")));
        Instant now = Instant.now();
        jdbc.update("""
                update model_artifact_installations set status='uninstalled', loaded=false,
                    installed_manifest='{}'::jsonb, updated_at=? where id=?
                """, Timestamp.from(now), installationId);
        jdbc.update("""
                update model_runtime_deployments set installed_artifact_id=null, artifact_sync_status='missing', updated_at=? where id=?
                """, Timestamp.from(now), deployment.get("id"));
        jdbc.update("update model_artifacts set lifecycle_status='recycled', recycled_at=? where id=?",
                Timestamp.from(now), job.get("artifact_id"));
        runtimes.checkHealth(text(deployment.get("id")));
        events.publish(jobId, "completed", "completed", 100, "模型制品已卸载", response);
        audit("model_artifact_installation", installationId, "uninstalled", text(job.get("operator_name")),
                Map.of(), response, text(job.get("reason")));
    }

    @SuppressWarnings("unchecked")
    private Map<String, Object> runtimeAction(Map<String, Object> deployment, String action) {
        String base = text(deployment.get("service_url"));
        String group = text(deployment.get("model_group_code"));
        String version = text(deployment.get("model_version"));
        String url = base + "/api/v1/runtime/" + action + "?model_group=" + encode(group);
        if ("load".equals(action)) {
            url += "&model_version=" + encode(version);
        }
        int timeout = ((Number) deployment.getOrDefault("request_timeout_seconds", 60)).intValue();
        Map<String, Object> response = restTemplateBuilder
                .setConnectTimeout(Duration.ofSeconds(10))
                .setReadTimeout(Duration.ofSeconds(Math.max(30, timeout)))
                .build().postForObject(URI.create(url), null, Map.class);
        return response == null ? Map.of() : response;
    }

    private String findOrCreateArtifact(Map<String, Object> deployment, String operator) {
        String existing = text(deployment.get("desired_artifact_id"));
        if (existing != null) {
            return existing;
        }
        List<Map<String, Object>> rows = jdbc.queryForList(
                "select id from model_artifacts where model_version_id=? order by created_at desc limit 1",
                deployment.get("model_version_id"));
        if (!rows.isEmpty()) {
            return text(rows.get(0).get("id"));
        }
        String artifactId = Ids.next("artifact");
        jdbc.update("""
                insert into model_artifacts(
                    id, model_version_id, artifact_type, object_key, sha256, size_bytes,
                    runtime_constraints, validation_status, validation_report, package_format,
                    manifest, source_type, lifecycle_status, created_by
                ) values (?,?,?,'pending',repeat('0',64),0,'{}'::jsonb,'pending','{}'::jsonb,
                    'onnx-package','{}'::jsonb,'upload','registered',?)
                """, artifactId, deployment.get("model_version_id"), "ONNX", operator(operator));
        return artifactId;
    }

    private String upsertInstallation(String artifactId, String deploymentId) {
        List<Map<String, Object>> rows = jdbc.queryForList("""
                select id from model_artifact_installations
                where artifact_id=? and runtime_deployment_id=? and runtime_node_id='shared-volume'
                """, artifactId, deploymentId);
        if (!rows.isEmpty()) {
            String id = text(rows.get(0).get("id"));
            jdbc.update("""
                    update model_artifact_installations set status='queued', error_code=null,
                        error_detail='{}'::jsonb, updated_at=? where id=?
                    """, Timestamp.from(Instant.now()), id);
            return id;
        }
        String id = Ids.next("install");
        jdbc.update("""
                insert into model_artifact_installations(
                    id, artifact_id, runtime_deployment_id, runtime_node_id, status
                ) values (?,?,?,'shared-volume','queued')
                """, id, artifactId, deploymentId);
        return id;
    }

    private Map<String, Object> deployment(String deploymentId) {
        List<Map<String, Object>> rows = jdbc.queryForList("""
                select d.*, mg.code as model_group_code, mg.name as model_group_name,
                       v.version as model_version
                from model_runtime_deployments d
                join model_groups mg on mg.id=d.model_group_id
                join model_versions v on v.id=d.model_version_id
                where d.id=?
                """, deploymentId);
        if (rows.isEmpty()) {
            throw notFound("模型运行配置不存在");
        }
        return rows.get(0);
    }

    private Map<String, Object> installation(String installationId) {
        List<Map<String, Object>> rows = jdbc.queryForList("""
                select i.*, d.active, d.model_group_id, d.model_version_id,
                       mg.code as model_group_code, v.version as model_version
                from model_artifact_installations i
                join model_runtime_deployments d on d.id=i.runtime_deployment_id
                join model_groups mg on mg.id=d.model_group_id
                join model_versions v on v.id=d.model_version_id
                where i.id=?
                """, installationId);
        if (rows.isEmpty()) {
            throw notFound("模型制品安装记录不存在");
        }
        return rows.get(0);
    }

    private List<Map<String, Object>> artifactFiles(String artifactId) {
        return jdbc.queryForList("""
                select id, component_type, file_name, object_key, storage_url, sha256,
                       size_bytes, content_type, validation_status,
                       validation_report::text as validation_report
                from model_artifact_files where artifact_id=?
                order by case component_type when 'primary' then 1 when 'secondary' then 2 else 3 end
                """, artifactId).stream().map(row -> parse(row, "validation_report")).toList();
    }

    private Map<String, Object> installerStatus() {
        try {
            return installer.status();
        } catch (Exception e) {
            return Map.of("status", "unavailable", "message", concise(e));
        }
    }

    private void requireApproval(Map<String, Object> deployment, Map<String, Object> request) {
        if ("production".equals(deployment.get("environment"))
                && requestText(request, "approval_ticket", null) == null) {
            throw badRequest("生产环境安装必须填写审批单号");
        }
    }

    static List<String> requiredComponents(String modelGroup) {
        if ("pointcloud-analyzer".equals(modelGroup)) {
            return List.of();
        }
        if ("thermal-analyzer".equals(modelGroup)) {
            return List.of("primary", "secondary", "labels");
        }
        return List.of("primary", "labels");
    }

    static String safeFileName(String original, String fallback) {
        String source = original == null || original.isBlank() ? fallback : original;
        String name = source.replace('\\', '/');
        name = name.substring(name.lastIndexOf('/') + 1).trim();
        name = name.replaceAll("[^A-Za-z0-9._-]", "_");
        if (name.isBlank() || name.equals(".") || name.equals("..")) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "文件名不合法");
        }
        return name.length() > 180 ? name.substring(name.length() - 180) : name;
    }

    private void validateUpload(String component, MultipartFile file, long maxBytes, String extension, boolean required) {
        if (file == null || file.isEmpty()) {
            if (required) {
                throw badRequest("缺少" + componentLabel(component));
            }
            return;
        }
        if (file.getSize() > maxBytes) {
            throw badRequest(componentLabel(component) + "超过允许大小");
        }
        String name = safeFileName(file.getOriginalFilename(), component + extension).toLowerCase(Locale.ROOT);
        if (!name.endsWith(extension)) {
            throw badRequest(componentLabel(component) + "必须是 " + extension + " 文件");
        }
    }

    private void finish(String jobId, String status, String errorCode, String errorMessage) {
        jdbc.update("""
                update model_artifact_jobs set status=?, progress=case when ?='completed' then 100 else progress end,
                    current_stage=?, error_code=?, error_message=?, finished_at=?, updated_at=? where id=?
                """, status, status, status, errorCode, errorMessage, Timestamp.from(Instant.now()),
                Timestamp.from(Instant.now()), jobId);
    }

    private boolean booleanOption(Map<String, Object> job, String key, boolean fallback) {
        Object raw = job.get("options_json");
        Map<String, Object> options = raw instanceof Map<?, ?> ? castMap(raw) : Map.of();
        Object value = options.get(key);
        return value == null ? fallback : Boolean.parseBoolean(String.valueOf(value));
    }

    private void audit(String entityType, String entityId, String action, String operator,
                       Map<String, Object> before, Map<String, Object> after, String reason) {
        jdbc.update("""
                insert into model_audit_logs(id, entity_type, entity_id, action, operator_name,
                    before_snapshot, after_snapshot, reason, created_at)
                values (?,?,?,?,?,?::jsonb,?::jsonb,?,?)
                """, Ids.next("audit"), entityType, entityId, action, operator(operator),
                Jsonb.write(before), Jsonb.write(after), reason, Timestamp.from(Instant.now()));
    }

    private Map<String, Object> parse(Map<String, Object> source, String... jsonColumns) {
        Map<String, Object> result = new LinkedHashMap<>(source);
        for (String column : jsonColumns) {
            Object value = result.remove(column);
            String target = column.endsWith("_json") ? column.substring(0, column.length() - 5) : column;
            result.put(target, Jsonb.map(value == null ? "{}" : String.valueOf(value)));
        }
        return result;
    }

    @SuppressWarnings("unchecked")
    private Map<String, Object> castMap(Object value) {
        return (Map<String, Object>) value;
    }

    private String requestText(Map<String, Object> request, String key, String fallback) {
        if (request == null) {
            return fallback;
        }
        String value = text(request.get(key));
        return value == null ? fallback : value;
    }

    private String operator(String value) {
        return value == null || value.isBlank() ? "模型运维管理员" : value.trim();
    }

    private String text(Object value) {
        if (value == null) {
            return null;
        }
        String result = String.valueOf(value).trim();
        return result.isBlank() || "null".equalsIgnoreCase(result) ? null : result;
    }

    private double number(Object value) {
        return value instanceof Number number ? number.doubleValue() : 0;
    }

    private String safeSegment(String value) {
        if (value == null || !value.matches("[A-Za-z0-9._-]+")) {
            throw badRequest("模型标识包含非法字符");
        }
        return value;
    }

    private String defaultExtension(String component) {
        return "labels".equals(component) ? ".txt" : ".onnx";
    }

    private String componentLabel(String component) {
        return switch (component) {
            case "primary" -> "主模型文件";
            case "secondary" -> "辅助模型文件";
            case "labels" -> "标签文件";
            default -> "制品文件";
        };
    }

    private String encode(String value) {
        return java.net.URLEncoder.encode(value, java.nio.charset.StandardCharsets.UTF_8);
    }

    private String jobLabel(String type) {
        return switch (type) {
            case "install" -> "安装";
            case "load" -> "加载";
            case "unload" -> "释放";
            case "uninstall" -> "卸载";
            default -> "制品";
        };
    }

    private String concise(Exception exception) {
        Throwable cause = exception;
        while (cause.getCause() != null) {
            cause = cause.getCause();
        }
        String message = cause.getMessage();
        return message == null || message.isBlank() ? cause.getClass().getSimpleName() : message;
    }

    private ResponseStatusException notFound(String message) {
        return new ResponseStatusException(HttpStatus.NOT_FOUND, message);
    }

    private ResponseStatusException badRequest(String message) {
        return new ResponseStatusException(HttpStatus.BAD_REQUEST, message);
    }

    private ResponseStatusException conflict(String message) {
        return new ResponseStatusException(HttpStatus.CONFLICT, message);
    }

    private record FileInput(String component, MultipartFile file) {
    }
}
