import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import {
  getChannelPluginCatalogEntry,
  listChannelPluginCatalogEntries,
} from "../../../src/channels/plugins/catalog.js";

type CatalogEntryMeta = {
  id: string;
  label: string;
  selectionLabel: string;
  docsPath: string;
  blurb: string;
  detailLabel?: string;
  aliases?: string[];
};

export function describeChannelCatalogEntryContract(params: {
  channelId: string;
  npmSpec: string;
  alias?: string;
}) {
  describe(`${params.channelId} channel catalog contract`, () => {
    it("keeps the shipped catalog entry aligned", () => {
      const entry = getChannelPluginCatalogEntry(params.channelId);
      expect(entry?.install.npmSpec).toBe(params.npmSpec);
      if (params.alias) {
        expect(entry?.meta.aliases).toContain(params.alias);
      }
    });

    it("appears in the channel catalog listing", () => {
      const ids = listChannelPluginCatalogEntries().map((entry) => entry.id);
      expect(ids).toContain(params.channelId);
    });
  });
}

export function describeBundledMetadataOnlyChannelCatalogContract(params: {
  pluginId: string;
  packageName: string;
  npmSpec: string;
  meta: CatalogEntryMeta;
  defaultChoice?: string;
}) {
  describe(`${params.pluginId} bundled metadata-only channel catalog contract`, () => {
    it("includes the bundled metadata-only channel entry when the runtime entrypoint is omitted", () => {
      const packageRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-catalog-"));
      const bundledDir = path.join(packageRoot, "dist", "extensions", params.pluginId);
      fs.mkdirSync(bundledDir, { recursive: true });
      fs.writeFileSync(
        path.join(packageRoot, "package.json"),
        JSON.stringify({ name: "openclaw" }),
        "utf8",
      );
      fs.writeFileSync(
        path.join(bundledDir, "package.json"),
        JSON.stringify({
          name: params.packageName,
          openclaw: {
            extensions: ["./index.js"],
            channel: params.meta,
            install: {
              npmSpec: params.npmSpec,
              defaultChoice: params.defaultChoice,
            },
          },
        }),
        "utf8",
      );
      fs.writeFileSync(path.join(bundledDir, "index.js"), "export default {};\n", "utf8");
      fs.writeFileSync(
        path.join(bundledDir, "openclaw.plugin.json"),
        JSON.stringify({ id: params.pluginId, channels: [params.meta.id], configSchema: {} }),
        "utf8",
      );

      const entry = listChannelPluginCatalogEntries({
        env: {
          ...process.env,
          OPENCLAW_BUNDLED_PLUGINS_DIR: path.join(packageRoot, "dist", "extensions"),
        },
      }).find((item) => item.id === params.meta.id);

      expect(entry?.install.npmSpec).toBe(params.npmSpec);
      expect(entry?.pluginId).toBe(params.pluginId);
    });
  });
}

export function describeOfficialFallbackChannelCatalogContract(params: {
  channelId: string;
  npmSpec: string;
  meta: CatalogEntryMeta;
  packageName: string;
  pluginId: string;
  externalNpmSpec: string;
  externalLabel: string;
}) {
  describe(`${params.channelId} official fallback channel catalog contract`, () => {
    it("includes shipped official channel catalog entries when bundled metadata is omitted", () => {
      const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-official-catalog-"));
      const catalogPath = path.join(dir, "channel-catalog.json");
      fs.writeFileSync(
        catalogPath,
        JSON.stringify({
          entries: [
            {
              name: params.packageName,
              openclaw: {
                channel: params.meta,
                install: {
                  npmSpec: params.npmSpec,
                  defaultChoice: "npm",
                },
              },
            },
          ],
        }),
      );

      const entry = listChannelPluginCatalogEntries({
        env: {
          ...process.env,
          OPENCLAW_BUNDLED_PLUGINS_DIR: "/nonexistent/bundled/plugins",
        },
        officialCatalogPaths: [catalogPath],
      }).find((item) => item.id === params.channelId);

      expect(entry?.install.npmSpec).toBe(params.npmSpec);
      expect(entry?.pluginId).toBeUndefined();
    });

    it("lets external catalogs override shipped fallback channel metadata", () => {
      const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-fallback-catalog-"));
      const bundledDir = path.join(dir, "dist", "extensions", params.pluginId);
      const officialCatalogPath = path.join(dir, "channel-catalog.json");
      const externalCatalogPath = path.join(dir, "catalog.json");
      fs.mkdirSync(bundledDir, { recursive: true });
      fs.writeFileSync(
        path.join(bundledDir, "package.json"),
        JSON.stringify({
          name: params.packageName,
          openclaw: {
            channel: {
              ...params.meta,
              label: `${params.meta.label} Bundled`,
              selectionLabel: `${params.meta.label} Bundled`,
              blurb: "bundled fallback",
            },
            install: { npmSpec: params.npmSpec },
          },
        }),
        "utf8",
      );
      fs.writeFileSync(
        officialCatalogPath,
        JSON.stringify({
          entries: [
            {
              name: params.packageName,
              openclaw: {
                channel: {
                  ...params.meta,
                  label: `${params.meta.label} Official`,
                  selectionLabel: `${params.meta.label} Official`,
                  blurb: "official fallback",
                },
                install: { npmSpec: params.npmSpec },
              },
            },
          ],
        }),
        "utf8",
      );
      fs.writeFileSync(
        externalCatalogPath,
        JSON.stringify({
          entries: [
            {
              name: params.externalNpmSpec,
              openclaw: {
                channel: {
                  ...params.meta,
                  label: params.externalLabel,
                  selectionLabel: params.externalLabel,
                  blurb: "external override",
                },
                install: { npmSpec: params.externalNpmSpec },
              },
            },
          ],
        }),
        "utf8",
      );

      const entry = listChannelPluginCatalogEntries({
        catalogPaths: [externalCatalogPath],
        officialCatalogPaths: [officialCatalogPath],
        env: {
          ...process.env,
          OPENCLAW_BUNDLED_PLUGINS_DIR: path.join(dir, "dist", "extensions"),
        },
      }).find((item) => item.id === params.channelId);

      expect(entry?.install.npmSpec).toBe(params.externalNpmSpec);
      expect(entry?.meta.label).toBe(params.externalLabel);
      expect(entry?.pluginId).toBeUndefined();
    });
  });
}
