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

export interface AgentRunTargetTaskRequest {
  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 currentBranch: string;
  readonly headCommit: string;
  readonly clean: true;
  readonly originRepository: string;
  readonly verifiedCommit: string;
  readonly matchedRemoteRef: string;
  readonly refRelation: "branch-and-head-match-remote-ref";
  readonly sourceAuthority: {
    readonly kind: "target-workspace-origin";
    readonly credentialScope: "target-owned-git-credential";
    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 = {
    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(", ")}；请同时提供 --target、--target-workspace、--repo、--ref 和 --mdtodo-id。`,
      { missing },
    );
  }
  return {
    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: validateMdtodoId(raw.mdtodoId as string),
  };
}

export function preflightTargetTask(request: AgentRunTargetTaskRequest, markerEnv: string): AgentRunTargetTaskPreflight {
  if (process.env[markerEnv] !== request.target) {
    throw new AgentRunTargetTaskPreflightError(
      "target-execution",
      `Target 预检只能在外层 trans 重入 ${request.target} 后执行；当前缺少受控执行标记 ${markerEnv}。`,
      { target: request.target, markerEnv, targetRoute: `${request.target}:${request.targetWorkspace}` },
    );
  }
  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 },
    );
  }

  const gitRoot = git(request, ["rev-parse", "--show-toplevel"], "workspace-git-root").trim();
  let observedRoot: string;
  try {
    observedRoot = realpathSync(gitRoot);
  } catch {
    throw new AgentRunTargetTaskPreflightError(
      "workspace-git-root",
      `Target Git 返回了无效工作区根目录：${gitRoot}`,
      { targetWorkspace: request.targetWorkspace },
    );
  }
  if (observedRoot !== workspaceRoot) {
    throw new AgentRunTargetTaskPreflightError(
      "workspace-git-root",
      `--target-workspace 必须指向 Git 工作区根目录；当前根目录是 ${gitRoot}`,
      { targetWorkspace: request.targetWorkspace, observedWorkspaceRoot: gitRoot },
    );
  }

  const originUrl = git(request, ["remote", "get-url", "origin"], "origin").trim();
  const originRepository = repositoryFromRemote(originUrl);
  if (originRepository === null || originRepository.toLowerCase() !== request.repository.toLowerCase()) {
    throw new AgentRunTargetTaskPreflightError(
      "origin",
      `Target 工作区 origin 与 --repo 不一致；期望 ${request.repository}。`,
      {
        expectedRepository: request.repository,
        observedRepository: originRepository,
        observedRemoteFingerprint: sha256(originUrl),
      },
    );
  }

  const status = git(request, ["status", "--porcelain=v1", "--untracked-files=normal"], "workspace-status");
  const dirtyEntryCount = status.split(/\r?\n/u).filter(Boolean).length;
  if (dirtyEntryCount > 0) {
    throw new AgentRunTargetTaskPreflightError(
      "workspace-status",
      "Target 固定工作区存在未提交修改；请先保存并按语义提交并行改动，再重新派单。",
      { targetWorkspace: request.targetWorkspace, dirtyEntryCount },
    );
  }

  const currentBranch = git(request, ["branch", "--show-current"], "workspace-branch").trim() || "(detached)";
  const headCommit = git(request, ["rev-parse", "HEAD"], "workspace-head").trim().toLowerCase();
  const remote = git(request, ["ls-remote", "--exit-code", "origin", request.ref], "remote-ref");
  const refs = remote.split(/\r?\n/u)
    .map((line) => line.trim().split(/\s+/u))
    .filter((parts) => /^[0-9a-f]{40}$/u.test(parts[0] ?? "") && (parts[1] ?? "").length > 0)
    .map((parts) => ({ commit: parts[0] as string, ref: parts[1] as string }));
  const selected = refs.find((item) => item.ref === `refs/heads/${request.ref}`) ?? refs[0];
  if (selected === undefined) {
    throw new AgentRunTargetTaskPreflightError(
      "remote-ref",
      `Target 工作区 origin 中找不到 --ref ${request.ref}。`,
      { repository: request.repository, ref: request.ref },
    );
  }
  if (currentBranch !== request.ref || headCommit !== selected.commit.toLowerCase()) {
    throw new AgentRunTargetTaskPreflightError(
      "workspace-ref-relation",
      "Target 任务 worktree 必须检出 --ref，且 HEAD 必须等于 origin 远端提交；请先完成分支切换、提交和推送。",
      {
        targetWorkspace: request.targetWorkspace,
        currentBranch,
        expectedBranch: request.ref,
        headCommit,
        remoteCommit: selected.commit,
        matchedRemoteRef: selected.ref,
      },
    );
  }

  return {
    ...request,
    targetRoute: `${request.target}:${request.targetWorkspace}`,
    projectId: request.repository,
    workspaceRoot,
    currentBranch,
    headCommit,
    clean: true,
    originRepository,
    verifiedCommit: selected.commit,
    matchedRemoteRef: selected.ref,
    refRelation: "branch-and-head-match-remote-ref",
    sourceAuthority: {
      kind: "target-workspace-origin",
      credentialScope: "target-owned-git-credential",
      valuesPrinted: false,
    },
  };
}

export function targetTaskPrompt(preflight: AgentRunTargetTaskPreflight, userPrompt: string | null): string {
  const context = [
    "任务目标上下文（由 UniDesk YAML-first CLI 在创建 task 前经 Target trans 只读核验）：",
    `- MDTODO ID: ${preflight.mdtodoId}`,
    `- Target: ${preflight.target}`,
    `- targetWorkspace: ${preflight.targetWorkspace}`,
    `- targetRoute: ${preflight.targetRoute}`,
    `- repository: ${preflight.repository}`,
    `- ref: ${preflight.ref}`,
    `- verifiedCommit: ${preflight.verifiedCommit}`,
    `- refRelation: ${preflight.refRelation}`,
    "- sourceAuthority: target-workspace-origin（凭据值不进入 task、prompt 或日志）",
    "",
    "执行边界：",
    `- 目标源码的探测、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 {
    mdtodoId: preflight.mdtodoId,
    target: preflight.target,
    targetWorkspace: preflight.targetWorkspace,
    targetRoute: preflight.targetRoute,
    repository: preflight.repository,
    ref: preflight.ref,
    projectId: preflight.projectId,
    verifiedCommit: preflight.verifiedCommit,
    matchedRemoteRef: preflight.matchedRemoteRef,
    refRelation: preflight.refRelation,
    workspace: {
      root: preflight.workspaceRoot,
      branch: preflight.currentBranch,
      headCommit: preflight.headCommit,
      clean: preflight.clean,
    },
    sourceAuthority: preflight.sourceAuthority,
    mutation: false,
    valuesPrinted: false,
  };
}

export function targetTaskIdempotencyKey(preflight: AgentRunTargetTaskPreflight): string {
  const identity = [
    preflight.target,
    preflight.targetWorkspace,
    preflight.repository,
    preflight.ref,
    preflight.mdtodoId,
    preflight.verifiedCommit,
  ].join("\n");
  return `artificer-target:${createHash("sha256").update(identity).digest("hex")}`;
}

function git(request: AgentRunTargetTaskRequest, args: string[], stage: string): string {
  const result = spawnSync("git", args, {
    cwd: request.targetWorkspace,
    encoding: "utf8",
    timeout: 15_000,
    env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
    maxBuffer: 1024 * 1024,
  });
  if (result.error !== undefined || result.status !== 0) {
    const stderr = String(result.stderr ?? "").trim().split(/\r?\n/u)[0] ?? "";
    throw new AgentRunTargetTaskPreflightError(
      stage,
      `Target Git 只读预检失败（${stage}）；请先在 ${request.target}:${request.targetWorkspace} 修复 origin/ref/凭据可达性。`,
      {
        target: request.target,
        targetWorkspace: request.targetWorkspace,
        repository: request.repository,
        ref: request.ref,
        exitCode: result.status,
        timedOut: result.error?.name === "ETIMEDOUT",
        stderrSummary: redactGitError(stderr),
      },
    );
  }
  return String(result.stdout ?? "");
}

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 validateMdtodoId(value: string): string {
  if (!/^R[1-9][0-9]*(?:\.[1-9][0-9]*)*$/u.test(value)) {
    throw new AgentRunTargetTaskPreflightError("target-context", "--mdtodo-id 必须使用 R1 或 R1.1 这类 MDTODO ID");
  }
  return value;
}

function repositoryFromRemote(value: string): string | null {
  const normalized = value.replace(/\.git$/u, "");
  const match = normalized.match(/^(?:git@github\.com:|https:\/\/github\.com\/)([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)$/u);
  return match?.[1] ?? null;
}

function redactGitError(value: string): string {
  return value
    .replace(/https?:\/\/[^@\s]+@/gu, "https://[redacted]@")
    .replace(/(token|password|authorization)[=:][^\s]+/giu, "$1=[redacted]")
    .slice(0, 240);
}

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