import assert from "node:assert/strict";
import { validateIntakeBatch } from "../../../packages/external-provider-contract/src/data-plane.mjs";
import {
  canMigrateManagedWriterBindingToDurable,
  createWriterToken,
  hashWriterToken,
  materializeDataProductPublish,
  materializeWriterBoundBatch,
  normalizeManagedWriterBindingRequest,
  normalizeWriterBindingRequest,
  safeWriterBinding,
  writerBindingRequestHash,
} from "../src/writer-binding.mjs";

const now = new Date("2026-07-15T12:00:00.000Z");
const request = {
  source: {
    tenantId: "tenant-01",
    connectionId: "connection-01",
    providerId: "example-provider",
  },
  allowedDataProductIds: ["fleet.positions.current.v1"],
  expiresAt: "2026-08-01T12:00:00.000Z",
};

const bindingPolicy = normalizeWriterBindingRequest(request, { now, maxTtlDays: 90 });
assert.deepEqual(bindingPolicy, {
  tenantId: "tenant-01",
  connectionId: "connection-01",
  providerId: "example-provider",
  allowedDataProductIds: ["fleet.positions.current.v1"],
  expiresAt: "2026-08-01T12:00:00.000Z",
});

const token = createWriterToken();
assert.match(token, /^ndc_edpwb_[A-Za-z0-9_-]{40,}$/);
assert.equal(hashWriterToken(token), hashWriterToken(token));
assert.notEqual(hashWriterToken(token), hashWriterToken(`${token}x`));

const managedPolicy = normalizeManagedWriterBindingRequest({
  ...request,
  expiresAt: null,
  allowedDataProductIds: ["fleet.positions.current.v1", "asset.status.current.v1"],
  generation: 1,
  capabilityDigest: hashWriterToken(token),
}, { now, maxTtlDays: 90 });
assert.deepEqual(managedPolicy.allowedDataProductIds, [
  "asset.status.current.v1",
  "fleet.positions.current.v1",
]);
assert.equal(managedPolicy.generation, 1);
assert.equal(managedPolicy.capabilityDigest, hashWriterToken(token));
assert.equal(managedPolicy.expiresAt, null);
assert.equal(canMigrateManagedWriterBindingToDurable({
  ...managedPolicy,
  expiresAt: "2026-08-01T12:00:00.000Z",
  active: true,
  capabilityDigest: managedPolicy.capabilityDigest,
}, managedPolicy, now), true);
assert.equal(canMigrateManagedWriterBindingToDurable({
  ...managedPolicy,
  expiresAt: "2026-07-01T12:00:00.000Z",
  active: true,
  capabilityDigest: managedPolicy.capabilityDigest,
}, managedPolicy, now), false);
assert.equal(writerBindingRequestHash(managedPolicy), writerBindingRequestHash({
  ...managedPolicy,
  allowedDataProductIds: [...managedPolicy.allowedDataProductIds].reverse(),
}));
assert.throws(() => normalizeManagedWriterBindingRequest({
  ...request,
  expiresAt: null,
  generation: 0,
  capabilityDigest: hashWriterToken(token),
}, { now, maxTtlDays: 90 }), /managed_writer_binding_generation_invalid/);
assert.throws(() => normalizeManagedWriterBindingRequest({
  ...request,
  expiresAt: null,
  generation: 1,
  capabilityDigest: "not-a-digest",
}, { now, maxTtlDays: 90 }), /managed_writer_binding_capability_digest_invalid/);
assert.throws(() => normalizeManagedWriterBindingRequest({
  ...request,
  expiresAt: null,
  generation: 1,
  capabilityDigest: hashWriterToken(token),
  token,
}, { now, maxTtlDays: 90 }), /managed_writer_binding_secret_material_forbidden/);
assert.throws(() => normalizeManagedWriterBindingRequest({
  ...request,
  generation: 1,
  capabilityDigest: hashWriterToken(token),
}), /managed_writer_binding_must_be_durable/);

const unscopedBatch = {
  schemaVersion: "nodedc.external-provider-contract/v1",
  source: { providerId: "example-provider" },
  contract: { dataProductId: "fleet.positions.current.v1", ontologyRevision: "example.v1", version: "1.0.0" },
  batch: { runId: "run-01", sequence: 0, idempotencyKey: "run-01.batch-0", receivedAt: "2026-07-15T12:00:00.000Z" },
  facts: [{ sourceId: "unit-01", semanticType: "map.moving_object", observedAt: "2026-07-15T12:00:00.000Z" }],
};
const bound = materializeWriterBoundBatch(unscopedBatch, { ...bindingPolicy, active: true }, { now });
assert.deepEqual(bound.source, { providerId: "example-provider", tenantId: "tenant-01", connectionId: "connection-01" });
assert.equal(validateIntakeBatch(bound).ok, true);
assert.throws(() => materializeWriterBoundBatch({
  ...unscopedBatch,
  source: { ...unscopedBatch.source, tenantId: "forged-tenant" },
}, { ...bindingPolicy, active: true }, { now }), /writer_bound_scope_forbidden/);
assert.throws(() => materializeWriterBoundBatch(unscopedBatch, { ...bindingPolicy, active: true }, {
  now,
  hasScopeHeaders: true,
}), /writer_bound_scope_forbidden/);
assert.throws(() => materializeWriterBoundBatch(unscopedBatch, {
  ...bindingPolicy,
  providerId: "other-provider",
  active: true,
}, { now }), /writer_binding_provider_forbidden/);
assert.throws(() => materializeWriterBoundBatch({
  ...unscopedBatch,
  contract: { ...unscopedBatch.contract, dataProductId: "other.product.v1" },
}, { ...bindingPolicy, active: true }, { now }), /writer_binding_data_product_forbidden/);
assert.throws(() => materializeWriterBoundBatch(unscopedBatch, { ...bindingPolicy, active: false }, { now }), /writer_binding_inactive/);

const materializedPublish = materializeDataProductPublish({
  schemaVersion: "nodedc.data-product.publish/v1",
  batch: { runId: "run-02", sequence: 0, idempotencyKey: "run-02.batch-0" },
  facts: unscopedBatch.facts,
}, { ...bindingPolicy, active: true }, {
  id: "fleet.positions.current.v1",
  version: "1.0.0",
  ontologyRevision: "ontology.example-fleet.v1",
}, "fleet.positions.current.v1", { now });
assert.deepEqual(materializedPublish.source, {
  tenantId: "tenant-01",
  connectionId: "connection-01",
  providerId: "example-provider",
});
assert.equal(materializedPublish.batch.receivedAt, now.toISOString());
assert.equal(materializedPublish.contract.version, "1.0.0");
assert.equal(validateIntakeBatch(materializedPublish).ok, true);
assert.throws(() => materializeDataProductPublish({
  schemaVersion: "nodedc.data-product.publish/v1",
  batch: { runId: "run-02", sequence: 0, idempotencyKey: "run-02.batch-0" },
  facts: unscopedBatch.facts,
}, { ...bindingPolicy, active: true }, {
  id: "other.product.v1",
  version: "1.0.0",
  ontologyRevision: "ontology.example-fleet.v1",
}, "other.product.v1", { now }), /writer_binding_data_product_forbidden/);

assert.throws(() => normalizeWriterBindingRequest({
  ...request,
  expiresAt: "2027-01-01T00:00:00.000Z",
}, { now, maxTtlDays: 90 }), /writer_binding_expiry_invalid/);
assert.throws(() => normalizeWriterBindingRequest({
  ...request,
  apiToken: "must-never-be-here",
}, { now, maxTtlDays: 90 }), /writer_binding_request_secret_material_forbidden/);
assert.throws(() => normalizeWriterBindingRequest({
  ...request,
  arbitraryRuntimeSetting: "must-not-be-accepted",
}, { now, maxTtlDays: 90 }), /writer_binding_request_fields_invalid/);

const safeBinding = safeWriterBinding({
  id: "binding-01",
  ...bindingPolicy,
  active: true,
  createdAt: now,
  token: "must-not-leak",
  tokenHash: "must-not-leak",
});
assert.equal("token" in safeBinding, false);
assert.equal("tokenHash" in safeBinding, false);
const safeManagedBinding = safeWriterBinding({
  id: "binding-managed-01",
  ...managedPolicy,
  active: true,
  createdAt: now,
});
assert.equal(safeManagedBinding.expiresAt, null);
assert.equal(materializeDataProductPublish({
  schemaVersion: "nodedc.data-product.publish/v1",
  batch: { runId: "run-durable", sequence: 0, idempotencyKey: "run-durable.batch-0" },
  facts: unscopedBatch.facts,
}, safeManagedBinding, {
  id: "fleet.positions.current.v1",
  version: "1.0.0",
  ontologyRevision: "ontology.example-fleet.v1",
}, "fleet.positions.current.v1", { now: new Date("2036-07-15T12:00:00.000Z") }).batch.runId, "run-durable");

console.log("external-data-plane writer bindings: ok");
