import { createHash } from "node:crypto";
import { realpathSync, statSync } from "node:fs";

export interface AgentRunTargetTaskRequest {
  readonly title: string;
  readonly target: string;
  readonly targetWorkspace: string;
  readonly repository: string;
  readonly ref: string;
  readonly mdtodoId: string;
}

export interface AgentRunTargetTaskPreflight extends AgentRunTargetTaskRequest {
  readonly targetRoute: string;
  readonly projectId: string;
  readonly workspaceRoot: string;
  readonly sourceAuthority: {
    readonly kind: "explicit-target-context";
    readonly gitStateInspected: false;
    readonly valuesPrinted: false;
  };
}

export interface AgentRunTargetTaskOperation {
  readonly aipod: string;
  readonly tenantId: string;
  readonly projectId: string;
  readonly prompt: string;
}

export interface AgentRunTargetTaskOperationEvidence {
  readonly version: "v1";
  readonly fingerprint: string;
  readonly payloadHash: string;
  readonly scope: {
    readonly tenantId: string;
    readonly projectId: string;
    readonly aipod: string;
    readonly title: string;
    readonly mdtodoId: string;
    readonly target: string;
    readonly repository: string;
    readonly ref: string;
    readonly targetWorkspace: string;
  };
  readonly reason: "canonical-logical-operation";
  readonly valuesPrinted: false;
}

export class AgentRunTargetTaskPreflightError extends Error {
  readonly stage: string;
  readonly details: Record<string, unknown>;

  constructor(stage: string, message: string, details: Record<string, unknown> = {}) {
    super(message);
    this.name = "AgentRunTargetTaskPreflightError";
    this.stage = stage;
    this.details = { ...details, mutation: false, valuesPrinted: false };
  }
}

export function targetTaskRequestFromArgs(args: string[]): AgentRunTargetTaskRequest | null {
  const raw = {
    title: option(args, "title"),
    target: option(args, "target"),
    targetWorkspace: option(args, "target-workspace"),
    repository: option(args, "repo"),
    ref: option(args, "ref"),
    mdtodoId: option(args, "mdtodo-id"),
  };
  const targetMode = raw.targetWorkspace !== null || raw.repository !== null || raw.ref !== null || raw.mdtodoId !== null;
  if (!targetMode) return null;
  const missing = Object.entries(raw).filter(([, value]) => value === null).map(([key]) => key);
  if (missing.length > 0) {
    throw new AgentRunTargetTaskPreflightError(
      "target-context",
      `Artificer Target 派单缺少 ${missing.join(", ")}；请同时提供 --title、--target、--target-workspace、--repo、--ref 和 --mdtodo-id。`,
      { missing },
    );
  }
  return {
    title: validateChineseTaskTitle(raw.title),
    target: validateTarget(raw.target as string),
    targetWorkspace: validateTargetWorkspace(raw.targetWorkspace as string),
    repository: normalizeRepository(raw.repository as string),
    ref: validateRef(raw.ref as string),
    mdtodoId: raw.mdtodoId as string,
  };
}

export function preflightTargetTask(request: AgentRunTargetTaskRequest): AgentRunTargetTaskPreflight {
  let workspaceRoot: string;
  try {
    if (!statSync(request.targetWorkspace).isDirectory()) throw new Error("not a directory");
    workspaceRoot = realpathSync(request.targetWorkspace);
  } catch {
    throw new AgentRunTargetTaskPreflightError(
      "workspace",
      `Target 工作区不存在或不是目录：${request.target}:${request.targetWorkspace}`,
      { target: request.target, targetWorkspace: request.targetWorkspace },
    );
  }

  return preflightTargetTaskFromObservedWorkspace(request, workspaceRoot);
}

export function preflightTargetTaskFromObservedWorkspace(
  request: AgentRunTargetTaskRequest,
  workspaceRoot: string,
): AgentRunTargetTaskPreflight {
  if (!workspaceRoot.startsWith("/") || workspaceRoot.includes("\n") || workspaceRoot.includes("\0")) {
    throw new AgentRunTargetTaskPreflightError(
      "workspace",
      `Target 工作区返回了无效的绝对目录：${request.target}:${request.targetWorkspace}`,
      { target: request.target, targetWorkspace: request.targetWorkspace },
    );
  }
  return {
    ...request,
    targetRoute: `${request.target}:${request.targetWorkspace}`,
    projectId: request.repository,
    workspaceRoot,
    sourceAuthority: {
      kind: "explicit-target-context",
      gitStateInspected: false,
      valuesPrinted: false,
    },
  };
}

let activeTargetTaskPreflight: AgentRunTargetTaskPreflight | null = null;

export async function withAgentRunTargetTaskPreflight<T>(
  preflight: AgentRunTargetTaskPreflight | null,
  action: () => Promise<T>,
): Promise<T> {
  const previous = activeTargetTaskPreflight;
  activeTargetTaskPreflight = preflight;
  try {
    return await action();
  } finally {
    activeTargetTaskPreflight = previous;
  }
}

export function currentAgentRunTargetTaskPreflight(request: AgentRunTargetTaskRequest): AgentRunTargetTaskPreflight | null {
  if (activeTargetTaskPreflight === null) return null;
  const matches = activeTargetTaskPreflight.title === request.title
    && activeTargetTaskPreflight.target === request.target
    && activeTargetTaskPreflight.targetWorkspace === request.targetWorkspace
    && activeTargetTaskPreflight.repository === request.repository
    && activeTargetTaskPreflight.ref === request.ref
    && activeTargetTaskPreflight.mdtodoId === request.mdtodoId;
  if (matches) return activeTargetTaskPreflight;
  throw new AgentRunTargetTaskPreflightError(
    "target-context",
    "Target 目录探测结果与当前 Artificer 派单上下文不一致。",
    { target: request.target, targetWorkspace: request.targetWorkspace },
  );
}

export function targetTaskPrompt(preflight: AgentRunTargetTaskPreflight, userPrompt: string | null): string {
  const context = [
    "任务目标上下文（由用户显式提供，并由 UniDesk CLI 在 Target 上受控探测目录存在）：",
    `- 中文任务标题: ${preflight.title}`,
    `- MDTODO ID: ${preflight.mdtodoId}`,
    `- Target: ${preflight.target}`,
    `- targetWorkspace: ${preflight.targetWorkspace}`,
    `- targetRoute: ${preflight.targetRoute}`,
    `- repository: ${preflight.repository}`,
    `- ref: ${preflight.ref}`,
    "- sourceAuthority: explicit-target-context（不读取 Git 状态，不把 dirty/ahead/behind/diff 作为门禁或警告）",
    "",
    "执行边界：",
    `- 目标源码的探测、Git、编辑、验证和运行面观察全部使用 \`trans ${preflight.target}:${preflight.targetWorkspace} <operation>\` 或对应 k3s route。`,
    "- runner primary workspace 只承载 Artificer 默认受控 resource bundle、工具与 skill；不得把它当作目标源码，也不得用容器本地结果替代 Target 原入口证据。",
    `- 最终报告必须回链 MDTODO ${preflight.mdtodoId}。`,
  ].join("\n");
  return userPrompt === null || userPrompt.trim().length === 0 ? context : `${context}\n\n${userPrompt}`;
}

export function targetTaskDisclosure(preflight: AgentRunTargetTaskPreflight): Record<string, unknown> {
  return {
    title: preflight.title,
    mdtodoId: preflight.mdtodoId,
    target: preflight.target,
    targetWorkspace: preflight.targetWorkspace,
    targetRoute: preflight.targetRoute,
    repository: preflight.repository,
    ref: preflight.ref,
    projectId: preflight.projectId,
    workspace: {
      root: preflight.workspaceRoot,
      gitStateInspected: false,
      valuesPrinted: false,
    },
    sourceAuthority: preflight.sourceAuthority,
    mutation: false,
    valuesPrinted: false,
  };
}

export function targetTaskOperationEvidence(
  preflight: AgentRunTargetTaskPreflight,
  operation: AgentRunTargetTaskOperation,
): AgentRunTargetTaskOperationEvidence {
  const payloadHash = sha256(operation.prompt);
  const scope = {
    tenantId: operation.tenantId,
    projectId: operation.projectId,
    aipod: operation.aipod,
    title: preflight.title,
    mdtodoId: preflight.mdtodoId,
    target: preflight.target,
    repository: preflight.repository,
    ref: preflight.ref,
    targetWorkspace: preflight.targetWorkspace,
  };
  const fingerprint = sha256Hex(JSON.stringify({ version: "v1", scope, payloadHash }));
  return {
    version: "v1",
    fingerprint,
    payloadHash,
    scope,
    reason: "canonical-logical-operation",
    valuesPrinted: false,
  };
}

export function resolveTargetTaskIdempotency(
  preflight: AgentRunTargetTaskPreflight,
  operation: AgentRunTargetTaskOperation,
  providedKey: string | null,
): { readonly idempotencyKey: string; readonly evidence: AgentRunTargetTaskOperationEvidence } {
  const evidence = targetTaskOperationEvidence(preflight, operation);
  const idempotencyKey = `artificer-target:${evidence.fingerprint}`;
  if (providedKey !== null && providedKey !== idempotencyKey) {
    throw new AgentRunTargetTaskPreflightError(
      "idempotency-key-conflict",
      "显式 --idempotency-key 与当前 Artificer logical operation 不匹配；不同 payload 不得复用同一 key。",
      {
        code: "target-task-idempotency-key-conflict",
        reason: "provided-key-does-not-match-canonical-logical-operation",
        canonicalIdempotencyKey: idempotencyKey,
        logicalOperationFingerprint: evidence.fingerprint,
        payloadHash: evidence.payloadHash,
        providedKeyFingerprint: sha256(providedKey),
        mutation: false,
        valuesPrinted: false,
      },
    );
  }
  return { idempotencyKey, evidence };
}

export function targetTaskIdempotencyKey(
  preflight: AgentRunTargetTaskPreflight,
  operation: AgentRunTargetTaskOperation,
): string {
  return resolveTargetTaskIdempotency(preflight, operation, null).idempotencyKey;
}

export function validateChineseTaskTitle(raw: string | null): string {
  if (raw === null || raw.trim().length === 0) {
    throw new AgentRunTargetTaskPreflightError(
      "task-title",
      "Artificer 创建任务必须提供 --title <中文短标题>，例如 --title \"修复 HWLAB 的 cicd\"。",
      { reason: "missing-chinese-task-title" },
    );
  }
  if (/\r|\n|[\u0000-\u001f\u007f]/u.test(raw)) {
    throw new AgentRunTargetTaskPreflightError("task-title", "Artificer 任务标题必须是单行短字符串。", {
      reason: "invalid-task-title-control-character",
    });
  }
  const title = raw.trim().replace(/\s+/gu, " ");
  const length = Array.from(title).length;
  if (length < 2 || length > 80) {
    throw new AgentRunTargetTaskPreflightError("task-title", "Artificer 任务标题长度必须为 2 到 80 个字符。", {
      reason: "invalid-task-title-length",
      length,
    });
  }
  if (!isChineseTaskTitle(title)) {
    throw new AgentRunTargetTaskPreflightError(
      "task-title",
      "Artificer 任务标题必须至少包含一个中文汉字；HWLAB、CI/CD 等专有名和空格可以保留。",
      { reason: "missing-han-character" },
    );
  }
  return title;
}

export function isChineseTaskTitle(value: string | undefined): value is string {
  return typeof value === "string" && /\p{Script=Han}/u.test(value);
}

function option(args: string[], name: string): string | null {
  const flag = `--${name}`;
  for (let index = 0; index < args.length; index += 1) {
    const arg = args[index] ?? "";
    if (arg === flag) {
      const value = args[index + 1];
      if (value === undefined || value.startsWith("--")) {
        throw new AgentRunTargetTaskPreflightError("target-context", `${flag} requires a value`);
      }
      return value;
    }
    if (arg.startsWith(`${flag}=`)) return arg.slice(flag.length + 1);
  }
  return null;
}

function validateTarget(value: string): string {
  if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u.test(value)) {
    throw new AgentRunTargetTaskPreflightError("target-context", "--target 必须是 YAML 声明的 Target ID");
  }
  return value;
}

function validateTargetWorkspace(value: string): string {
  if (!value.startsWith("/") || value.includes("\0")) {
    throw new AgentRunTargetTaskPreflightError("target-context", "--target-workspace 必须是 Target 上的绝对路径");
  }
  return value.replace(/\/+$/u, "") || "/";
}

function normalizeRepository(value: string): string {
  const normalized = value
    .replace(/^https:\/\/github\.com\//u, "")
    .replace(/^git@github\.com:/u, "")
    .replace(/\.git$/u, "");
  if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(normalized)) {
    throw new AgentRunTargetTaskPreflightError("target-context", "--repo 必须使用 owner/repo 或等价 GitHub URL");
  }
  return normalized;
}

function validateRef(value: string): string {
  const disallowed = /[\u0000-\u0020~^:?*\\\[]/u;
  if (value.length === 0 || value.length > 255 || value.startsWith("-") || disallowed.test(value)
    || value.includes("..") || value.endsWith(".") || value.endsWith("/") || value.includes("//")) {
    throw new AgentRunTargetTaskPreflightError("target-context", "--ref 不是安全的 Git ref");
  }
  return value;
}

function sha256(value: string): string {
  return `sha256:${sha256Hex(value)}`;
}

function sha256Hex(value: string): string {
  return createHash("sha256").update(value).digest("hex");
}
