/**
 * 导出(契约 D10):异步任务 + 状态机 QUEUED → RUNNING → DONE/FAILED。
 *
 * ExportAdapter 是外部导出工具的 port(参数清单 OPEN-6,真身到位后替换)。
 * 当前 stub 适配器产出"文件清单 manifest"文本,证明状态机端到端可跑;
 * 产物存进程内存(v1 stub;生产应落对象存储/磁盘 —— 见 OPEN 清单)。
 */

import { randomUUID } from "node:crypto";
import { FileLibError } from "./model.js";
import { FILE_LIB_AUDIT_ACTIONS, writeFileLibAudit } from "./audit.js";
import { requireAccessInTx, type FileLibActor } from "./treeService.js";
import type { FileDeps } from "./fileService.js";

export interface ExportAdapterInput {
  readonly storageDir: string;
  readonly target: string;
  readonly params: Record<string, unknown>;
  readonly listFiles: (prefix?: string) => Promise<readonly { path: string; size: number }[]>;
  readonly readFile: (path: string) => Promise<Buffer>;
}

export interface ExportArtifact {
  readonly filename: string;
  readonly content: Buffer;
}

export interface ExportAdapter {
  readonly target: string;
  run(input: ExportAdapterInput): Promise<ExportArtifact>;
}

/** stub 适配器:生成项目文件清单,端到端验证 job 状态机。OPEN-6 后换真导出工具。 */
export function createManifestStubAdapter(versionStore: FileDeps["versionStore"]): ExportAdapter {
  return {
    target: "manifest",
    async run(input) {
      const files = await input.listFiles();
      const lines = [
        `# Export manifest (stub adapter)`,
        `target: ${input.target}`,
        `storageDir: ${input.storageDir}`,
        `files: ${files.length}`,
        ``,
        ...files.map((f) => `${String(f.size).padStart(10)}  ${f.path}`),
      ];
      return { filename: "manifest.txt", content: Buffer.from(lines.join("\n"), "utf8") };
    },
  };
}

// v1 stub 产物存储(进程内存,重启即失;生产替换为持久存储)。
const artifacts = new Map<string, ExportArtifact>();

export interface ExportDeps extends FileDeps {
  readonly adapters: readonly ExportAdapter[];
}

export interface ExportJobDto {
  readonly id: string;
  readonly nodeId: string;
  readonly target: string;
  readonly status: "QUEUED" | "RUNNING" | "DONE" | "FAILED";
  readonly error: string | null;
  readonly createdAt: Date;
}

/** 提交导出(需 VIEW):建行(QUEUED)+ export.run 审计,同事务;异步执行。 */
export async function submitExport(
  deps: ExportDeps,
  actor: FileLibActor,
  projectId: string,
  target: string,
  params: Record<string, unknown>,
): Promise<ExportJobDto> {
  const { node } = await deps.prisma.$transaction(async (tx) =>
    requireAccessInTx(tx, deps, actor, projectId, "VIEW"),
  );
  if (node.kind !== "PROJECT") {
    throw new FileLibError(400, "invalid_node_kind", "export applies to projects only");
  }
  const adapter = deps.adapters.find((a) => a.target === target);
  if (adapter === undefined) {
    throw new FileLibError(400, "unknown_target", `no export adapter for target "${target}"`);
  }
  if (node.storageDir === null) {
    throw new FileLibError(409, "project_not_ready", "project repository is not ready");
  }

  const jobId = randomUUID();
  const job = await deps.prisma.$transaction(async (tx) => {
    const created = await tx.fileLibExportJob.create({
      data: {
        id: jobId,
        organizationId: deps.organizationId,
        nodeId: node.id,
        target,
        params: params as never,
        status: "QUEUED",
        createdByUserId: actor.userId,
      },
    });
    await writeFileLibAudit(tx, {
      action: FILE_LIB_AUDIT_ACTIONS.exportRun,
      actorUserId: actor.userId,
      organizationId: deps.organizationId,
      objectType: "export_job",
      objectId: jobId,
      objectPath: node.pathIds,
      detail: { target, params },
    });
    return created;
  });

  const storageDir = node.storageDir;
  setImmediate(() => {
    void runExportJob(deps, adapter, jobId, storageDir, target, params).catch(() => undefined);
  });
  return toDto(job);
}

async function runExportJob(
  deps: ExportDeps,
  adapter: ExportAdapter,
  jobId: string,
  storageDir: string,
  target: string,
  params: Record<string, unknown>,
): Promise<void> {
  await deps.prisma.fileLibExportJob.update({ where: { id: jobId }, data: { status: "RUNNING" } });
  try {
    const artifact = await adapter.run({
      storageDir,
      target,
      params,
      listFiles: (prefix) => deps.versionStore.list(storageDir, prefix),
      readFile: (path) => deps.versionStore.read(storageDir, path),
    });
    artifacts.set(jobId, artifact);
    await deps.prisma.fileLibExportJob.update({
      where: { id: jobId },
      data: { status: "DONE", downloadUrl: `/database/api/exports/${jobId}/download` },
    });
  } catch (error) {
    await deps.prisma.fileLibExportJob.update({
      where: { id: jobId },
      data: { status: "FAILED", error: String(error) },
    });
  }
}

export async function getExportJob(
  deps: ExportDeps,
  actor: FileLibActor,
  jobId: string,
): Promise<ExportJobDto> {
  const job = await deps.prisma.fileLibExportJob.findFirst({
    where: { id: jobId, organizationId: deps.organizationId },
  });
  if (job === null) throw new FileLibError(404, "export_not_found", "export job not found");
  // D8:对源项目无 View → 404(不泄露 job 存在性)。
  await deps.prisma.$transaction(async (tx) => requireAccessInTx(tx, deps, actor, job.nodeId, "VIEW"));
  return toDto(job);
}

export async function downloadExport(
  deps: ExportDeps,
  actor: FileLibActor,
  jobId: string,
): Promise<ExportArtifact> {
  await getExportJob(deps, actor, jobId);
  const artifact = artifacts.get(jobId);
  if (artifact === undefined) {
    throw new FileLibError(409, "export_not_ready", "export artifact is not available");
  }
  return artifact;
}

function toDto(job: {
  id: string;
  nodeId: string;
  target: string;
  status: "QUEUED" | "RUNNING" | "DONE" | "FAILED";
  error: string | null;
  createdAt: Date;
}): ExportJobDto {
  return {
    id: job.id,
    nodeId: job.nodeId,
    target: job.target,
    status: job.status,
    error: job.error,
    createdAt: job.createdAt,
  };
}
