import assert from "node:assert/strict";
import { normalizeDataProductDefinition } from "../src/data-product-policy.mjs";

const input = {
  id: "fleet.positions.current.v1",
  version: "1.0.0",
  ontologyRevision: "ontology.map.v1",
  deliveryMode: "snapshot+patch",
  semanticTypes: ["vehicle.trike", "map.moving_object"],
  fields: ["status", "source_id", "observed_at", "geometry"],
  history: {
    mode: "sampled",
    intervalMs: 60_000,
    strategy: "latest-per-entity-per-bucket",
    retentionDays: 365,
  },
};
const definition = normalizeDataProductDefinition(input);
assert.equal(definition.history.mode, "sampled");
assert.equal(definition.history.intervalMs, 60_000);
assert.deepEqual(definition.semanticTypes, ["map.moving_object", "vehicle.trike"]);
assert.deepEqual(definition.fields, ["geometry", "observed_at", "source_id", "status"]);
assert.deepEqual(input.semanticTypes, ["vehicle.trike", "map.moving_object"]);
assert.deepEqual(input.fields, ["status", "source_id", "observed_at", "geometry"]);
assert.equal(Object.isFrozen(definition.semanticTypes), true);
assert.equal(Object.isFrozen(definition.fields), true);
assert.deepEqual(definition.fieldContracts, {});
assert.equal(Object.isFrozen(definition.fieldContracts), true);

const reordered = normalizeDataProductDefinition({
  ...input,
  semanticTypes: [...input.semanticTypes].reverse(),
  fields: [...input.fields].reverse(),
});
assert.deepEqual(reordered.semanticTypes, definition.semanticTypes);
assert.deepEqual(reordered.fields, definition.fields);

assert.throws(() => normalizeDataProductDefinition({ ...definition, providerId: "gelios" }), /data_product_definition_invalid/);
assert.throws(() => normalizeDataProductDefinition({ ...definition, history: { mode: "none", intervalMs: 1000 } }), /history_policy_none_has_sampling_fields/);

for (const semanticTypes of [
  undefined,
  "map.moving_object",
  [],
  ["map.moving_object", null],
  ["map.moving_object", "Map.invalid"],
]) {
  assert.throws(
    () => normalizeDataProductDefinition({ ...input, semanticTypes }),
    /data_product_definition_(semantic_types_invalid|shape_invalid)/,
  );
}
assert.throws(
  () => normalizeDataProductDefinition({ ...input, semanticTypes: ["map.moving_object", "map.moving_object"] }),
  /data_product_definition_semantic_types_duplicate/,
);
assert.throws(
  () => normalizeDataProductDefinition({ ...input, semanticTypes: ["map.moving_object", " map.moving_object "] }),
  /data_product_definition_semantic_types_duplicate/,
);

for (const fields of [
  undefined,
  "source_id",
  [],
  ["source_id", null],
  ["source_id", "Invalid"],
]) {
  assert.throws(
    () => normalizeDataProductDefinition({ ...input, fields }),
    /data_product_definition_(fields_invalid|shape_invalid)/,
  );
}
assert.throws(
  () => normalizeDataProductDefinition({ ...input, fields: ["source_id", "source_id"] }),
  /data_product_definition_fields_duplicate/,
);
assert.throws(
  () => normalizeDataProductDefinition({ ...input, fields: ["source_id", " source_id "] }),
  /data_product_definition_fields_duplicate/,
);

const strict = normalizeDataProductDefinition({
  ...input,
  fields: ["geometry", "movement_state", "signal_state", "speed_kph"],
  fieldContracts: {
    geometry: { type: "point", required: false },
    movement_state: { type: "string", required: true, enum: ["stopped", "moving"] },
    signal_state: { type: "string", required: true, enum: ["inactive", "active"] },
    speed_kph: { type: "number", required: false, minimum: 0 },
  },
});
assert.deepEqual(strict.fieldContracts.movement_state.enum, ["moving", "stopped"]);
assert.equal(Object.isFrozen(strict.fieldContracts), true);
assert.equal(Object.isFrozen(strict.fieldContracts.signal_state), true);
assert.equal(Object.isFrozen(strict.fieldContracts.signal_state.enum), true);

const telemetry = normalizeDataProductDefinition({
  ...input,
  fields: ["sensor_readings"],
  fieldContracts: {
    sensor_readings: { type: "telemetry_readings", required: false },
  },
});
assert.equal(telemetry.fieldContracts.sensor_readings.type, "telemetry_readings");

assert.throws(
  () => normalizeDataProductDefinition({
    ...input,
    fieldContracts: { status: { type: "string", required: true } },
  }),
  /data_product_field_contracts_must_match_fields/,
);
assert.throws(
  () => normalizeDataProductDefinition({
    ...input,
    fieldContracts: Object.fromEntries(input.fields.map((field) => [
      field,
      field === "status"
        ? { type: "string", required: true, enum: ["active", 1] }
        : { type: field === "geometry" ? "point" : "string", required: false },
    ])),
  }),
  /data_product_field_contract_enum_invalid/,
);
assert.throws(
  () => normalizeDataProductDefinition({
    ...strict,
    fieldContracts: {
      ...strict.fieldContracts,
      speed_kph: { type: "number", required: false, minimum: 10, maximum: 1 },
    },
  }),
  /data_product_field_contract_range_invalid/,
);

console.log("external-data-plane data product policy: ok");
