import { describe, expect, it } from "vitest";
import {
  canonicalFieldGroupHash,
  isFieldDefinitionVersionError,
  parseFieldDefinitionVersion,
  type FieldDefinitionVersion,
  type FieldGroup,
} from "./schema.js";

const group = {
  key: "article_fields", title: "Article fields", active: true,
  location: [[{ parameter: "contentType", operator: "eq", value: "article" }]],
  fields: [{ key: "title", name: "title", label: "Title", type: "text", settings: {} }],
} satisfies FieldGroup;
const version = async (definition: FieldGroup = group) => ({
  groupKey: definition.key, revision: 1, canonicalHash: await canonicalFieldGroupHash(definition),
  definition, createdAt: "2026-08-08T00:00:00.000Z", origin: "import" as const,
});
const _definition: FieldDefinitionVersion["definition"] = group;
// @ts-expect-error legacy groups are not immutable definition snapshots
const _legacy: FieldDefinitionVersion["definition"] = { key: "legacy", label: "Legacy", location: { entityType: "content" }, fields: [] };
void _definition; void _legacy;

describe("FieldDefinitionVersion trust boundary", () => {
  it("verifies canonical bytes and detaches a deeply frozen final FieldGroup", async () => {
    const parsed = await parseFieldDefinitionVersion(await version());
    expect(parsed.canonicalHash).toBe(await canonicalFieldGroupHash(group));
    expect(Object.isFrozen(parsed.definition)).toBe(true);
    expect(Object.isFrozen(parsed.definition.fields)).toBe(true);
    (group.fields[0] as { label: string }).label = "Changed";
    expect(parsed.definition.fields[0]!.label).toBe("Title");
  });

  it("canonicalizes key order and rejects retained hashes over tampered definition bytes", async () => {
    const hash = await canonicalFieldGroupHash(group);
    const reordered = { active: true, fields: group.fields, key: group.key, location: group.location, title: group.title } satisfies FieldGroup;
    expect(await canonicalFieldGroupHash(reordered)).toBe(hash);
    await expect(parseFieldDefinitionVersion({ ...(await version()), definition: { ...group, title: "Tampered" } })).rejects.toMatchObject({ code: "invalid-definition-version", field: "fieldDefinitionVersion.canonicalHash" });
  });

  it("enforces the whole-graph 1,000 field node limit", async () => {
    const fields = Array.from({ length: 1000 }, (_, index) => ({ key: `field_${index}`, name: `field_${index}`, label: "Field", type: "text" as const, settings: {} }));
    const limit = { ...group, fields } satisfies FieldGroup;
    await expect(parseFieldDefinitionVersion(await version(limit))).resolves.toMatchObject({ definition: { fields: expect.any(Array) } });
    const over = { ...group, fields: [...fields, { key: "over", name: "over", label: "Over", type: "text" as const, settings: {} }] } satisfies FieldGroup;
    await expect(parseFieldDefinitionVersion(await version(over))).rejects.toMatchObject({ code: "invalid-definition-version" });
  });

  it("rejects getters and uses a structural error guard", async () => {
    const input = await version();
    Object.defineProperty(input.definition, "title", { enumerable: true, get: () => "executed" });
    await expect(parseFieldDefinitionVersion(input)).rejects.toSatisfy(isFieldDefinitionVersionError);
    try { await parseFieldDefinitionVersion(input); } catch (error) {
      expect(isFieldDefinitionVersionError(error)).toBe(true);
      expect(error).toMatchObject({ code: "invalid-definition-version", field: expect.any(String), detail: expect.any(String) });
    }
  });
});
