import assert from "node:assert/strict";
import test from "node:test";

import {
  buildCommanderReadinessSummary,
  commitsMatch,
  evaluateArtifactRuntimeReadiness,
  normalizeCommit
} from "./src/artifact-runtime-readiness-guard.mjs";

const targetSha = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
const staleSha = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
const digest = "sha256:1111111111111111111111111111111111111111111111111111111111111111";

function artifactReport(sourceCommitId = targetSha, overrides = {}) {
  return {
    commitId: sourceCommitId.slice(0, 7),
    artifactPublish: {
      status: "published",
      sourceCommitId,
      serviceCount: 2,
      requiredServiceCount: 2,
      publishedCount: 2,
      registryPrefix: "127.0.0.1:5000/hwlab",
      services: [
        {
          serviceId: "hwlab-cloud-api",
          artifactRequired: true,
          status: "published",
          imageTag: sourceCommitId.slice(0, 7),
          digest
        },
        {
          serviceId: "hwlab-cloud-web",
          artifactRequired: true,
          status: "published",
          imageTag: sourceCommitId.slice(0, 7),
          digest,
          runtimeKind: "cloud-web",
          distFreshness: { status: "pass" }
        }
      ],
      ...overrides
    }
  };
}

function artifactCatalog(commitId = targetSha, {
  ciPublished = true,
  registryVerified = true,
  serviceDigest = digest,
  artifactState = "published"
} = {}) {
  return {
    artifactState,
    commitId,
    publish: {
      ciPublished,
      registryVerified
    },
    services: [
      {
        serviceId: "hwlab-cloud-api",
        commitId,
        artifactRequired: true,
        publishState: serviceDigest === "not_published" ? "skeleton-only" : "published",
        imageTag: commitId.slice(0, 7),
        digest: serviceDigest
      },
      {
        serviceId: "hwlab-cloud-web",
        commitId,
        artifactRequired: true,
        publishState: serviceDigest === "not_published" ? "skeleton-only" : "published",
        imageTag: commitId.slice(0, 7),
        digest: serviceDigest
      }
    ]
  };
}

function desiredStatePlan(commitId = targetSha, state = "already_promoted", status = "pass") {
  return {
    status,
    summary: {
      desiredCommitId: commitId,
      desiredImageTag: commitId.slice(0, 7),
      diagnostics: status === "blocked" ? 1 : 0,
      blockers: status === "blocked" ? 1 : 0
    },
    target: {
      convergence: {
        state,
        comparableFields: 8,
        matchingTargetFields: state === "already_promoted" ? 8 : 0,
        pendingTargetFields: state === "already_promoted" ? 0 : 8
      }
    },
    services: [
      {
        serviceId: "hwlab-cloud-web",
        catalog: { commitId }
      }
    ]
  };
}

function liveRuntime(commitId = targetSha, { cloudWebCommitId = commitId, apiCommitId = commitId } = {}) {
  return {
    mode: "live-read-only-http",
    api: {
      status: "observed",
      source: "health-live",
      evidenceSource: "live-http",
      endpoint: "http://74.48.78.17:16667/health/live",
      serviceId: "hwlab-cloud-api",
      environment: "dev",
      healthStatus: "ok",
      commitId: apiCommitId,
      shortCommitId: apiCommitId.slice(0, 7),
      commitSource: "runtime-env",
      imageTag: apiCommitId.slice(0, 7),
      observedAt: "2026-05-22T00:00:00.000Z"
    },
    cloudWeb: {
      status: "observed",
      source: "health-live",
      evidenceSource: "live-http",
      endpoint: "http://74.48.78.17:16666/health/live",
      serviceId: "hwlab-cloud-web",
      environment: "dev",
      healthStatus: "ok",
      artifactKind: "cloud-web",
      commitId: cloudWebCommitId,
      shortCommitId: cloudWebCommitId.slice(0, 7),
      commitSource: "runtime-env",
      imageTag: cloudWebCommitId.slice(0, 7),
      observedAt: "2026-05-22T00:00:00.000Z"
    }
  };
}

function readiness(overrides = {}) {
  return evaluateArtifactRuntimeReadiness({
    artifactReport: artifactReport(overrides.artifactSource ?? targetSha, overrides.artifactReportOverrides),
    artifactCatalog: artifactCatalog(overrides.catalogCommit ?? targetSha, overrides.catalogOptions ?? {}),
    desiredStatePlan: overrides.desiredPlan ?? desiredStatePlan(overrides.desiredCommit ?? targetSha, overrides.desiredState ?? "already_promoted", overrides.desiredStatus ?? "pass"),
    runtimeReport: null,
    liveRuntime: overrides.live ?? liveRuntime(targetSha),
    targetRef: "origin/main",
    targetCommitId: overrides.targetCommit ?? targetSha
  });
}

test("commit comparison accepts full and short lowercase SHA forms only", () => {
  assert.equal(normalizeCommit("ABCDEF1"), "abcdef1");
  assert.equal(normalizeCommit("not-a-sha"), "unknown");
  assert.equal(commitsMatch(targetSha, targetSha.slice(0, 7)), true);
  assert.equal(commitsMatch(targetSha, "unknown"), false);
});

test("readiness passes only when artifact, desired-state, API runtime, and Cloud Web runtime match target", () => {
  const result = readiness();
  assert.equal(result.ready, true);
  assert.equal(result.canInferFromSourceLocalStatic, false);
  assert.deepEqual(result.blockers, []);
});

test("stale local artifact report cannot imply latest-main readiness", () => {
  const result = readiness({ targetCommit: staleSha });
  assert.equal(result.ready, false);
  assert.equal(result.canInferFromSourceLocalStatic, false);
  assert.deepEqual(
    result.blockers.map((item) => item.id),
    [
      "artifact-report-target-match",
      "artifact-catalog-target-match",
      "desired-state-target-match",
      "api-runtime-target-match",
      "cloud-web-runtime-target-match"
    ]
  );
});

test("desired-state promotion pending blocks before apply", () => {
  const result = readiness({
    desiredCommit: staleSha,
    desiredState: "promotion_pending",
    desiredStatus: "planned"
  });
  assert.equal(result.ready, false);
  assert.equal(result.desiredState.targetConvergence.state, "promotion_pending");
  assert.ok(result.blockers.some((item) => item.id === "desired-state-target-match"));
});

test("unpublished catalog digests keep readiness blocked", () => {
  const result = readiness({
    catalogOptions: {
      artifactState: "contract-skeleton",
      ciPublished: false,
      registryVerified: false,
      serviceDigest: "not_published"
    }
  });
  assert.equal(result.ready, false);
  assert.equal(result.catalog.ciPublished, false);
  assert.equal(result.catalog.registryVerified, false);
  assert.deepEqual(
    result.blockers.map((item) => item.id),
    ["artifact-catalog-published"]
  );
});

test("Cloud Web served runtime identity must match artifact and target", () => {
  const result = readiness({
    live: liveRuntime(targetSha, { cloudWebCommitId: staleSha })
  });
  assert.equal(result.ready, false);
  assert.ok(result.blockers.some((item) => item.id === "cloud-web-runtime-target-match"));
  assert.ok(result.blockers.some((item) => item.id === "cloud-web-runtime-artifact-match"));
  assert.equal(result.runtime.cloudWeb.effective.shortCommitId, staleSha.slice(0, 7));
});

test("Cloud Web publish report must retain build freshness evidence", () => {
  const result = readiness({
    artifactReportOverrides: {
      services: [
        {
          serviceId: "hwlab-cloud-api",
          artifactRequired: true,
          status: "published",
          imageTag: targetSha.slice(0, 7),
          digest
        },
        {
          serviceId: "hwlab-cloud-web",
          artifactRequired: true,
          status: "published",
          imageTag: targetSha.slice(0, 7),
          digest,
          runtimeKind: "cloud-web"
        }
      ]
    }
  });
  assert.equal(result.ready, false);
  assert.equal(result.artifact.cloudWeb.distFreshnessStatus, "missing");
  assert.ok(result.blockers.some((item) => item.id === "cloud-web-published-build-freshness"));
});

test("compact commander summary exposes canPublish/canApply and dry-run devLiveClaim false", () => {
  const result = readiness();
  const summary = buildCommanderReadinessSummary({
    readiness: result,
    targetRef: "origin/main",
    generatedFromCommit: targetSha,
    explicitLive: false,
    cloudWebDistFreshness: { status: "pass", mismatches: [] },
    codeAgentTimeoutReadiness: {
      status: "pass",
      frontendTimeoutMs: 1800000,
      backendTimeoutMs: 1200000,
      requiredFrontendTimeoutMs: 35000,
      requiredBackendTimeoutMs: 1200000,
      transportWaitsForBackend: true,
      summary: "ready"
    },
    runtimeReport: {
      runtimeIdentity: {
        ready: true,
        runtime: { durable: true, ready: true },
        readiness: { ready: true, durability: { ready: true } }
      }
    }
  });

  assert.equal(summary.kind, "hwlab-dev-rollout-readiness");
  assert.equal(summary.canPublish, true);
  assert.equal(summary.canApply, true);
  assert.equal(summary.devLiveClaim, false);
  assert.deepEqual(summary.blockedReasons, []);
  assert.equal(summary.currentSourceCommit.shortCommitId, targetSha.slice(0, 7));
});

test("compact summary blocks publish on stale Cloud Web source dist", () => {
  const result = readiness();
  const summary = buildCommanderReadinessSummary({
    readiness: result,
    targetRef: "origin/main",
    generatedFromCommit: targetSha,
    explicitLive: false,
    cloudWebDistFreshness: { status: "blocked", mismatches: ["index.html"] },
    codeAgentTimeoutReadiness: {
      status: "pass",
      frontendTimeoutMs: 1800000,
      backendTimeoutMs: 1200000,
      requiredFrontendTimeoutMs: 35000,
      requiredBackendTimeoutMs: 1200000,
      transportWaitsForBackend: true,
      summary: "ready"
    },
    runtimeReport: {
      runtimeIdentity: {
        ready: true,
        runtime: { durable: true, ready: true },
        readiness: { ready: true, durability: { ready: true } }
      }
    }
  });

  assert.equal(summary.canPublish, false);
  assert.equal(summary.canApply, false);
  assert.equal(summary.cloudWebBuildFreshness.status, "blocked");
  assert.ok(summary.blockedReasons.some((item) => item.id === "cloud-web-build-freshness"));
  assert.ok(summary.nextCommands.includes("bun run --cwd web/hwlab-cloud-web build"));
});

test("compact summary separates Code Agent timeout and durable runtime blockers from apply readiness", () => {
  const result = readiness();
  const summary = buildCommanderReadinessSummary({
    readiness: result,
    targetRef: "origin/main",
    generatedFromCommit: targetSha,
    explicitLive: false,
    cloudWebDistFreshness: { status: "pass", mismatches: [] },
    codeAgentTimeoutReadiness: {
      status: "blocked",
      frontendTimeoutMs: 4500,
      backendTimeoutMs: 1200000,
      requiredFrontendTimeoutMs: 35000,
      requiredBackendTimeoutMs: 1200000,
      transportWaitsForBackend: false,
      summary: "Code Agent browser timeout is too short."
    },
    runtimeReport: {
      runtimeIdentity: {
        ready: false,
        runtime: {
          durable: false,
          ready: false,
          blocker: "runtime_durable_adapter_query_blocked",
          connection: { queryResult: "query_blocked" }
        },
        readiness: {
          ready: false,
          durability: {
            ready: false,
            blocker: "runtime_durable_adapter_query_blocked",
            blockedLayer: "durability_query",
            queryResult: "query_blocked"
          }
        },
        blockerCodes: ["runtime_durable_adapter_query_blocked"]
      }
    }
  });

  assert.equal(summary.canPublish, false);
  assert.equal(summary.canApply, false);
  assert.equal(summary.codeAgentTimeoutReadiness.status, "blocked");
  assert.equal(summary.runtimeDurableBlocker.classification, "runtime_durable_adapter_query_blocked");
  assert.ok(summary.blockedReasons.some((item) => item.id === "code-agent-timeout-readiness"));
  assert.ok(summary.blockedReasons.some((item) => item.id === "runtime-durable:runtime_durable_adapter_query_blocked"));
});

test("compact summary blocks publish when Code Agent transport timeout does not exceed backend timeout", () => {
  const result = readiness();
  const summary = buildCommanderReadinessSummary({
    readiness: result,
    targetRef: "origin/main",
    generatedFromCommit: targetSha,
    explicitLive: false,
    cloudWebDistFreshness: { status: "pass", mismatches: [] },
    codeAgentTimeoutReadiness: {
      status: "blocked",
      frontendTimeoutMs: 1200000,
      backendTimeoutMs: 1200000,
      requiredFrontendTimeoutMs: 35000,
      requiredBackendTimeoutMs: 1200000,
      transportWaitsForBackend: false,
      summary: "Code Agent timeout layering is unsafe."
    },
    runtimeReport: {
      runtimeIdentity: {
        ready: true,
        runtime: { durable: true, ready: true },
        readiness: { ready: true, durability: { ready: true } }
      }
    }
  });

  assert.equal(summary.canPublish, false);
  assert.equal(summary.canApply, false);
  assert.equal(summary.codeAgentTimeoutReadiness.transportWaitsForBackend, false);
  assert.ok(summary.blockedReasons.some((item) => item.id === "code-agent-timeout-readiness"));
});

test("compact summary keeps provider-secret blockers out of publish but blocks apply", () => {
  const result = readiness();
  const summary = buildCommanderReadinessSummary({
    readiness: result,
    targetRef: "origin/main",
    generatedFromCommit: targetSha,
    explicitLive: false,
    cloudWebDistFreshness: { status: "pass", mismatches: [] },
    codeAgentTimeoutReadiness: {
      status: "pass",
      frontendTimeoutMs: 1800000,
      backendTimeoutMs: 1200000,
      requiredFrontendTimeoutMs: 35000,
      requiredBackendTimeoutMs: 1200000,
      transportWaitsForBackend: true,
      summary: "ready"
    },
    edgeReport: {
      blockers: [
        {
          scope: "code-agent-provider-secret",
          classification: "provider_secret_missing",
          summary: "Code Agent provider Secret is missing."
        }
      ]
    },
    runtimeReport: {
      runtimeIdentity: {
        ready: true,
        runtime: { durable: true, ready: true },
        readiness: { ready: true, durability: { ready: true } }
      }
    }
  });

  assert.equal(summary.canPublish, true);
  assert.equal(summary.canApply, false);
  assert.equal(summary.codeAgentProviderReadiness.status, "blocked");
  assert.ok(summary.blockedReasons.some((item) => item.id === "code-agent-provider:code-agent-provider-secret"));
});
