import { lstat, readFile, readdir } from "node:fs/promises";
import { basename, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";

import { normalizeDataProductDefinition } from "./data-product-policy.mjs";
import { persistDataProductDefinition } from "./data-product-delivery.mjs";

const bundledDefinitionsDir = fileURLToPath(new URL("../definitions/", import.meta.url));

export async function loadDataProductDefinitions(directory = bundledDefinitionsDir) {
  const root = resolve(directory);
  const entries = (await readdir(root, { withFileTypes: true }))
    .filter((entry) => entry.name.endsWith(".json"))
    .sort((left, right) => left.name.localeCompare(right.name));
  const definitions = [];
  for (const entry of entries) {
    if (!entry.isFile() || entry.isSymbolicLink()) throw new Error("data_product_definition_file_invalid");
    const path = join(root, entry.name);
    const metadata = await lstat(path);
    if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 2 || metadata.size > 256 * 1024) {
      throw new Error("data_product_definition_file_invalid");
    }
    let value;
    try {
      value = JSON.parse(await readFile(path, "utf8"));
    } catch {
      throw new Error("data_product_definition_json_invalid");
    }
    const definition = normalizeDataProductDefinition(value);
    if (`${definition.id}.json` !== basename(path)) throw new Error("data_product_definition_filename_mismatch");
    definitions.push(definition);
  }
  if (new Set(definitions.map((definition) => definition.id)).size !== definitions.length) {
    throw new Error("data_product_definition_duplicate");
  }
  return Object.freeze(definitions);
}

export async function reconcileDataProductDefinitions(db, directory = bundledDefinitionsDir) {
  const definitions = await loadDataProductDefinitions(directory);
  for (const definition of definitions) await persistDataProductDefinition(db, definition);
  return definitions;
}
