import { describe, expect, expectTypeOf, it } from "vitest";
import { InsufficientPermissionError } from "./errors";
import { definePermissions, ensurePermission, requirePermission } from "./permissions";
import type { CommandContext } from "./types";

describe("definePermissions", () => {
  it("infers literal types for permission keys", () => {
    const result = definePermissions("primitives", {
      category: ["createCategory", "updateCategory"],
    } as const);

    expectTypeOf(
      result.permissions.category.createCategory,
    ).toEqualTypeOf<"primitives:category:createCategory">();
  });

  it("creates scoped permission keys as module:scope:command", () => {
    const result = definePermissions("user-management", {
      user: ["createUser", "updateUser"],
      role: ["createRole"],
    } as const);

    expect(result.permissions.user.createUser).toBe("user-management:user:createUser");
    expect(result.permissions.user.updateUser).toBe("user-management:user:updateUser");
    expect(result.permissions.role.createRole).toBe("user-management:role:createRole");
  });
});

describe("requirePermission", () => {
  const ctx: CommandContext = {
    actorId: "user-1",
    permissions: [
      "primitives:createCategory",
      "primitives:activateCategory",
      "inventory:createItem",
    ],
  };

  it("returns ok when permission is present", () => {
    expect(requirePermission(ctx, "primitives:createCategory").ok).toBe(true);
    expect(requirePermission(ctx, "inventory:createItem").ok).toBe(true);
  });

  it("returns InsufficientPermissionError when permission is missing", () => {
    const result = requirePermission(ctx, "primitives:deleteCategory");
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InsufficientPermissionError);
    }
  });

  it("includes actorId and permission key in error", () => {
    const result = requirePermission(ctx, "orders:createOrder");
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error.name).toBe("InsufficientPermissionError");
      expect(result.error.code).toBe("INSUFFICIENT_PERMISSION");
      expect(result.error.message).toContain("user-1");
      expect(result.error.message).toContain("orders:createOrder");
    }
  });

  it("returns error when permissions array is empty", () => {
    const emptyCtx: CommandContext = { actorId: "user-2", permissions: [] };
    const result = requirePermission(emptyCtx, "primitives:createCategory");
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InsufficientPermissionError);
    }
  });

  describe("hierarchical matching", () => {
    it("matches module-level permission to full key", () => {
      const moduleCtx: CommandContext = {
        actorId: "user-1",
        permissions: ["user-management"],
      };
      expect(requirePermission(moduleCtx, "user-management:role:createRole").ok).toBe(true);
    });

    it("matches scope-level permission to full key", () => {
      const scopeCtx: CommandContext = {
        actorId: "user-1",
        permissions: ["user-management:role"],
      };
      expect(requirePermission(scopeCtx, "user-management:role:createRole").ok).toBe(true);
    });

    it("does not match partial module name", () => {
      const partialCtx: CommandContext = {
        actorId: "user-1",
        permissions: ["user"],
      };
      const result = requirePermission(partialCtx, "user-management:role:createRole");
      expect(result.ok).toBe(false);
    });

    it("does not match unrelated module", () => {
      const unrelatedCtx: CommandContext = {
        actorId: "user-1",
        permissions: ["audit"],
      };
      const result = requirePermission(unrelatedCtx, "user-management:role:createRole");
      expect(result.ok).toBe(false);
    });

    it("scope-level does not match different scope", () => {
      const scopeCtx: CommandContext = {
        actorId: "user-1",
        permissions: ["user-management:user"],
      };
      const result = requirePermission(scopeCtx, "user-management:role:createRole");
      expect(result.ok).toBe(false);
    });
  });
});

describe("ensurePermission", () => {
  const ctx: CommandContext = {
    actorId: "user-1",
    permissions: ["user-management:user", "inventory:createItem"],
  };

  it("returns undefined when permission is present (exact match)", () => {
    expect(ensurePermission(ctx, "inventory:createItem")).toBeUndefined();
  });

  it("returns undefined when umbrella scope grants the key (hierarchical match)", () => {
    expect(ensurePermission(ctx, "user-management:user:createUser")).toBeUndefined();
  });

  it("throws InsufficientPermissionError when permission is missing", () => {
    expect(() => ensurePermission(ctx, "user-management:role:createRole")).toThrow(
      InsufficientPermissionError,
    );
  });

  it("thrown error carries actorId and permission key", () => {
    try {
      ensurePermission(ctx, "orders:createOrder");
      expect.fail("should have thrown");
    } catch (e) {
      expect(e).toBeInstanceOf(InsufficientPermissionError);
      const err = e as InstanceType<typeof InsufficientPermissionError>;
      expect(err.code).toBe("INSUFFICIENT_PERMISSION");
      expect(err.message).toContain("user-1");
      expect(err.message).toContain("orders:createOrder");
    }
  });
});
