import { spawnSync } from "node:child_process";

import { ENVIRONMENT_DEV, SERVICE_IDS } from "../../internal/protocol/index.mjs";

const envVarName = "HWLAB_DEV_BASE_IMAGE";
const dockerCommand = "docker";
const dockerTimeoutMs = 15_000;
const node20TagPattern = /^20-/u;
const recommendedNode20Image = "node:20-bookworm-slim";
const recommendedHwlabBaseImage = "127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim";
const recommendedPreloadTar = "hwlab-node20-bookworm-slim.tar";
const forbiddenImageFragments = Object.freeze([
  "unidesk",
  "backend-core",
  "provider-gateway",
  "microservice-proxy",
  "code-queue"
]);
const allowedExplicitBaseImages = Object.freeze([
  "node:20-*",
  "hwlab-dev-base:*",
  "*/hwlab-dev-base:*",
  "hwlab-node20-base:*",
  "*/hwlab-node20-base:*",
  "hwlab-node-runtime-base:*",
  "*/hwlab-node-runtime-base:*"
]);

function baseImageRecommendation(envValue = recommendedNode20Image) {
  return {
    source: "Preloaded D601 local Docker cache or D601-local registry tag.",
    envVar: envVarName,
    preferredImageTag: recommendedNode20Image,
    preferredHwlabImageTag: recommendedHwlabBaseImage,
    recommendedEnvValue: envValue
  };
}

function provisionPlan({ exactBlocker = null, envValue = recommendedNode20Image, required = true } = {}) {
  return {
    required,
    exactBlocker,
    expectedImageTag: recommendedNode20Image,
    recommendedEnv: {
      name: envVarName,
      value: envValue,
      assignment: `${envVarName}=${envValue}`
    },
    recommendedHwlabImageTag: recommendedHwlabBaseImage,
    nextHumanCommand: required ? `docker pull ${recommendedNode20Image}` : null,
    commands: {
      d601OnlinePreload: [
        `docker pull ${recommendedNode20Image}`,
        `${envVarName}=${recommendedNode20Image} node scripts/preflight-dev-base-image.mjs`
      ],
      d601HwlabTagProvision: [
        `docker pull ${recommendedNode20Image}`,
        `docker tag ${recommendedNode20Image} ${recommendedHwlabBaseImage}`,
        `${envVarName}=${recommendedHwlabBaseImage} node scripts/preflight-dev-base-image.mjs`
      ],
      offlineSourceExport: [
        `docker pull ${recommendedNode20Image}`,
        `docker save ${recommendedNode20Image} -o ${recommendedPreloadTar}`
      ],
      offlineD601Import: [
        `docker load -i ${recommendedPreloadTar}`,
        `${envVarName}=${recommendedNode20Image} node scripts/preflight-dev-base-image.mjs`
      ],
      artifactPublishDryRun: [
        `${envVarName}=${envValue} node scripts/artifact-publish.mjs --preflight --no-report`
      ]
    }
  };
}

function blockedReportFor(exactBlocker) {
  const plan = provisionPlan({ exactBlocker });
  return {
    exactBlocker,
    expectedImageTag: plan.expectedImageTag,
    recommendedEnv: plan.recommendedEnv,
    recommendedHwlabImageTag: plan.recommendedHwlabImageTag,
    nextHumanCommand: plan.nextHumanCommand,
    preloadCommands: plan.commands.d601OnlinePreload,
    offlinePreloadCommands: {
      sourceExport: plan.commands.offlineSourceExport,
      d601Import: plan.commands.offlineD601Import
    }
  };
}

function runDocker(args) {
  const result = spawnSync(dockerCommand, args, {
    encoding: "utf8",
    timeout: dockerTimeoutMs
  });

  return {
    ok: result.status === 0,
    status: result.status,
    signal: result.signal,
    errorCode: result.error?.code ?? null,
    stdout: result.stdout?.trim() ?? "",
    stderr: result.stderr?.trim() ?? "",
    timedOut: result.error?.code === "ETIMEDOUT"
  };
}

function splitJsonLines(stdout) {
  return stdout
    .split("\n")
    .map((line) => line.trim())
    .filter(Boolean)
    .map((line) => JSON.parse(line));
}

function candidateTag(image) {
  return `${image.Repository}:${image.Tag}`;
}

function imageRefFromInspect(requestedImage, inspectDoc) {
  const repoTags = Array.isArray(inspectDoc.RepoTags) ? inspectDoc.RepoTags : [];
  if (repoTags.includes(requestedImage)) {
    return requestedImage;
  }
  return repoTags.find((tag) => !tag.includes("<none>")) ?? requestedImage;
}

function isNode20BaseRef(imageRef) {
  const lowerRef = imageRef.toLowerCase();
  return lowerRef.startsWith("node:20-");
}

function isHwlabDevBaseRef(imageRef) {
  const lowerRef = imageRef.toLowerCase();
  return /(^|\/)(hwlab-dev-base|hwlab-node20-base|hwlab-node-runtime-base)(?=[:@]|$)/u.test(lowerRef);
}

function isTaggedImage(image) {
  return image.Repository && image.Tag && image.Repository !== "<none>" && image.Tag !== "<none>";
}

function approvalForBaseRef(imageRef) {
  if (isNode20BaseRef(imageRef)) {
    return {
      approved: true,
      reason: "local Node 20 builder base image"
    };
  }
  if (isHwlabDevBaseRef(imageRef)) {
    return {
      approved: true,
      reason: "explicit HWLAB DEV builder base image"
    };
  }
  return {
    approved: false,
    reason: `image reference is not one of the approved builder base patterns: ${allowedExplicitBaseImages.join(", ")}`
  };
}

function forbiddenReasons(imageRef) {
  const lowerRef = imageRef.toLowerCase();
  const reasons = [];

  for (const fragment of forbiddenImageFragments) {
    if (lowerRef.includes(fragment)) {
      reasons.push(`image reference contains forbidden runtime/build substitute fragment: ${fragment}`);
    }
  }

  for (const serviceId of SERVICE_IDS) {
    if (
      lowerRef.includes(`/pikastech/${serviceId}:`) ||
      lowerRef.includes(`/pikastech/${serviceId}@`) ||
      lowerRef.startsWith(`${serviceId}:`) ||
      lowerRef.startsWith(`${serviceId}@`)
    ) {
      reasons.push(`image reference points at HWLAB service artifact ${serviceId}, not a builder base image`);
    }
  }

  return reasons;
}

function inspectEvidenceStrings(inspectDoc) {
  const labels = inspectDoc.Config?.Labels ?? {};
  const env = Array.isArray(inspectDoc.Config?.Env) ? inspectDoc.Config.Env : [];
  return [
    ...(Array.isArray(inspectDoc.RepoTags) ? inspectDoc.RepoTags : []),
    ...(Array.isArray(inspectDoc.RepoDigests) ? inspectDoc.RepoDigests : []),
    ...Object.entries(labels).map(([key, value]) => `${key}=${value}`),
    ...env
  ].filter((value) => typeof value === "string" && value.length > 0);
}

function inspectEvidenceBlockers(inspectDoc) {
  const blockers = [];
  for (const evidence of inspectEvidenceStrings(inspectDoc)) {
    for (const reason of forbiddenReasons(evidence)) {
      blockers.push(`${reason} in image inspect evidence ${JSON.stringify(evidence)}`);
    }
  }
  return [...new Set(blockers)];
}

function baseResult(overrides) {
  return {
    preflight: "hwlab-dev-base-image",
    environment: ENVIRONMENT_DEV,
    status: "blocked",
    publishUsable: false,
    artifactPublish: {
      issue: "#35",
      usable: false,
      reason: "No approved local DEV builder base image was selected."
    },
    imageSource: "none",
    requestedImage: null,
    localTag: null,
    imageId: null,
    repoDigests: [],
    candidates: [],
    rejectedCandidates: [],
    blockers: [],
    nextSteps: [],
    recommendation: baseImageRecommendation(),
    provision: provisionPlan(),
    blockedReport: null,
    constraints: {
      localOnly: true,
      doesNotPull: true,
      doesNotBuild: true,
      doesNotPush: true,
      doesNotDeploy: true,
      doesNotReadSecrets: true,
      allowedExplicitBaseImages,
      forbiddenImageFragments,
      forbiddenHwlabServiceImages: SERVICE_IDS
    },
    ...overrides
  };
}

function blocked(overrides) {
  const exactBlocker = overrides.blockers?.length
    ? overrides.blockers.join("; ")
    : "DEV builder base image preflight did not return ready.";
  const envValue = overrides.recommendedEnvValue ?? overrides.requestedImage ?? recommendedNode20Image;
  return baseResult({
    status: "blocked",
    publishUsable: false,
    artifactPublish: {
      issue: "#35",
      usable: false,
      reason: "DEV artifact publish must stop until the blocker is resolved."
    },
    recommendation: baseImageRecommendation(envValue),
    provision: provisionPlan({ exactBlocker, envValue }),
    blockedReport: blockedReportFor(exactBlocker),
    ...overrides
  });
}

function ready({ imageSource, approval, requestedImage, localTag, inspectDoc, candidates }) {
  return baseResult({
    status: "ready",
    publishUsable: true,
    artifactPublish: {
      issue: "#35",
      usable: true,
      reason: "A local approved DEV builder base image is available."
    },
    imageSource,
    approval,
    requestedImage,
    localTag,
    imageId: inspectDoc.Id ?? null,
    repoDigests: Array.isArray(inspectDoc.RepoDigests) ? inspectDoc.RepoDigests : [],
    candidates,
    recommendation: baseImageRecommendation(localTag),
    provision: provisionPlan({
      envValue: localTag,
      required: false
    }),
    blockedReport: null,
    blockers: [],
    nextSteps: [
      "Use the reported localTag as the DEV builder base image for artifact publish.",
      "Keep publish DEV-only and record the produced HWLAB service image tags and digests separately."
    ]
  });
}

function inspectLocalImage(imageRef) {
  const inspect = runDocker(["image", "inspect", imageRef]);
  if (!inspect.ok) {
    return {
      ok: false,
      inspect,
      docs: []
    };
  }

  return {
    ok: true,
    inspect,
    docs: JSON.parse(inspect.stdout)
  };
}

function dockerUnavailableResult(versionResult) {
  const reason = versionResult.timedOut
    ? "Docker CLI timed out during version detection."
    : "Docker CLI is not available in this runner.";
  return blocked({
    blockers: [reason],
    nextSteps: [
      "Run this preflight in the D601 builder environment with Docker available.",
      `Preload an approved local Node 20 builder image and set ${envVarName}=<local-image> when using a non-node:20-* tag.`,
      "Do not use a UniDesk, Code Queue, backend-core, provider-gateway, or microservice-proxy runtime image as a substitute."
    ],
    containerEngine: {
      command: dockerCommand,
      available: false,
      version: null,
      errorCode: versionResult.errorCode,
      stderr: versionResult.stderr
    }
  });
}

function candidateSource(image) {
  if (image.Repository === "node" && node20TagPattern.test(image.Tag)) {
    return "local-docker-node:20-*";
  }
  if (isHwlabDevBaseRef(candidateTag(image))) {
    return "local-docker-hwlab-dev-base";
  }
  return null;
}

function candidatePriority(candidate) {
  if (candidate.source === "local-docker-node:20-*") return 0;
  if (candidate.source === "local-docker-hwlab-dev-base") return 1;
  return 2;
}

function rejectedBaseCandidate(image) {
  if (!isTaggedImage(image)) return null;
  const localTag = candidateTag(image);
  if (image.Repository === "node" && !node20TagPattern.test(image.Tag)) {
    return {
      source: "local-docker-node",
      localTag,
      imageId: image.ID ?? null,
      reason: "local Node image is not Node 20 and is not approved as the HWLAB DEV builder base image"
    };
  }
  return null;
}

function selectLocalBaseCandidate(images) {
  const candidates = images
    .filter(isTaggedImage)
    .map((image) => {
      const source = candidateSource(image);
      if (!source) return null;
      const localTag = candidateTag(image);
      return {
        source,
        approval: approvalForBaseRef(localTag),
        localTag,
        imageId: image.ID ?? null,
        size: image.Size ?? null,
        createdSince: image.CreatedSince ?? null
      };
    })
    .filter(Boolean)
    .map((image) => ({
      ...image,
      priority: candidatePriority(image)
    }))
    .sort((left, right) => (
      left.priority - right.priority || left.localTag.localeCompare(right.localTag)
    ))
    .map(({ priority, ...candidate }) => candidate);

  const rejectedCandidates = images
    .map(rejectedBaseCandidate)
    .filter(Boolean)
    .sort((left, right) => left.localTag.localeCompare(right.localTag));

  return {
    candidates,
    rejectedCandidates,
    selected: candidates[0] ?? null
  };
}

function explicitImageBlockers(requestedImage, localTag) {
  const approval = approvalForBaseRef(localTag);
  const requestedApproval = approvalForBaseRef(requestedImage);
  if (approval.approved || requestedApproval.approved) {
    return {
      approval: approval.approved ? approval : requestedApproval,
      blockers: []
    };
  }

  return {
    approval,
    blockers: [approval.reason]
  };
}

function readyFromExplicitImage({ requestedImage, inspectDoc, containerEngine }) {
  const localTag = imageRefFromInspect(requestedImage, inspectDoc);
  const localTagBlockers = forbiddenReasons(localTag);
  const evidenceBlockers = inspectEvidenceBlockers(inspectDoc);
  const localTagApproval = explicitImageBlockers(requestedImage, localTag);
  const blockers = [...localTagBlockers, ...evidenceBlockers, ...localTagApproval.blockers];
  if (blockers.length > 0) {
    return blocked({
      imageSource: `env:${envVarName}`,
      requestedImage,
      localTag,
      imageId: inspectDoc.Id ?? null,
      blockers,
      recommendedEnvValue: recommendedNode20Image,
      nextSteps: [
        `Set ${envVarName} to an approved local builder base image, not a runtime service image.`,
        `Allowed references are local node:20-* tags or HWLAB dev base tags such as ${recommendedHwlabBaseImage}.`,
        "Do not continue #35 artifact publish with this image."
      ],
      containerEngine
    });
  }

  return {
    ...ready({
      imageSource: `env:${envVarName}`,
      approval: localTagApproval.approval,
      requestedImage,
      localTag,
      inspectDoc,
      candidates: [
        {
          source: `env:${envVarName}`,
          approval: localTagApproval.approval,
          localTag,
          imageId: inspectDoc.Id ?? null
        }
      ]
    }),
    containerEngine
  };
}

export async function runDevBaseImagePreflight({ env = process.env } = {}) {
  const requestedImage = env[envVarName]?.trim() || null;
  const version = runDocker(["--version"]);
  if (!version.ok) {
    return dockerUnavailableResult(version);
  }

  const containerEngine = {
    command: dockerCommand,
    available: true,
    version: version.stdout,
    errorCode: null,
    stderr: ""
  };

  if (requestedImage) {
    const refBlockers = forbiddenReasons(requestedImage);
    const requestedApproval = approvalForBaseRef(requestedImage);
    if (refBlockers.length > 0 || !requestedApproval.approved) {
      return blocked({
        imageSource: `env:${envVarName}`,
        requestedImage,
        blockers: [
          ...refBlockers,
          ...(requestedApproval.approved ? [] : [requestedApproval.reason])
        ],
        recommendedEnvValue: recommendedNode20Image,
        nextSteps: [
          `Set ${envVarName} to an approved local builder base image, not a runtime service image.`,
          `Allowed references are local node:20-* tags or HWLAB dev base tags such as ${recommendedHwlabBaseImage}.`,
          "Do not continue #35 artifact publish with this image."
        ],
        containerEngine
      });
    }

    const inspected = inspectLocalImage(requestedImage);
    if (!inspected.ok || inspected.docs.length === 0) {
      return blocked({
        imageSource: `env:${envVarName}`,
        requestedImage,
        blockers: [`${envVarName} is set to ${requestedImage}, but that image is not present in the local Docker image cache.`],
        nextSteps: [
          `Preload ${recommendedNode20Image} or tag it as ${recommendedHwlabBaseImage} before artifact publish.`,
          `Keep ${envVarName} pointed at the exact local tag once the image is available.`,
          "Do not pull from third-party hosting or substitute a UniDesk runtime image during this preflight."
        ],
        containerEngine
      });
    }

    return readyFromExplicitImage({
      requestedImage,
      inspectDoc: inspected.docs[0],
      containerEngine
    });
  }

  const imageList = runDocker(["image", "ls", "--format", "{{json .}}"]);
  if (!imageList.ok) {
    return blocked({
      blockers: ["Docker image list failed; local base-image cache could not be inspected."],
      nextSteps: [
        "Start or repair the local Docker daemon for the D601 builder environment.",
        `Then set ${envVarName}=<approved-local-image> or provide a local node:20-* image.`,
        "Do not claim #35 artifact publish readiness until this preflight returns ready."
      ],
      containerEngine: {
        ...containerEngine,
        stderr: imageList.stderr,
        errorCode: imageList.errorCode
      }
    });
  }

  const { candidates, rejectedCandidates, selected } = selectLocalBaseCandidate(splitJsonLines(imageList.stdout));
  if (!selected) {
    return blocked({
      blockers: [`${envVarName} is not set and no approved local DEV builder base image was found in the Docker image cache; expected ${recommendedNode20Image} or ${recommendedHwlabBaseImage}.`],
      candidates,
      rejectedCandidates,
      nextSteps: [
        `Preload ${recommendedNode20Image} into the D601 Docker cache, or tag it as ${recommendedHwlabBaseImage}.`,
        `Set ${envVarName}=${recommendedNode20Image} or ${envVarName}=${recommendedHwlabBaseImage} when invoking artifact publish.`,
        "Rerun this preflight and require status=ready before #35 artifact publish.",
        "Do not use UniDesk runtime, Code Queue runner, backend-core, provider-gateway, or microservice-proxy images as substitutes."
      ],
      containerEngine
    });
  }

  const inspected = inspectLocalImage(selected.localTag);
  if (!inspected.ok || inspected.docs.length === 0) {
    return blocked({
      imageSource: selected.source,
      requestedImage: null,
      localTag: selected.localTag,
      candidates,
      rejectedCandidates,
      blockers: [`Selected local candidate ${selected.localTag} could not be inspected.`],
      nextSteps: [
        `Set ${envVarName} to a known-good approved local base image and rerun this preflight.`,
        "Do not continue #35 artifact publish until image inspect succeeds."
      ],
      containerEngine
    });
  }
  const evidenceBlockers = inspectEvidenceBlockers(inspected.docs[0]);
  if (evidenceBlockers.length > 0) {
    return blocked({
      imageSource: selected.source,
      requestedImage: null,
      localTag: selected.localTag,
      candidates,
      rejectedCandidates,
      imageId: inspected.docs[0].Id ?? null,
      blockers: evidenceBlockers,
      nextSteps: [
        `Replace ${selected.localTag} with a clean approved Node 20 builder base image.`,
        "Do not continue #35 artifact publish with an image whose inspect metadata points at a forbidden runtime substitute."
      ],
      containerEngine
    });
  }

  return {
    ...ready({
      imageSource: selected.source,
      approval: selected.approval,
      requestedImage: null,
      localTag: selected.localTag,
      inspectDoc: inspected.docs[0],
      candidates
    }),
    rejectedCandidates,
    containerEngine
  };
}

export function unexpectedPreflightErrorResult(error) {
  const message = error instanceof Error ? error.message : String(error);
  return blocked({
    blockers: [`Unexpected preflight error: ${message}`],
    nextSteps: [
      "Treat this as a blocker and inspect the preflight command output.",
      "Do not continue #35 artifact publish until the preflight returns status=ready."
    ]
  });
}

export function formatPreflightResult(result) {
  return JSON.stringify(result, null, 2);
}

export function exitCodeForPreflight(result) {
  return result.publishUsable ? 0 : 2;
}

export function summarizeForRuntimeBase(result) {
  return {
    status: result.status,
    publishUsable: result.publishUsable,
    imageSource: result.imageSource,
    requestedImage: result.requestedImage,
    localTag: result.localTag,
    imageId: result.imageId,
    blockers: result.blockers,
    nextSteps: result.nextSteps,
    containerEngine: result.containerEngine
  };
}
