import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { mkdtemp, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { promisify } from "node:util";

import { SERVICE_IDS } from "../internal/protocol/index.mjs";
import { writeStructuredFile } from "./src/structured-config.mjs";

const execFileAsync = promisify(execFile);
const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
const V02_SERVICE_IDS = Object.freeze([
  "hwlab-cloud-api",
  "hwlab-cloud-web",
  "hwlab-gateway",
  "hwlab-edge-proxy",
  "hwlab-agent-skills"
]);

async function git(args) {
  const result = await execFileAsync("git", args, {
    cwd: repoRoot,
    timeout: 5000,
    maxBuffer: 1024 * 1024
  });
  return result.stdout.trim();
}

function digestFor(index) {
  return `sha256:${String(index + 1).padStart(64, "0")}`;
}

function envInputHashFor(index) {
  return String(index + 1).padStart(64, "a");
}

test("refresh accepts an absolute publish report path", async () => {
  const commitId = await git(["rev-parse", "HEAD"]);
  const shortCommitId = await git(["rev-parse", "--short=7", "HEAD"]);
  const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-"));
  const reportPath = path.join(tempDir, "dev-artifacts.json");
  await writeFile(reportPath, `${JSON.stringify({
    reportVersion: "v1",
    taskId: "artifact-publish",
    commitId: shortCommitId,
    artifactPublish: {
      status: "published",
      mode: "publish",
      sourceCommitId: commitId,
      registryPrefix: "127.0.0.1:5000/hwlab",
      serviceCount: SERVICE_IDS.length,
      requiredServiceCount: SERVICE_IDS.length,
      disabledServiceCount: 0,
      publishedCount: SERVICE_IDS.length,
      publishPlan: {
        version: "v2",
        services: SERVICE_IDS.map((serviceId, index) => ({
          serviceId,
          required: true,
          digest: digestFor(index),
          image: `127.0.0.1:5000/hwlab/${serviceId}:${shortCommitId}`,
          imageTag: shortCommitId
        }))
      },
      services: SERVICE_IDS.map((serviceId, index) => ({
        serviceId,
        status: "published",
        artifactRequired: true,
        image: `127.0.0.1:5000/hwlab/${serviceId}:${shortCommitId}`,
        imageTag: shortCommitId,
        digest: digestFor(index),
        repositoryDigest: `127.0.0.1:5000/hwlab/${serviceId}@${digestFor(index)}`,
        buildCreatedAt: "2026-05-24T00:00:00.000Z",
        buildSource: `pikasTech/HWLAB@${commitId}`,
        componentCommitId: commitId,
        componentInputHash: `${String(index + 1).padStart(64, "a")}`,
        dockerfileHash: `${String(index + 1).padStart(64, "b")}`,
        buildArgsHash: `${String(index + 1).padStart(64, "c")}`,
        ciAffected: index === 0,
        ciReason: index === 0 ? ["component-path-changed"] : ["component-inputs-unchanged"]
      }))
    }
  }, null, 2)}\n`);

  const result = await execFileAsync(process.execPath, [
    "scripts/refresh-artifact-catalog.mjs",
    "--target-ref",
    "HEAD",
    "--publish-report",
    reportPath,
    "--no-write"
  ], {
    cwd: repoRoot,
    timeout: 15000,
    maxBuffer: 10 * 1024 * 1024
  });
  const payload = JSON.parse(result.stdout);
  assert.equal(payload.status, "published");
  assert.equal(payload.publishedCount, SERVICE_IDS.length);
  assert.deepEqual(payload.wrote, []);
  assert.equal(payload.services[0].componentCommitId, commitId);
  assert.equal(payload.services[0].ciAffected, true);
  assert.equal(payload.services[0].componentInputHash, `${String(1).padStart(64, "a")}`);
});

test("refresh is read-only by default", async () => {
  const commitId = await git(["rev-parse", "HEAD"]);
  const shortCommitId = await git(["rev-parse", "--short=7", "HEAD"]);
  const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-default-readonly-"));
  const reportPath = path.join(tempDir, "dev-artifacts.json");
  await writeFile(reportPath, `${JSON.stringify({
    reportVersion: "v1",
    taskId: "artifact-publish",
    commitId: shortCommitId,
    artifactPublish: {
      status: "published",
      mode: "publish",
      sourceCommitId: commitId,
      registryPrefix: "127.0.0.1:5000/hwlab",
      serviceCount: SERVICE_IDS.length,
      requiredServiceCount: SERVICE_IDS.length,
      disabledServiceCount: 0,
      publishedCount: SERVICE_IDS.length,
      publishPlan: {
        version: "v2",
        services: SERVICE_IDS.map((serviceId, index) => ({
          serviceId,
          required: true,
          digest: digestFor(index),
          image: `127.0.0.1:5000/hwlab/${serviceId}:${shortCommitId}`,
          imageTag: shortCommitId
        }))
      },
      services: SERVICE_IDS.map((serviceId, index) => ({
        serviceId,
        status: "published",
        artifactRequired: true,
        image: `127.0.0.1:5000/hwlab/${serviceId}:${shortCommitId}`,
        imageTag: shortCommitId,
        digest: digestFor(index),
        repositoryDigest: `127.0.0.1:5000/hwlab/${serviceId}@${digestFor(index)}`
      }))
    }
  }, null, 2)}\n`);

  const result = await execFileAsync(process.execPath, [
    "scripts/refresh-artifact-catalog.mjs",
    "--target-ref",
    "HEAD",
    "--publish-report",
    reportPath
  ], {
    cwd: repoRoot,
    timeout: 15000,
    maxBuffer: 10 * 1024 * 1024
  });
  const payload = JSON.parse(result.stdout);
  assert.equal(payload.status, "published");
  assert.deepEqual(payload.wrote, []);
  assert.equal(payload.deployTruthPolicy, "deploy.yaml is human-authored runtime config; artifact promotion must not rewrite it");
});

test("refresh keeps reused service image tags from the publish report", async () => {
  const commitId = await git(["rev-parse", "HEAD"]);
  const shortCommitId = await git(["rev-parse", "--short=7", "HEAD"]);
  const reusedTag = "d42c77d";
  const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-reused-"));
  const reportPath = path.join(tempDir, "dev-artifacts.json");
  await writeFile(reportPath, `${JSON.stringify({
    reportVersion: "v1",
    taskId: "artifact-publish",
    commitId: shortCommitId,
    artifactPublish: {
      status: "published",
      mode: "publish",
      sourceCommitId: commitId,
      registryPrefix: "127.0.0.1:5000/hwlab",
      serviceCount: SERVICE_IDS.length,
      requiredServiceCount: SERVICE_IDS.length,
      disabledServiceCount: 0,
      publishedCount: 1,
      reusedCount: SERVICE_IDS.length - 1,
      publishPlan: {
        version: "v2",
        services: SERVICE_IDS.map((serviceId, index) => ({
          serviceId,
          required: true,
          status: index === 0 ? "published" : "reused",
          digest: digestFor(index),
          image: `127.0.0.1:5000/hwlab/${serviceId}:${index === 0 ? shortCommitId : reusedTag}`,
          imageTag: index === 0 ? shortCommitId : reusedTag
        }))
      },
      services: SERVICE_IDS.map((serviceId, index) => ({
        serviceId,
        status: index === 0 ? "published" : "reused",
        artifactRequired: true,
        commitId: index === 0 ? shortCommitId : reusedTag,
        sourceCommitId: index === 0 ? commitId : reusedTag,
        image: `127.0.0.1:5000/hwlab/${serviceId}:${index === 0 ? shortCommitId : reusedTag}`,
        imageTag: index === 0 ? shortCommitId : reusedTag,
        digest: digestFor(index),
        repositoryDigest: `127.0.0.1:5000/hwlab/${serviceId}@${digestFor(index)}`,
        buildCreatedAt: "2026-05-24T00:00:00.000Z",
        buildSource: index === 0 ? `pikasTech/HWLAB@${commitId}` : `pikasTech/HWLAB@${reusedTag}`,
        componentCommitId: index === 0 ? commitId : reusedTag,
        componentInputHash: `${String(index + 1).padStart(64, "d")}`,
        dockerfileHash: `${String(index + 1).padStart(64, "e")}`,
        buildArgsHash: `${String(index + 1).padStart(64, "f")}`,
        ciAffected: index === 0,
        ciReason: index === 0 ? ["component-path-changed"] : ["component-inputs-unchanged"],
        reusedFrom: index === 0 ? null : `catalog:${reusedTag}`
      }))
    }
  }, null, 2)}\n`);

  const result = await execFileAsync(process.execPath, [
    "scripts/refresh-artifact-catalog.mjs",
    "--target-ref",
    "HEAD",
    "--publish-report",
    reportPath,
    "--no-write"
  ], {
    cwd: repoRoot,
    timeout: 15000,
    maxBuffer: 10 * 1024 * 1024
  });
  const payload = JSON.parse(result.stdout);
  assert.equal(payload.status, "published");
  assert.equal(payload.publishedCount, 1);
  assert.equal(payload.reusedCount, SERVICE_IDS.length - 1);
  assert.equal(payload.services[0].imageTag, shortCommitId);
  assert.equal(payload.services[1].imageTag, reusedTag);
  assert.equal(payload.services[1].publishState, "reused");
  assert.equal(payload.services[1].reusedFrom, `catalog:${reusedTag}`);
});

test("v02 refresh accepts current runtime service full image tags", async () => {
  const commitId = await git(["rev-parse", "HEAD"]);
  const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-v02-runtime-services-"));
  const reportPath = path.join(tempDir, "v02-artifacts.json");
  await writeFile(reportPath, `${JSON.stringify({
    reportVersion: "v1",
    taskId: "v02-artifact-publish",
    commitId,
    artifactPublish: {
      status: "published",
      mode: "publish",
      sourceCommitId: commitId,
      registryPrefix: "127.0.0.1:5000/hwlab",
      serviceCount: V02_SERVICE_IDS.length,
      requiredServiceCount: V02_SERVICE_IDS.length,
      disabledServiceCount: 0,
      publishedCount: V02_SERVICE_IDS.length,
      reusedCount: 0,
      serviceInventory: {
        version: "v2",
        serviceCount: V02_SERVICE_IDS.length,
        requiredServiceCount: V02_SERVICE_IDS.length,
        disabledServiceCount: 0,
        requiredServiceIds: V02_SERVICE_IDS,
        disabledServiceIds: [],
        services: V02_SERVICE_IDS.map((serviceId) => ({
          serviceId,
          publishEnabled: true,
          artifactRequired: true,
          artifactScope: "required",
          runtimeKind: "node-command",
          implementationState: "repo-entrypoint",
          sourceState: "source-present",
          entrypoint: `cmd/${serviceId}/main.ts`,
          disabledReason: null
        }))
      },
      publishPlan: {
        version: "v2",
        services: V02_SERVICE_IDS.map((serviceId, index) => ({
          serviceId,
          required: true,
          digest: digestFor(index),
          image: `127.0.0.1:5000/hwlab/${serviceId}:${commitId}`,
          imageTag: commitId
        }))
      },
      services: V02_SERVICE_IDS.map((serviceId, index) => ({
        serviceId,
        status: "published",
        artifactRequired: true,
        commitId,
        sourceCommitId: commitId,
        image: `127.0.0.1:5000/hwlab/${serviceId}:${commitId}`,
        imageTag: commitId,
        digest: digestFor(index),
        repositoryDigest: `127.0.0.1:5000/hwlab/${serviceId}@${digestFor(index)}`
      }))
    }
  }, null, 2)}\n`);

  const result = await execFileAsync(process.execPath, [
    "scripts/refresh-artifact-catalog.mjs",
    "--lane",
    "v02",
    "--image-tag-mode",
    "full",
    "--target-ref",
    "HEAD",
    "--catalog-path",
    path.join(tempDir, "artifact-catalog.v02.json"),
    "--publish-report",
    reportPath,
    "--no-write"
  ], {
    cwd: repoRoot,
    timeout: 15000,
    maxBuffer: 10 * 1024 * 1024
  });
  const payload = JSON.parse(result.stdout);
  assert.equal(payload.status, "published");
  assert.equal(payload.publishedCount, V02_SERVICE_IDS.length);
  assert.equal(payload.reusedCount, 0);
  assert.deepEqual(payload.services.map((service) => service.serviceId), V02_SERVICE_IDS);
  for (const service of payload.services) {
    assert.equal(service.imageTag, commitId);
  }
});

test("v03 refresh accepts runtime lane config and env-reuse image tags", async () => {
  const commitId = await git(["rev-parse", "HEAD"]);
  const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-v03-runtime-services-"));
  const deployPath = path.join(tempDir, "deploy.yaml");
  const catalogPath = path.join(tempDir, "artifact-catalog.v03.json");
  const reportPath = path.join(tempDir, "v03-artifacts.json");
  const serviceIds = V02_SERVICE_IDS;
  await writeStructuredFile(tempDir, deployPath, {
    environment: "dev",
    namespace: "hwlab-dev",
    endpoint: "http://74.48.78.17:16667",
    profiles: { dev: { enabled: true }, prod: { enabled: false } },
    lanes: {
      v03: {
        namespace: "hwlab-v03",
        endpoint: "https://hwlab-v03.74-48-78-17.nip.io",
        envReuseServices: serviceIds,
        serviceDeclarations: Object.fromEntries(serviceIds.map((serviceId) => [serviceId, {
          runtimeKind: "node-command",
          entrypoint: `cmd/${serviceId}/main.ts`,
          componentPaths: [`cmd/${serviceId}/`]
        }])),
        bootScripts: Object.fromEntries(serviceIds.map((serviceId) => [serviceId, `deploy/runtime/boot/${serviceId}.sh`]))
      }
    },
    services: SERVICE_IDS.map((serviceId) => ({ serviceId }))
  });
  await writeFile(reportPath, `${JSON.stringify({
    reportVersion: "v1",
    taskId: "v03-artifact-publish",
    commitId,
    artifactPublish: {
      status: "published",
      mode: "publish",
      sourceCommitId: commitId,
      registryPrefix: "127.0.0.1:5000/hwlab",
      serviceCount: serviceIds.length,
      requiredServiceCount: serviceIds.length,
      disabledServiceCount: 0,
      publishedCount: serviceIds.length,
      reusedCount: 0,
      serviceInventory: {
        version: "v2",
        serviceCount: serviceIds.length,
        requiredServiceCount: serviceIds.length,
        disabledServiceCount: 0,
        requiredServiceIds: serviceIds,
        disabledServiceIds: [],
        services: serviceIds.map((serviceId) => ({
          serviceId,
          publishEnabled: true,
          artifactRequired: true,
          artifactScope: "required",
          runtimeKind: "node-command",
          implementationState: "repo-entrypoint",
          sourceState: "source-present",
          entrypoint: `cmd/${serviceId}/main.ts`,
          disabledReason: null
        }))
      },
      publishPlan: {
        version: "v2",
        services: serviceIds.map((serviceId, index) => {
          const envTag = `env-${envInputHashFor(index).slice(0, 12)}`;
          return {
            serviceId,
            required: true,
            digest: digestFor(index),
            image: `127.0.0.1:5000/hwlab/${serviceId}-env:${envTag}`,
            imageTag: envTag
          };
        })
      },
      services: serviceIds.map((serviceId, index) => {
        const environmentInputHash = envInputHashFor(index);
        const envTag = `env-${environmentInputHash.slice(0, 12)}`;
        const image = `127.0.0.1:5000/hwlab/${serviceId}-env:${envTag}`;
        return {
          serviceId,
          status: "published",
          artifactRequired: true,
          runtimeMode: "env-reuse-git-mirror-checkout",
          envReuse: true,
          commitId,
          sourceCommitId: commitId,
          image,
          imageTag: envTag,
          digest: digestFor(index),
          repositoryDigest: `127.0.0.1:5000/hwlab/${serviceId}-env@${digestFor(index)}`,
          environmentImage: image,
          environmentDigest: digestFor(index),
          environmentInputHash,
          codeInputHash: envInputHashFor(index + serviceIds.length),
          bootRepo: "git@github.com:pikasTech/HWLAB.git",
          bootCommit: commitId,
          bootSh: `deploy/runtime/boot/${serviceId}.sh`
        };
      })
    }
  }, null, 2)}\n`);

  const result = await execFileAsync(process.execPath, [
    "scripts/refresh-artifact-catalog.mjs",
    "--lane",
    "v03",
    "--image-tag-mode",
    "full",
    "--target-ref",
    "HEAD",
    "--deploy-config",
    deployPath,
    "--catalog-path",
    catalogPath,
    "--publish-report",
    reportPath,
    "--no-write"
  ], {
    cwd: repoRoot,
    timeout: 15000,
    maxBuffer: 10 * 1024 * 1024
  });
  const payload = JSON.parse(result.stdout);
  assert.equal(payload.status, "published");
  assert.equal(payload.endpoint, "https://hwlab-v03.74-48-78-17.nip.io");
  assert.equal(payload.requiredServiceCount, serviceIds.length);
  assert.deepEqual(payload.services.map((service) => service.serviceId), serviceIds);
  assert.equal(payload.services.every((service) => service.runtimeMode === "env-reuse-git-mirror-checkout"), true);
  assert.equal(payload.services.every((service) => service.imageTag.startsWith("env-")), true);
});

test("v02 refresh follows the current deploy endpoint instead of the retired 19667 freeze", async () => {
  const commitId = await git(["rev-parse", "HEAD"]);
  const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-v02-https-endpoint-"));
  const deployPath = path.join(tempDir, "deploy.yaml");
  const catalogPath = path.join(tempDir, "artifact-catalog.v02.json");
  await writeStructuredFile(tempDir, deployPath, {
    environment: "dev",
    namespace: "hwlab-dev",
    endpoint: "http://74.48.78.17:16667",
    profiles: { dev: { enabled: true }, prod: { enabled: false } },
    lanes: {
      v02: {
        namespace: "hwlab-v02",
        endpoint: "https://hwlab.74-48-78-17.nip.io",
        envReuseServices: V02_SERVICE_IDS,
        serviceDeclarations: Object.fromEntries(V02_SERVICE_IDS.map((serviceId) => [serviceId, {
          runtimeKind: "node-command",
          entrypoint: `cmd/${serviceId}/main.ts`,
          componentPaths: [`cmd/${serviceId}/`]
        }])),
        bootScripts: Object.fromEntries(V02_SERVICE_IDS.map((serviceId) => [serviceId, `deploy/runtime/boot/${serviceId}.sh`]))
      }
    },
    services: SERVICE_IDS.map((serviceId) => ({ serviceId }))
  });
  await writeFile(catalogPath, `${JSON.stringify({
    catalogVersion: "v1",
    kind: "hwlab-artifact-catalog",
    environment: "v02",
    profile: "v02",
    namespace: "hwlab-v02",
    endpoint: "http://74.48.78.17:19667",
    commitId,
    artifactState: "contract-skeleton",
    publish: { registryPrefix: "127.0.0.1:5000/hwlab" },
    allowedProfiles: ["v02"],
    forbiddenProfiles: ["dev", "prod"],
    services: V02_SERVICE_IDS.map((serviceId) => ({
      serviceId,
      profile: "v02",
      namespace: "hwlab-v02",
      commitId,
      sourceCommitId: commitId,
      image: `127.0.0.1:5000/hwlab/${serviceId}:${commitId}`,
      imageTag: commitId,
      digest: "not_published"
    }))
  }, null, 2)}\n`);

  const result = await execFileAsync(process.execPath, [
    "scripts/refresh-artifact-catalog.mjs",
    "--lane",
    "v02",
    "--image-tag-mode",
    "full",
    "--target-ref",
    "HEAD",
    "--deploy-config",
    deployPath,
    "--catalog-path",
    catalogPath,
    "--blocked",
    "--no-write"
  ], {
    cwd: repoRoot,
    timeout: 15000,
    maxBuffer: 10 * 1024 * 1024
  });
  const payload = JSON.parse(result.stdout);
  assert.equal(payload.status, "blocked");
  assert.equal(payload.endpoint, "https://hwlab.74-48-78-17.nip.io");
});

test("v02 refresh reports current runtime records as reused", async () => {
  const commitId = await git(["rev-parse", "HEAD"]);
  const previousCommitId = "1234567890abcdef1234567890abcdef12345678";
  const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-v02-reuse-"));
  const reportPath = path.join(tempDir, "v02-artifacts.json");
  await writeFile(reportPath, `${JSON.stringify({
    reportVersion: "v1",
    taskId: "v02-artifact-publish",
    commitId,
    artifactPublish: {
      status: "published",
      mode: "publish",
      sourceCommitId: commitId,
      registryPrefix: "127.0.0.1:5000/hwlab",
      serviceCount: V02_SERVICE_IDS.length,
      requiredServiceCount: V02_SERVICE_IDS.length,
      disabledServiceCount: 0,
      publishedCount: 0,
      reusedCount: V02_SERVICE_IDS.length,
      serviceInventory: {
        version: "v2",
        serviceCount: V02_SERVICE_IDS.length,
        requiredServiceCount: V02_SERVICE_IDS.length,
        disabledServiceCount: 0,
        requiredServiceIds: V02_SERVICE_IDS,
        disabledServiceIds: [],
        services: V02_SERVICE_IDS.map((serviceId) => ({
          serviceId,
          publishEnabled: true,
          artifactRequired: true,
          artifactScope: "required",
          runtimeKind: "node-command",
          implementationState: "repo-entrypoint",
          sourceState: "source-present",
          entrypoint: `cmd/${serviceId}/main.ts`,
          disabledReason: null
        }))
      },
      publishPlan: {
        version: "v2",
        services: V02_SERVICE_IDS.map((serviceId, index) => ({
          serviceId,
          required: true,
          status: "reused",
          digest: digestFor(index),
          image: `127.0.0.1:5000/hwlab/${serviceId}:${previousCommitId}`,
          imageTag: previousCommitId
        }))
      },
      services: V02_SERVICE_IDS.map((serviceId, index) => ({
        serviceId,
        status: "reused",
        artifactRequired: true,
        commitId: previousCommitId,
        sourceCommitId: previousCommitId,
        image: `127.0.0.1:5000/hwlab/${serviceId}:${previousCommitId}`,
        imageTag: previousCommitId,
        digest: digestFor(index),
        repositoryDigest: `127.0.0.1:5000/hwlab/${serviceId}@${digestFor(index)}`,
        buildBackend: "reused-catalog",
        reusedFrom: `catalog:${previousCommitId}`
      }))
    }
  }, null, 2)}\n`);

  const result = await execFileAsync(process.execPath, [
    "scripts/refresh-artifact-catalog.mjs",
    "--lane",
    "v02",
    "--image-tag-mode",
    "full",
    "--target-ref",
    "HEAD",
    "--catalog-path",
    path.join(tempDir, "artifact-catalog.v02.json"),
    "--publish-report",
    reportPath,
    "--no-write"
  ], {
    cwd: repoRoot,
    timeout: 15000,
    maxBuffer: 10 * 1024 * 1024
  });
  const payload = JSON.parse(result.stdout);
  assert.equal(payload.status, "published");
  assert.equal(payload.publishedCount, 0);
  assert.equal(payload.reusedCount, V02_SERVICE_IDS.length);
  assert.deepEqual(payload.services.map((service) => service.serviceId), V02_SERVICE_IDS);
  for (const service of payload.services) {
    assert.equal(service.publishState, "reused");
    assert.equal(service.buildBackend, "reused-catalog");
    assert.equal(service.sourceCommitId, previousCommitId);
    assert.equal(service.imageTag, previousCommitId);
  }
});
