import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import {
  L2_CONNECTION_INSTANCE_SCHEMA_VERSION,
  instantiateL2Connection,
  validateDataProductPublish,
  validateProviderPackage,
} from "../src/index.mjs";
import {
  GELIOS_POSITIONS_DATA_PRODUCT_ID,
  GELIOS_POSITIONS_DATA_PRODUCT_VERSION,
  GELIOS_POSITIONS_ONTOLOGY_REVISION,
  GELIOS_UNIT_SOURCE_ID_PREFIX,
  geliosProviderPackageV1,
  geliosUnitsCurrentFixtureV1,
} from "../providers/gelios/v1/index.mjs";
import { geliosProviderPackageV2 } from "../providers/gelios/v2/index.mjs";
import { geliosProviderPackageV3 } from "../providers/gelios/v3/index.mjs";
import { geliosProviderPackageV4 } from "../providers/gelios/v4/index.mjs";
import { geliosProviderPackageV5 } from "../providers/gelios/v5/index.mjs";
import { geliosProviderPackageV6 } from "../providers/gelios/v6/index.mjs";
import { geliosProviderPackageV7 } from "../providers/gelios/v7/index.mjs";
import { geliosProviderPackageV8 } from "../providers/gelios/v8/index.mjs";
import { geliosProviderPackageV9 } from "../providers/gelios/v9/index.mjs";
import { geliosProviderPackageV10 } from "../providers/gelios/v10/index.mjs";
import { geliosProviderPackageV11 } from "../providers/gelios/v11/index.mjs";
import { geliosProviderPackageV12 } from "../providers/gelios/v12/index.mjs";
import { geliosProviderPackageV13 } from "../providers/gelios/v13/index.mjs";
import { normalizeDataProductDefinition } from "../../../services/external-data-plane/src/data-product-policy.mjs";

const expectedFields = [
  "course_degrees",
  "display_name",
  "elevation_meters",
  "geometry",
  "hdop",
  "horizontal_accuracy_meters",
  "object_kind",
  "operational_status",
  "position_source",
  "position_valid",
  "quality_flags",
  "satellite_count",
  "speed_kph",
];

assert.deepEqual(validateProviderPackage(geliosProviderPackageV1), { ok: true, errors: [] });
assert.deepEqual(validateProviderPackage(geliosProviderPackageV2), { ok: true, errors: [] });
assert.deepEqual(validateProviderPackage(geliosProviderPackageV3), { ok: true, errors: [] });
assert.deepEqual(validateProviderPackage(geliosProviderPackageV4), { ok: true, errors: [] });
assert.deepEqual(validateProviderPackage(geliosProviderPackageV5), { ok: true, errors: [] });
assert.deepEqual(validateProviderPackage(geliosProviderPackageV6), { ok: true, errors: [] });
assert.deepEqual(validateProviderPackage(geliosProviderPackageV7), { ok: true, errors: [] });
assert.deepEqual(validateProviderPackage(geliosProviderPackageV8), { ok: true, errors: [] });
assert.deepEqual(validateProviderPackage(geliosProviderPackageV9), { ok: true, errors: [] });
assert.deepEqual(validateProviderPackage(geliosProviderPackageV10), { ok: true, errors: [] });
assert.deepEqual(validateProviderPackage(geliosProviderPackageV11), { ok: true, errors: [] });
assert.deepEqual(validateProviderPackage(geliosProviderPackageV12), { ok: true, errors: [] });
assert.deepEqual(validateProviderPackage(geliosProviderPackageV13), { ok: true, errors: [] });
assert.equal(geliosProviderPackageV9.id, "gelios.provider.v9");
assert.equal(geliosProviderPackageV9.version, "9.0.0");
assert.equal(geliosProviderPackageV10.id, "gelios.provider.v10");
assert.equal(geliosProviderPackageV10.version, "10.0.0");
assert.equal(geliosProviderPackageV11.id, "gelios.provider.v11");
assert.equal(geliosProviderPackageV11.version, "11.0.0");
assert.equal(geliosProviderPackageV12.id, "gelios.provider.v12");
assert.equal(geliosProviderPackageV12.version, "12.0.1");
assert.equal(geliosProviderPackageV13.id, "gelios.provider.v13");
assert.equal(geliosProviderPackageV13.version, "13.0.0");
assert.equal(geliosProviderPackageV8.id, "gelios.provider.v8");

const profileProduct = geliosProviderPackageV8.dataProducts.find((product) => product.id === "fleet.units.profile.current.v1");
const registeredProfileProduct = JSON.parse(await readFile(new URL(
  "../../../services/external-data-plane/definitions/fleet.units.profile.current.v1.json",
  import.meta.url,
), "utf8"));
assert.deepEqual(profileProduct, registeredProfileProduct);
assert.deepEqual(profileProduct.semanticTypes, ["map.moving_object"]);
assert.equal(profileProduct.history.mode, "none");
const profileCollection = geliosProviderPackageV8.collectionProfiles.find((profile) => profile.id === "gelios.units.profile.cold.v1");
assert.equal(profileCollection.schedule.intervalMs, 6 * 60 * 60 * 1000);
assert.deepEqual(profileCollection.capabilityIds, ["gelios.units.current.read"]);
const profileMapping = geliosProviderPackageV8.mappingContracts.find((mapping) => mapping.id === "gelios.units.to.fleet.units.profile.current.v1");
assert.equal(profileMapping.fact.sourceId.prefix, "gelios-unit-");
assert.equal(profileMapping.fact.attributes.hardware_type_name.paths[0], "hwType.name");
assert.equal(profileMapping.fact.attributes.trip_detection_type.paths[0], "tripParams.tripDetectionType.name");
assert.equal(profileMapping.fact.attributes.use_odometer.coerce, "boolean");

const contactsProduct = geliosProviderPackageV10.dataProducts.find(
  (product) => product.id === "fleet.units.contacts.current.v1",
);
const registeredContactsProduct = JSON.parse(await readFile(new URL(
  "../../../services/external-data-plane/definitions/fleet.units.contacts.current.v1.json",
  import.meta.url,
), "utf8"));
assert.deepEqual(contactsProduct, registeredContactsProduct);
const contactsMapping = geliosProviderPackageV10.mappingContracts.find(
  (mapping) => mapping.id === "gelios.units.to.fleet.units.contacts.current.v1",
);
assert.deepEqual(contactsMapping.fact.attributes.device_imei, {
  strategy: "first_non_empty",
  paths: ["imei"],
  coerce: "string",
  omitIfMissing: true,
  guardPath: "imeiIsVisible",
  guardEquals: true,
});
assert.equal(contactsMapping.fact.attributes.device_phone_primary.guardPath, "phoneIsVisible");
assert.equal(contactsMapping.fact.attributes.device_phone_secondary.guardPath, "phone2IsVisible");
assert.equal(contactsMapping.fact.attributes.provider_creator_login.paths[0], "creator.login");

const identityProduct = geliosProviderPackageV11.dataProducts.find(
  (product) => product.id === "fleet.units.identity.current.v1",
);
const registeredIdentityProduct = JSON.parse(await readFile(new URL(
  "../../../services/external-data-plane/definitions/fleet.units.identity.current.v1.json",
  import.meta.url,
), "utf8"));
assert.deepEqual(identityProduct, registeredIdentityProduct);
assert.equal(identityProduct.fieldContracts.available_user_logins.type, "string_array");
assert.equal(identityProduct.fieldContracts.custom_fields.type, "string_array");
const identityCapability = geliosProviderPackageV11.capabilities.find(
  (capability) => capability.id === "gelios.units.identity.read",
);
assert.deepEqual(identityCapability.request.query, {
  inclextra: "true",
  inclcstmflds: "true",
  incldrvrs: "true",
  inclusersav: "true",
});
const identityCapabilityV12 = geliosProviderPackageV12.capabilities.find(
  (capability) => capability.id === "gelios.units.identity.read",
);
const unitsCapabilityV12 = geliosProviderPackageV12.capabilities.find(
  (capability) => capability.id === "gelios.units.current.read",
);
assert.equal(identityCapability.request.response.collectionPaths.includes("items"), false);
assert.deepEqual(identityCapabilityV12.request.response.collectionPaths, [
  "items",
  ...identityCapability.request.response.collectionPaths,
]);
assert.deepEqual(unitsCapabilityV12.request.response.collectionPaths, [
  "items",
  ...geliosProviderPackageV11.capabilities.find(
    (capability) => capability.id === "gelios.units.current.read",
  ).request.response.collectionPaths,
]);
assert.equal(Object.isFrozen(geliosProviderPackageV12), true);
assert.equal(Object.isFrozen(identityCapabilityV12.request.response.collectionPaths), true);
assert.equal(Object.isFrozen(unitsCapabilityV12.request.response.collectionPaths), true);
const directoryCapabilityV13 = geliosProviderPackageV13.capabilities.find(
  (capability) => capability.id === "gelios.users.directory.read",
);
assert.equal(directoryCapabilityV13.request.path, "/api/v1/users");
assert.deepEqual(directoryCapabilityV13.request.response.collectionPaths, ["items"]);
assert.equal(directoryCapabilityV13.request.response.pagination.maxItems, 5000);
const contactsProductV2 = geliosProviderPackageV13.dataProducts.find(
  (product) => product.id === "fleet.units.contacts.current.v2",
);
const registeredContactsProductV2 = JSON.parse(await readFile(new URL(
  "../../../services/external-data-plane/definitions/fleet.units.contacts.current.v2.json",
  import.meta.url,
), "utf8"));
assert.deepEqual(contactsProductV2, registeredContactsProductV2);
const contactsProfileV2 = geliosProviderPackageV13.collectionProfiles.find(
  (profile) => profile.id === "gelios.units.contacts.warm.v2",
);
assert.deepEqual(contactsProfileV2.capabilityIds, [
  "gelios.units.identity.read",
  "gelios.users.directory.read",
]);
const contactsMappingV2 = geliosProviderPackageV13.mappingContracts.find(
  (mapping) => mapping.id === "gelios.units.to.fleet.units.contacts.current.v2",
);
assert.equal(
  contactsMappingV2.derivations.available_user_emails.kind,
  "bounded_response_lookup",
);
assert.equal(
  contactsMappingV2.derivations.provider_creator_email.parameters.resultMode,
  "first",
);
assert.equal(Object.isFrozen(geliosProviderPackageV13), true);
const identityMapping = geliosProviderPackageV11.mappingContracts.find(
  (mapping) => mapping.id === "gelios.units.to.fleet.units.identity.current.v1",
);
assert.equal(identityMapping.fact.attributes.device_imei.guardPath, undefined);
assert.equal(identityMapping.fact.attributes.device_phone_primary.guardPath, undefined);
assert.equal(identityMapping.fact.attributes.assigned_driver_name.paths[0], "driver.name");
assert.equal(identityMapping.derivations.available_user_logins.kind, "bounded_string_list");
assert.equal(identityMapping.derivations.custom_fields.kind, "bounded_named_values");

const incompleteGuard = structuredClone(geliosProviderPackageV10);
delete incompleteGuard.mappingContracts.at(-1).fact.attributes.device_imei.guardEquals;
assert.equal(
  validateProviderPackage(incompleteGuard).errors.some(
    (error) => error.endsWith("guard_requires_path_and_equals"),
  ),
  true,
);

const telemetryProduct = geliosProviderPackageV7.dataProducts.find((product) => product.id === "fleet.positions.current.v5");
const registeredTelemetryProduct = JSON.parse(await readFile(new URL(
  "../../../services/external-data-plane/definitions/fleet.positions.current.v5.json",
  import.meta.url,
), "utf8"));
assert.deepEqual(telemetryProduct, registeredTelemetryProduct);
assert.equal(telemetryProduct.fieldContracts.sensor_readings.type, "telemetry_readings");
const telemetryCapability = geliosProviderPackageV7.capabilities.find((capability) => capability.id === "gelios.units.current.read");
assert.deepEqual(telemetryCapability.request.query, {
  incltrip: "true",
  inclcntrs: "true",
  inclsnsrs: "true",
  incllsv: "true",
});
const telemetryMapping = geliosProviderPackageV7.mappingContracts.find((mapping) => mapping.id === "gelios.units.to.fleet.positions.current.v7");
assert.equal(telemetryMapping.derivations.sensor_readings.kind, "bounded_readings");
assert.equal(telemetryMapping.derivations.sensor_readings.parameters.maxReadings, 128);
assert.equal(telemetryMapping.fact.attributes.mileage_km.paths[0], "counters.mileage.value");
assert.equal(telemetryMapping.fact.attributes.engine_hours.paths[0], "counters.engineHours.value");
assert.equal(geliosProviderPackageV7.dataProducts.some((product) => product.id === "map.gelios-geofences.current.v1"), true);

const zonesProduct = geliosProviderPackageV6.dataProducts.find((product) => product.id === "map.gelios-geofences.current.v1");
const registeredZonesProduct = JSON.parse(await readFile(new URL(
  "../../../services/external-data-plane/definitions/map.zones.current.v2.json",
  import.meta.url,
), "utf8"));
assert.deepEqual(zonesProduct.semanticTypes, ["map.zone"]);
assert.equal(zonesProduct.fieldContracts.geometry.type, "geometry");
assert.equal(registeredZonesProduct.version, "2.0.0");
assert.deepEqual(registeredZonesProduct.fieldContracts.dataset_authority.enum, ["moscow-department-of-transport"]);
assert.deepEqual(registeredZonesProduct.fieldContracts.source_adapter.enum, [
  "depttrans-mmap-snapshot-v1",
  "depttrans-api-snapshot-v1",
]);
const zonesCapability = geliosProviderPackageV6.capabilities.find((capability) => capability.id === "gelios.geozones.current.read");
assert.equal(zonesCapability.request.query.pl, 100);
assert.equal(zonesCapability.request.query.po, 0);
assert.equal(zonesCapability.request.response.pagination.maxItems, 5000);
assert.equal(zonesCapability.request.response.pagination.totalPath, "paginationMetadata.totalCount");
const zonesMapping = geliosProviderPackageV6.mappingContracts.find((mapping) => mapping.id === "gelios.geozones.to.map.gelios-geofences.current.v1");
assert.deepEqual(zonesMapping.fact.observedAt.paths, ["updatedAt"]);
assert.equal(zonesMapping.fact.observedAt.fallback, "collection_received_at");

const strictProduct = geliosProviderPackageV5.dataProducts[0];
const registeredStrictProduct = JSON.parse(await readFile(new URL(
  "../../../services/external-data-plane/definitions/fleet.positions.current.v4.json",
  import.meta.url,
), "utf8"));
const geliosOntologyEntities = JSON.parse(await readFile(new URL(
  "../../../services/ontology-core/catalog/domain-packages/gelios/entities.json",
  import.meta.url,
), "utf8"));
assert.equal(geliosProviderPackageV5.id, "gelios.provider.v5");
assert.equal(strictProduct.id, "fleet.positions.current.v4");
assert.deepEqual(strictProduct, registeredStrictProduct);
assert.deepEqual(strictProduct.fieldContracts.signal_state.enum, ["active", "inactive"]);
assert.deepEqual(strictProduct.fieldContracts.movement_state.enum, ["moving", "stopped"]);
for (const ontologyEntityId of ["gelios.signal_state", "gelios.movement_state"]) {
  const valueContract = geliosOntologyEntities.entities.find((entity) => entity.id === ontologyEntityId)?.valueContract;
  assert.ok(valueContract, `${ontologyEntityId} must expose an Ontology valueContract`);
  assert.deepEqual(
    strictProduct.fieldContracts[valueContract.field].enum,
    valueContract.values.map(({ value: state }) => state),
    `${valueContract.field} Data Product enum must equal the Ontology value contract`,
  );
}

const withInvalidClosedStateDefault = structuredClone(geliosProviderPackageV5);
withInvalidClosedStateDefault.mappingContracts[0].derivations.signal_state.default = "invented";
assert.equal(
  validateProviderPackage(withInvalidClosedStateDefault).errors.includes(
    "mappingContract.gelios.units.to.fleet.positions.current.v5.fact.attributes.signal_state.derivation.default_not_in_field_contract_enum",
  ),
  true,
);

const withInvalidClosedStateRule = structuredClone(geliosProviderPackageV5);
withInvalidClosedStateRule.mappingContracts[0].derivations.movement_state.rules[0] = "integer_speed_gt_threshold.teleporting";
assert.equal(
  validateProviderPackage(withInvalidClosedStateRule).errors.includes(
    "mappingContract.gelios.units.to.fleet.positions.current.v5.fact.attributes.movement_state.derivation.rules[0]_not_in_field_contract_enum",
  ),
  true,
);

const withOptionalRequiredState = structuredClone(geliosProviderPackageV5);
withOptionalRequiredState.mappingContracts[0].fact.attributes.signal_state.omitIfMissing = true;
assert.equal(
  validateProviderPackage(withOptionalRequiredState).errors.includes(
    "mappingContract.gelios.units.to.fleet.positions.current.v5.fact.attributes.signal_state_required_but_mapping_can_omit",
  ),
  true,
);

const withMalformedFieldContract = structuredClone(geliosProviderPackageV5);
withMalformedFieldContract.dataProducts[0].fieldContracts.signal_state = null;
assert.doesNotThrow(() => validateProviderPackage(withMalformedFieldContract));
assert.equal(validateProviderPackage(withMalformedFieldContract).ok, false);

const withUnboundedNumericMapping = structuredClone(geliosProviderPackageV5);
delete withUnboundedNumericMapping.mappingContracts[0].fact.attributes.speed_kph.minimum;
assert.equal(
  validateProviderPackage(withUnboundedNumericMapping).errors.includes(
    "mappingContract.gelios.units.to.fleet.positions.current.v5.fact.attributes.speed_kph.minimum_must_match_field_contract",
  ),
  true,
);

const withNonOmittingBoundedMapping = structuredClone(geliosProviderPackageV5);
withNonOmittingBoundedMapping.mappingContracts[0].fact.attributes.course_degrees.omitIfInvalid = false;
assert.equal(
  validateProviderPackage(withNonOmittingBoundedMapping).errors.includes(
    "mappingContract.gelios.units.to.fleet.positions.current.v5.fact.attributes.course_degrees.bounded_number_must_omit_invalid",
  ),
  true,
);

const canonicalMonitoringProduct = geliosProviderPackageV4.dataProducts[0];
const canonicalMonitoringMapping = geliosProviderPackageV4.mappingContracts[0];
const registeredCanonicalMonitoringProduct = JSON.parse(await readFile(new URL(
  "../../../services/external-data-plane/definitions/fleet.positions.current.v3.json",
  import.meta.url,
), "utf8"));
assert.equal(geliosProviderPackageV4.id, "gelios.provider.v4");
assert.equal(geliosProviderPackageV4.version, "4.0.0");
assert.deepEqual(geliosProviderPackageV4.manifest.ontology, {
  packageId: "gelios",
  revision: "ontology.gelios.v1_1",
});
assert.deepEqual(geliosProviderPackageV4.collectionProfiles[0].capabilityIds, [
  "gelios.monitoring_config.current.read",
  "gelios.units.current.read",
]);
assert.equal(
  geliosProviderPackageV4.capabilities.find((item) => item.id === "gelios.monitoring_config.current.read").request.path,
  "/api/v1/users/me/monitoring-config",
);
assert.equal(
  geliosProviderPackageV4.capabilities.find((item) => item.id === "gelios.units.current.read").request.query.incltrip,
  "true",
);
assert.deepEqual(canonicalMonitoringProduct, registeredCanonicalMonitoringProduct);
assert.deepEqual(canonicalMonitoringProduct.fields.filter((field) => field.endsWith("_state")), [
  "movement_state",
  "signal_state",
]);
for (const forbiddenField of [
  "availability_state",
  "freshness_state",
  "operational_status",
  "position_state",
  "state_policy_version",
]) {
  assert.equal(canonicalMonitoringProduct.fields.includes(forbiddenField), false);
}
assert.deepEqual(canonicalMonitoringMapping.derivations.signal_state.rules, [
  "last_message_missing.inactive",
  "last_message_age_lt_resolved_monitoring_limit.active",
  "otherwise.inactive",
]);
assert.equal(canonicalMonitoringMapping.derivations.signal_state.default, "inactive");
assert.equal(canonicalMonitoringMapping.derivations.signal_state.parameters.activeDurationPath, "signalActiveDuration");
assert.equal(
  canonicalMonitoringMapping.derivations.signal_state.parameters.somewhatInactiveDurationPath,
  "signalSomewhatInactiveDuration",
);
assert.equal(canonicalMonitoringMapping.derivations.signal_state.parameters.collectionProfileFallbackSeconds, 120);
assert.equal(
  canonicalMonitoringMapping.derivations.signal_state.parameters.thresholdResolution,
  "somewhatInactive_then_active_then_profileFallback",
);
assert.equal(canonicalMonitoringMapping.derivations.movement_state.parameters.movingThresholdKph, 2);
assert.equal(canonicalMonitoringMapping.derivations.movement_state.default, "stopped");
assert.deepEqual(
  geliosProviderPackageV4.l2Templates[0].steps.map((step) => step.kind),
  [
    "collection_trigger",
    "provider_request",
    "provider_request",
    "extract_items",
    "semantic_mapping",
    "data_product_publish",
  ],
);

const stateAwareProduct = geliosProviderPackageV3.dataProducts[0];
const stateAwareMapping = geliosProviderPackageV3.mappingContracts[0];
const registeredStateAwareProduct = JSON.parse(await readFile(new URL(
  "../../../services/external-data-plane/definitions/fleet.positions.current.v2.json",
  import.meta.url,
), "utf8"));
assert.equal(geliosProviderPackageV3.id, "gelios.provider.v3");
assert.equal(geliosProviderPackageV3.version, "3.0.0");
assert.equal(geliosProviderPackageV3.authModes[0].id, "gelios.rest-rotating-bearer.v3");
assert.deepEqual(geliosProviderPackageV3.authModes[0].transport, {
  placement: "header",
  name: "Authorization",
});
assert.deepEqual(geliosProviderPackageV3.authModes[0].tokenLifecycle, {
  artifacts: ["access", "refresh"],
  requestArtifact: "access",
  refreshMode: "runtime_managed",
});
assert.equal(geliosProviderPackageV3.capabilities[0].request.baseUrl, "https://api.geliospro.com");
assert.equal(geliosProviderPackageV3.capabilities[0].request.path, "/api/v1/units");
assert.deepEqual(geliosProviderPackageV3.capabilities[0].request.query, {});
assert.equal(
  geliosProviderPackageV3.l2Templates[0].credentialBindings.find((binding) => binding.role === "provider").authModeId,
  "gelios.rest-rotating-bearer.v3",
);
assert.equal(stateAwareProduct.id, "fleet.positions.current.v2");
assert.equal(stateAwareProduct.version, "2.0.0");
assert.equal(stateAwareProduct.ontologyRevision, "ontology.map.moving_object.v2");
assert.deepEqual(stateAwareProduct, registeredStateAwareProduct);
assert.deepEqual(
  ["availability_state", "freshness_state", "motion_state", "position_state"].map((field) => (
    stateAwareMapping.fact.attributes[field]
  )),
  [
    { derive: "availability_state" },
    { derive: "freshness_state" },
    { derive: "motion_state" },
    { derive: "position_state" },
  ],
);
assert.equal(stateAwareMapping.fact.attributes.state_policy_version.constant, "map-moving-object-state/v1");
assert.equal(stateAwareMapping.derivations.motion_state.parameters.movingThresholdKph, 1);
assert.equal(stateAwareMapping.derivations.position_state.parameters.minSatelliteCount, 4);
assert.equal(stateAwareMapping.derivations.position_state.parameters.maxHdop, 5);
assert.equal(stateAwareMapping.derivations.position_state.parameters.maxHorizontalAccuracyMeters, 100);

const geliosSdkAuth = geliosProviderPackageV2.authModes[0];
const geliosSdkUnitsRead = geliosProviderPackageV2.capabilities[0];
assert.equal(geliosProviderPackageV2.id, "gelios.provider.v2");
assert.equal(geliosProviderPackageV2.version, "2.0.0");
assert.equal(geliosSdkAuth.id, "gelios.sdk-query-token.v2");
assert.deepEqual(geliosSdkAuth.transport, { placement: "query", name: "token" });
assert.deepEqual(geliosSdkAuth.tokenLifecycle, {
  artifacts: ["access"],
  requestArtifact: "access",
  refreshMode: "not_applicable",
});
assert.equal(geliosSdkUnitsRead.request.baseUrl, "https://admin.geliospro.com");
assert.equal(geliosSdkUnitsRead.request.path, "/sdk/");
assert.deepEqual(geliosSdkUnitsRead.request.query, { svc: "get_units", params: "{}" });
assert.equal(geliosProviderPackageV2.dataProducts[0].id, GELIOS_POSITIONS_DATA_PRODUCT_ID);

const geliosAuth = geliosProviderPackageV1.authModes[0];
const geliosUnitsRead = geliosProviderPackageV1.capabilities[0];
assert.equal(geliosAuth.id, "gelios.rest-bearer.v1");
assert.deepEqual(geliosAuth.transport, { placement: "header", name: "Authorization" });
assert.deepEqual(geliosAuth.tokenLifecycle, {
  artifacts: ["access", "refresh"],
  requestArtifact: "access",
  refreshMode: "operator_managed",
});
assert.equal(geliosAuth.tokenLifecycle.artifacts.includes("writer"), false);
assert.equal(geliosUnitsRead.request.baseUrl, "https://api.geliospro.com");
assert.equal(geliosUnitsRead.request.path, "/api/v1/units");
assert.deepEqual(geliosUnitsRead.request.query, {});

const product = geliosProviderPackageV1.dataProducts[0];
const registeredProduct = JSON.parse(await readFile(new URL(
  "../../../services/external-data-plane/definitions/fleet.positions.current.v1.json",
  import.meta.url,
), "utf8"));
assert.equal(product.id, GELIOS_POSITIONS_DATA_PRODUCT_ID);
assert.equal(product.version, GELIOS_POSITIONS_DATA_PRODUCT_VERSION);
assert.equal(product.ontologyRevision, GELIOS_POSITIONS_ONTOLOGY_REVISION);
assert.equal(product.id, "fleet.positions.current.v1");
assert.equal(product.version, "1.0.0");
assert.equal(product.ontologyRevision, "ontology.map.moving_object.v1");
assert.deepEqual(product.fields, expectedFields);
assert.deepEqual(product, registeredProduct);
assert.doesNotThrow(() => normalizeDataProductDefinition(product));
assert.equal(product.fields.every((field) => /^[a-z][a-z0-9_]*$/.test(field)), true);

const mapping = geliosProviderPackageV1.mappingContracts[0];
assert.equal(mapping.target.dataProductId, product.id);
assert.equal(mapping.target.version, product.version);
assert.equal(mapping.target.ontologyRevision, product.ontologyRevision);
assert.deepEqual(
  [...Object.keys(mapping.fact.attributes), "geometry"].sort(),
  [...product.fields].sort(),
);
assert.equal(mapping.derivations.operational_status.parameters.staleAfterMs, 60000);
assert.deepEqual(mapping.derivations.quality_flags.default, []);
assert.equal(mapping.fact.observedAt.fallback, "collection_received_at");
assert.equal(GELIOS_UNIT_SOURCE_ID_PREFIX, "gelios-unit-");
assert.equal(mapping.fact.sourceId.prefix, GELIOS_UNIT_SOURCE_ID_PREFIX);
assert.equal(mapping.fact.sourceId.coerce, "string");
assert.equal(mapping.fact.geometry.longitude.paths.includes("lastMsg.lon"), true);
assert.equal(mapping.fact.geometry.latitude.paths.includes("lastMsg.lat"), true);
assert.equal(mapping.fact.attributes.speed_kph.paths.includes("lastMsg.speed"), true);

assert.equal(validateDataProductPublish(geliosUnitsCurrentFixtureV1.expectedPublish).ok, true);
assert.equal(
  geliosUnitsCurrentFixtureV1.sourceResponse.units.length,
  geliosUnitsCurrentFixtureV1.expectedPublish.facts.length,
  "every entity returned for the bound credential remains in the publish fixture",
);
assert.deepEqual(
  geliosUnitsCurrentFixtureV1.expectedPublish.facts.map((fact) => fact.sourceId),
  geliosUnitsCurrentFixtureV1.sourceResponse.units.map(
    (unit) => `${GELIOS_UNIT_SOURCE_ID_PREFIX}${String(unit.id)}`,
  ),
);
assert.equal(
  geliosUnitsCurrentFixtureV1.expectedPublish.facts[1].sourceId,
  "gelios-unit-001002",
  "provider ID strings must remain byte-for-byte stable after the canonical prefix",
);
assert.equal(geliosUnitsCurrentFixtureV1.expectedPublish.facts[1].geometry, undefined);
assert.equal(geliosUnitsCurrentFixtureV1.expectedPublish.facts[1].attributes.operational_status, "no_position");
assert.equal(geliosUnitsCurrentFixtureV1.sourceResponse.units[1].lastMsg, undefined);
assert.equal(
  geliosUnitsCurrentFixtureV1.expectedPublish.facts[1].observedAt,
  geliosUnitsCurrentFixtureV1.collectionContext.receivedAt,
);

const realtimeProfileId = "gelios.positions.current.realtime.v1";
assert.equal(
  geliosProviderPackageV1.collectionProfiles[0].cardinality.onExceed,
  "require_partitioned_data_product",
);
const accountA = instantiateL2Connection(geliosProviderPackageV1, {
  tenantId: "tenant-alpha",
  connectionId: "gelios-account-alpha",
  collectionProfileId: realtimeProfileId,
  providerCredentialRef: "ndc-credref:provider-alpha-0001",
});
const accountB = instantiateL2Connection(geliosProviderPackageV1, {
  tenantId: "tenant-beta",
  connectionId: "gelios-account-beta",
  collectionProfileId: realtimeProfileId,
  providerCredentialRef: "ndc-credref:provider-beta-0001",
});

assert.equal(accountA.schemaVersion, L2_CONNECTION_INSTANCE_SCHEMA_VERSION);
assert.equal(accountA.package.id, accountB.package.id);
assert.equal(accountA.package.version, accountB.package.version);
assert.equal(accountA.l2TemplateId, accountB.l2TemplateId);
assert.notEqual(accountA.connectionId, accountB.connectionId);
assert.notEqual(accountA.credentialRefs.provider.reference, accountB.credentialRefs.provider.reference);
assert.equal(accountA.credentialRefs.provider.owner, "ndc_l2_credentials");
assert.deepEqual(Object.keys(accountA.credentialRefs), ["provider"]);
assert.deepEqual(accountA.systemBindings.publisher, {
  role: "publisher",
  owner: "ndc_l2_credentials",
  management: "control_plane_managed",
  nodeType: "n8n-nodes-ndc.ndcDataProductPublish",
  desiredState: "bound",
  status: "unresolved",
});
assert.equal(accountA.scope.mode, "all_visible_to_credential");
assert.equal(accountA.scope.refresh, "each_collection_run");
assert.equal(Object.isFrozen(accountA), true);
assert.equal(Object.isFrozen(accountA.credentialRefs), true);
assert.equal(Object.isFrozen(accountA.systemBindings), true);

const withEntityAllowlist = structuredClone(geliosProviderPackageV1);
withEntityAllowlist.capabilities[0].entityScope.unitIds = ["gelios-unit-1001"];
assert.equal(
  validateProviderPackage(withEntityAllowlist).errors.includes("capabilities[0].entityScope.unitIds_not_allowed"),
  true,
);

const withGroupFilter = structuredClone(geliosProviderPackageV1);
withGroupFilter.collectionProfiles[0].entityScope.businessEntityFilter = "provider_group";
assert.equal(
  validateProviderPackage(withGroupFilter).errors.includes("collectionProfiles[0].entityScope.businessEntityFilter_must_be_forbidden"),
  true,
);

const withWrongRevision = structuredClone(geliosProviderPackageV1);
withWrongRevision.mappingContracts[0].target.ontologyRevision = "gelios.positions.v1";
assert.equal(
  validateProviderPackage(withWrongRevision).errors.includes("mappingContract.gelios.units.to.fleet.positions.current.v1.target.ontologyRevision_must_match_data_product"),
  true,
);

const withMissingDerivation = structuredClone(geliosProviderPackageV1);
delete withMissingDerivation.mappingContracts[0].derivations.position_valid;
assert.equal(
  validateProviderPackage(withMissingDerivation).errors.includes("mappingContracts[0].derive_position_valid_missing_definition"),
  true,
);

const withWrongFactSemanticType = structuredClone(geliosProviderPackageV1);
withWrongFactSemanticType.mappingContracts[0].fact.semanticType.constant = "map.other";
assert.equal(
  validateProviderPackage(withWrongFactSemanticType).errors.includes(
    "mappingContract.gelios.units.to.fleet.positions.current.v1.fact.semanticType_must_equal_target_constant",
  ),
  true,
);

const nonGeospatialPackage = structuredClone(geliosProviderPackageV1);
nonGeospatialPackage.dataProducts[0].fields = ["display_name"];
nonGeospatialPackage.fieldPolicies[0].targetFields = ["display_name"];
delete nonGeospatialPackage.mappingContracts[0].fact.geometry;
nonGeospatialPackage.mappingContracts[0].fact.attributes = {
  display_name: { strategy: "first_non_empty", paths: ["name"], coerce: "string" },
};
delete nonGeospatialPackage.mappingContracts[0].derivations;
assert.deepEqual(validateProviderPackage(nonGeospatialPackage), { ok: true, errors: [] });

for (const restrictedPath of ["phone", "phone.number", "lmsg"]) {
  const withRestrictedMappingPath = structuredClone(geliosProviderPackageV1);
  withRestrictedMappingPath.mappingContracts[0].fact.attributes.display_name.paths = [restrictedPath];
  assert.equal(
    validateProviderPackage(withRestrictedMappingPath).errors.includes(
      "mappingContract.gelios.units.to.fleet.positions.current.v1.fact_source_path_restricted",
    ),
    true,
  );
}

const withFieldPolicyProductMismatch = structuredClone(geliosProviderPackageV1);
const secondProduct = structuredClone(withFieldPolicyProductMismatch.dataProducts[0]);
secondProduct.id = "fleet.positions.alternate.v1";
withFieldPolicyProductMismatch.dataProducts.push(secondProduct);
withFieldPolicyProductMismatch.manifest.dataProductIds.push(secondProduct.id);
withFieldPolicyProductMismatch.fieldPolicies[0].dataProductId = secondProduct.id;
assert.equal(
  validateProviderPackage(withFieldPolicyProductMismatch).errors.includes(
    "mappingContract.gelios.units.to.fleet.positions.current.v1.field_policy_data_product_mismatch",
  ),
  true,
);

const withCamelCaseField = structuredClone(geliosProviderPackageV1);
withCamelCaseField.dataProducts[0].fields[12] = "speedKph";
assert.equal(validateProviderPackage(withCamelCaseField).ok, false);

const withSecretField = structuredClone(geliosProviderPackageV1);
withSecretField.dataProducts[0].fields[12] = "api_token";
withSecretField.fieldPolicies[0].targetFields[12] = "api_token";
withSecretField.mappingContracts[0].fact.attributes.api_token = { constant: "metadata-only" };
delete withSecretField.mappingContracts[0].fact.attributes.speed_kph;
const secretFieldValidation = validateProviderPackage(withSecretField);
assert.equal(secretFieldValidation.errors.includes("dataProducts[0].fields_must_not_contain_secret_fields"), true);
assert.equal(secretFieldValidation.errors.includes("fieldPolicies[0].targetFields_must_not_contain_secret_fields"), true);
assert.equal(secretFieldValidation.errors.includes("mappingContracts[0].fact.attributes.api_token_secret_field_not_allowed"), true);

const withRuntimeAccountState = structuredClone(geliosProviderPackageV1);
withRuntimeAccountState.tenantId = "must-not-live-in-package";
assert.equal(
  validateProviderPackage(withRuntimeAccountState).errors.includes("providerPackage_must_not_contain_connection_instance_state"),
  true,
);

const withPrimitiveCapability = structuredClone(geliosProviderPackageV1);
withPrimitiveCapability.capabilities.push(null);
assert.doesNotThrow(() => validateProviderPackage(withPrimitiveCapability));
assert.equal(validateProviderPackage(withPrimitiveCapability).ok, false);

const withMalformedNestedArrays = structuredClone(geliosProviderPackageV1);
withMalformedNestedArrays.collectionProfiles[0].capabilityIds = {};
withMalformedNestedArrays.l2Templates[0].credentialBindings = {};
withMalformedNestedArrays.l2Templates[0].steps = {};
assert.doesNotThrow(() => validateProviderPackage(withMalformedNestedArrays));
assert.equal(validateProviderPackage(withMalformedNestedArrays).ok, false);

const withCredentialInStaticQuery = structuredClone(geliosProviderPackageV1);
withCredentialInStaticQuery.authModes[0].transport = { placement: "query", name: "token" };
withCredentialInStaticQuery.capabilities[0].request.query.token = "plain-provider-key";
assert.equal(
  validateProviderPackage(withCredentialInStaticQuery).errors.includes("capabilities[0].request.query.token_credential_parameter_not_allowed"),
  true,
);
assert.equal(
  validateProviderPackage(withCredentialInStaticQuery).errors.includes("capability.gelios.units.current.read.request.query_must_not_embed_credential_parameter"),
  true,
);

const withCredentialInBaseUrl = structuredClone(geliosProviderPackageV1);
withCredentialInBaseUrl.capabilities[0].request.baseUrl = "https://user:pass@api.geliospro.com/?token=forbidden";
assert.equal(validateProviderPackage(withCredentialInBaseUrl).ok, false);

for (const unsafePath of ["/ok\nX-Evil: yes", "/back\\slash", "/query?x=1"]) {
  const withUnsafeRequestPath = structuredClone(geliosProviderPackageV1);
  withUnsafeRequestPath.capabilities[0].request.path = unsafePath;
  assert.equal(validateProviderPackage(withUnsafeRequestPath).ok, false);
}

for (const unsafeCollectionPath of ["__proto__.polluted", "data.items[0]", "constructor.prototype"]) {
  const withUnsafeCollectionPath = structuredClone(geliosProviderPackageV1);
  withUnsafeCollectionPath.capabilities[0].request.response.collectionPaths = [unsafeCollectionPath];
  assert.equal(validateProviderPackage(withUnsafeCollectionPath).ok, false);
}

const withUnsafeMappingPath = structuredClone(geliosProviderPackageV1);
withUnsafeMappingPath.mappingContracts[0].fact.attributes.display_name.paths = ["__proto__.polluted"];
assert.equal(validateProviderPackage(withUnsafeMappingPath).ok, false);

const withUnsafeCollectionCapability = structuredClone(geliosProviderPackageV1);
withUnsafeCollectionCapability.capabilities[0].classification = "write";
assert.equal(
  validateProviderPackage(withUnsafeCollectionCapability).errors.includes("collectionProfile.gelios.positions.current.realtime.v1.capabilityIds_must_reference_implemented_safe_read"),
  true,
);

const withUnexecutedSecondCapability = structuredClone(geliosProviderPackageV1);
const secondCapability = structuredClone(withUnexecutedSecondCapability.capabilities[0]);
secondCapability.id = "gelios.units.metadata.read";
withUnexecutedSecondCapability.capabilities.push(secondCapability);
withUnexecutedSecondCapability.manifest.capabilityIds.push(secondCapability.id);
for (const profile of withUnexecutedSecondCapability.collectionProfiles) {
  profile.capabilityIds.push(secondCapability.id);
}
assert.equal(
  validateProviderPackage(withUnexecutedSecondCapability).errors.includes(
    "collectionProfile.gelios.positions.current.realtime.v1.template_capability_chain_mismatch",
  ),
  true,
);

const withReorderedSteps = structuredClone(geliosProviderPackageV1);
[withReorderedSteps.l2Templates[0].steps[0], withReorderedSteps.l2Templates[0].steps[1]] = [
  withReorderedSteps.l2Templates[0].steps[1],
  withReorderedSteps.l2Templates[0].steps[0],
];
assert.equal(
  validateProviderPackage(withReorderedSteps).errors.includes("l2Templates[0].steps_sequence_invalid"),
  true,
);

const withUnknownConnectionParameters = structuredClone(geliosProviderPackageV1);
withUnknownConnectionParameters.l2Templates[0].connectionParameters = ["foo"];
assert.equal(
  validateProviderPackage(withUnknownConnectionParameters).errors.includes(
    "l2Templates[0].connectionParameters_must_match_v1_contract",
  ),
  true,
);

const withCredentialAuthMismatch = structuredClone(geliosProviderPackageV1);
const secondAuthMode = structuredClone(withCredentialAuthMismatch.authModes[0]);
secondAuthMode.id = "gelios.rest-bearer.alternate.v1";
withCredentialAuthMismatch.authModes.push(secondAuthMode);
withCredentialAuthMismatch.manifest.authModeIds.push(secondAuthMode.id);
withCredentialAuthMismatch.l2Templates[0].credentialBindings[0].authModeId = secondAuthMode.id;
assert.equal(
  validateProviderPackage(withCredentialAuthMismatch).errors.includes(
    "l2Template.gelios.positions.current.l2.v1.provider_credential_auth_mode_mismatch",
  ),
  true,
);

const withCallerManagedPublisher = structuredClone(geliosProviderPackageV1);
withCallerManagedPublisher.l2Templates[0].credentialBindings[1].management = "connection_input";
assert.equal(
  validateProviderPackage(withCallerManagedPublisher).errors.includes(
    "l2Templates[0].credentialBindings[1].publisher_binding_shape_invalid",
  ),
  true,
);

const withUnknownTokenArtifact = structuredClone(geliosProviderPackageV1);
withUnknownTokenArtifact.authModes[0].tokenLifecycle.artifacts.push("writer");
assert.equal(
  validateProviderPackage(withUnknownTokenArtifact).errors.includes(
    "authModes[0].tokenLifecycle.artifacts_invalid",
  ),
  true,
);

const withRefreshUsedForRequest = structuredClone(geliosProviderPackageV1);
withRefreshUsedForRequest.authModes[0].tokenLifecycle.requestArtifact = "refresh";
assert.equal(
  validateProviderPackage(withRefreshUsedForRequest).errors.includes(
    "authModes[0].tokenLifecycle.requestArtifact_must_be_access",
  ),
  true,
);

const withUnmanagedRefreshArtifact = structuredClone(geliosProviderPackageV1);
withUnmanagedRefreshArtifact.authModes[0].tokenLifecycle.refreshMode = "not_applicable";
assert.equal(
  validateProviderPackage(withUnmanagedRefreshArtifact).errors.includes(
    "authModes[0].tokenLifecycle.refreshMode_required_for_refresh_artifact",
  ),
  true,
);

for (const history of [{
  mode: "none",
  intervalMs: 60000,
  strategy: "latest-per-entity-per-bucket",
  retentionDays: 90,
}, {
  mode: "sampled",
  intervalMs: 24 * 60 * 60 * 1000 + 1,
  strategy: "latest-per-entity-per-bucket",
  retentionDays: 90,
}, {
  mode: "all",
  retentionDays: 3651,
}]) {
  const withInvalidHistory = structuredClone(geliosProviderPackageV1);
  withInvalidHistory.dataProducts[0].history = history;
  assert.equal(validateProviderPackage(withInvalidHistory).ok, false);
  assert.throws(() => normalizeDataProductDefinition(withInvalidHistory.dataProducts[0]));
}

const withBrokenArtifactChain = structuredClone(geliosProviderPackageV1);
withBrokenArtifactChain.l2Templates[0].steps[4].dataProductId = "unrelated.product.v1";
assert.equal(
  validateProviderPackage(withBrokenArtifactChain).errors.includes("collectionProfile.gelios.positions.current.realtime.v1.template_data_product_mismatch"),
  true,
);

const withSecretNamedDerivationParameter = structuredClone(geliosProviderPackageV1);
withSecretNamedDerivationParameter.mappingContracts[0].derivations.position_valid.parameters = {
  accessToken: "opaque-provider-value",
};
assert.equal(
  validateProviderPackage(withSecretNamedDerivationParameter).errors.includes(
    "mappingContracts[0].derivations.position_valid.parameters.accessToken_secret_field_not_allowed",
  ),
  true,
);

const withObjectConstant = structuredClone(geliosProviderPackageV1);
withObjectConstant.mappingContracts[0].fact.attributes.object_kind.constant = {
  accessToken: "opaque-provider-value",
};
assert.equal(
  validateProviderPackage(withObjectConstant).errors.includes(
    "mappingContracts[0].fact.attributes.object_kind.constant_must_be_scalar_or_scalar_array",
  ),
  true,
);

assert.throws(() => instantiateL2Connection(geliosProviderPackageV1, {
  tenantId: "tenant-alpha",
  connectionId: "gelios-account-alpha",
  collectionProfileId: realtimeProfileId,
  providerCredentialRef: "Bearer plaintext-must-not-cross-boundary",
}), /l2_connection_invalid/);

assert.throws(() => instantiateL2Connection(geliosProviderPackageV1, {
  tenantId: "tenant-alpha",
  connectionId: "gelios-account-alpha",
  collectionProfileId: realtimeProfileId,
  providerCredentialRef: "ndc-credref:provider-alpha-0001",
  writerCredentialRef: "ndc-credref:caller-writer-forbidden",
}), /options\.writerCredentialRef_not_allowed/);

const serializedPackage = JSON.stringify(geliosProviderPackageV1);
assert.equal(serializedPackage.includes('"prefix":"unit-"'), false);
assert.equal(serializedPackage.includes("unitIds"), false);
assert.equal(serializedPackage.includes("tenant-alpha"), false);
assert.equal(serializedPackage.includes("gelios-gateway"), false);
assert.equal(serializedPackage.includes("writer_credential_ref"), false);
assert.equal(serializedPackage.includes("writerCapabilityByReferenceOnly"), false);
assert.equal(serializedPackage.includes("publisherBindingControlPlaneManaged\":true"), true);
assert.equal(serializedPackage.includes("providerSpecificServiceForbidden\":true"), true);
assert.deepEqual(
  geliosProviderPackageV1.l2Templates[0].steps.map((step) => step.kind),
  [
    "collection_trigger",
    "provider_request",
    "extract_items",
    "semantic_mapping",
    "data_product_publish",
  ],
);
assert.equal(
  geliosProviderPackageV1.l2Templates[0].steps.at(-1).nodeType,
  "n8n-nodes-ndc.ndcDataProductPublish",
);

console.log("external-provider-contract provider-package: ok");
