#!/usr/bin/env node
import { createHash } from "node:crypto";
import { readFile, writeFile } from "node:fs/promises";
import { resolve } from "node:path";

import {
  compileL2ExecutionPlan,
  instantiateL2Connection,
} from "../../packages/external-provider-contract/src/index.mjs";
import {
  geliosProviderPackageV12,
  geliosTelemetryFieldRegistryV5,
} from "../../packages/external-provider-contract/providers/gelios/v12/index.mjs";

const PUBLIC_PROFILE_METADATA = Object.freeze({
  "gelios.positions.current.realtime.v7": {
    cadence: "hot",
    dataClass: "operational",
  },
  "gelios.positions.current.manual.v7": {
    cadence: "on_demand",
    dataClass: "operational",
  },
  "gelios.geozones.current.realtime.v1": {
    cadence: "cold",
    dataClass: "operational",
  },
  "gelios.units.profile.cold.v1": {
    cadence: "cold",
    dataClass: "operational",
  },
  "gelios.units.contacts.cold.v1": {
    cadence: "cold",
    dataClass: "restricted",
  },
  "gelios.units.identity.warm.v1": {
    cadence: "warm",
    dataClass: "restricted",
  },
});

const engineRoot = resolve(
  process.env.NODEDC_ENGINE_SOURCE_ROOT || "../NODEDC_ENGINE_INFRA",
);
const executionCatalogPath = resolve(
  engineRoot,
  "nodedc-source/server/assets/execution-plans/v1/catalog.json",
);
const securityCatalogPath = resolve(
  engineRoot,
  "nodedc-source/server/assets/provider-packages/v1/catalog.json",
);

const executionCatalog = JSON.parse(
  await readFile(executionCatalogPath, "utf8"),
);
executionCatalog.packages = executionCatalog.packages.filter(
  (providerPackage) => providerPackage.id !== geliosProviderPackageV12.id,
);
executionCatalog.packages.push(executionCatalogEntry());
await writeFile(executionCatalogPath, `${JSON.stringify(executionCatalog, null, 2)}\n`);

const securityCatalog = JSON.parse(
  await readFile(securityCatalogPath, "utf8"),
);
securityCatalog.packages = securityCatalog.packages.filter(
  (providerPackage) => ![
    "gelios.provider.v11",
    geliosProviderPackageV12.id,
  ].includes(providerPackage.id),
);
securityCatalog.packages.push(securityCatalogEntry());
await writeFile(securityCatalogPath, `${JSON.stringify(securityCatalog, null, 2)}\n`);

console.log(JSON.stringify({
  ok: true,
  providerPackage: geliosProviderPackageV12.id,
  executionCatalogPath,
  securityCatalogPath,
  historicalExecutionPackagePreserved: executionCatalog.packages.some(
    (providerPackage) => providerPackage.id === "gelios.provider.v11",
  ),
  activeIdentityAuthority: geliosProviderPackageV12.id,
}, null, 2));

function executionCatalogEntry() {
  const profiles = geliosProviderPackageV12.collectionProfiles.map((profile) => {
    const connection = instantiateL2Connection(geliosProviderPackageV12, {
      tenantId: "tenant-catalog-build",
      connectionId: `catalog-${profile.id.replaceAll(".", "-")}`,
      collectionProfileId: profile.id,
      providerCredentialRef: "ndc-credref:catalog-build-provider-v12",
    });
    const plan = compileL2ExecutionPlan(
      geliosProviderPackageV12,
      connection,
      profile.dataProductId === "fleet.positions.current.v5"
        ? { telemetryFieldRegistry: geliosTelemetryFieldRegistryV5 }
        : {},
    );
    const { packageDigest: _packageDigest, ...artifacts } = plan.artifacts;
    const publicMetadata = PUBLIC_PROFILE_METADATA[profile.id];
    if (!publicMetadata) {
      throw new Error(`gelios_v12_public_profile_metadata_missing:${profile.id}`);
    }
    return {
      id: profile.id,
      dataProductId: profile.dataProductId,
      ...publicMetadata,
      capabilityIds: [...profile.capabilityIds],
      stepSignatures: plan.steps.map((step) => ({
        id: step.id,
        kind: step.kind,
        ...(step.config.capabilityId ? { capabilityId: step.config.capabilityId } : {}),
        ...(step.config.mappingContractId ? {
          mappingContractId: step.config.mappingContractId,
        } : {}),
        ...(step.config.dataProductId ? { dataProductId: step.config.dataProductId } : {}),
        ...(step.config.nodeType ? { nodeType: step.config.nodeType } : {}),
      })),
      artifacts,
      executionPlanTemplate: plan,
    };
  });
  return {
    id: geliosProviderPackageV12.id,
    providerId: geliosProviderPackageV12.providerId,
    version: geliosProviderPackageV12.version,
    contractDigest: canonicalDigest(geliosProviderPackageV12),
    providerCredential: {
      authModeId: "gelios.rest-rotating-bearer.v3",
      credentialType: "ndcProviderRotatingAccessApi",
    },
    publisher: {
      nodeType: "n8n-nodes-ndc.ndcDataProductPublish",
      credentialType: "ndcDataProductWriterApi",
    },
    capabilities: geliosProviderPackageV12.capabilities.map((capability) => ({
      id: capability.id,
      contractDigest: canonicalDigest(capability),
      requestDigest: canonicalDigest(capability.request),
      method: capability.request.method,
      url: requestUrl(capability.request),
    })),
    profiles,
  };
}

function securityCatalogEntry() {
  const productsByCapability = new Map(
    geliosProviderPackageV12.capabilities.map((capability) => [capability.id, new Set()]),
  );
  for (const profile of geliosProviderPackageV12.collectionProfiles) {
    if (profile.dataProductId !== "fleet.units.identity.current.v1") continue;
    for (const capabilityId of profile.capabilityIds) {
      productsByCapability.get(capabilityId)?.add(profile.dataProductId);
    }
  }
  return {
    id: geliosProviderPackageV12.id,
    version: geliosProviderPackageV12.version,
    providerId: geliosProviderPackageV12.providerId,
    providerCredential: {
      authModeId: "gelios.rest-rotating-bearer.v3",
      credentialType: "ndcProviderRotatingAccessApi",
    },
    capabilities: geliosProviderPackageV12.capabilities
      .filter((capability) => productsByCapability.get(capability.id)?.size)
      .map((capability) => ({
        id: capability.id,
        classification: capability.classification,
        status: capability.status,
        request: {
          method: capability.request.method,
          url: requestUrl(capability.request),
        },
        dataProductIds: [...productsByCapability.get(capability.id)].sort(),
      })),
    publisher: {
      nodeType: "n8n-nodes-ndc.ndcDataProductPublish",
      credentialType: "ndcDataProductWriterApi",
    },
  };
}

function requestUrl(request) {
  const url = new URL(request.path, request.baseUrl);
  for (const [name, value] of Object.entries(request.query || {})) {
    url.searchParams.append(name, String(value));
  }
  return url.toString();
}

function canonicalDigest(value) {
  return `sha256:${createHash("sha256").update(JSON.stringify(stableValue(value)), "utf8").digest("hex")}`;
}

function stableValue(value) {
  if (Array.isArray(value)) return value.map(stableValue);
  if (!value || typeof value !== "object") return value;
  return Object.fromEntries(
    Object.keys(value).sort().map((key) => [key, stableValue(value[key])]),
  );
}
