import { describe, expect, it } from "vitest";
import { can } from "./policy";

describe("policy.can", () => {
  it("lets a member post in-scope", () => {
    expect(can({ userId: "u1", role: "member" }, "channel:post")).toBe(true);
  });

  it("blocks a single-channel guest outside their scope", () => {
    expect(
      can({ userId: "u1", role: "single_channel_guest" }, "channel:post", { isOutsideGuestScope: true }),
    ).toBe(false);
  });

  it("lets an author edit their own message but not a member edit another's", () => {
    expect(can({ userId: "u1", role: "member" }, "message:edit_own", { isOwnResource: true })).toBe(true);
    expect(can({ userId: "u1", role: "member" }, "message:edit_own", { isOwnResource: false })).toBe(false);
  });

  it("only an owner can delete a workspace", () => {
    expect(can({ userId: "u1", role: "owner" }, "workspace:delete")).toBe(true);
    expect(can({ userId: "u1", role: "admin" }, "workspace:delete")).toBe(false);
  });

  describe("tasks", () => {
    it("lets any guest create and read tasks", () => {
      expect(can({ userId: "u1", role: "single_channel_guest" }, "task:create")).toBe(true);
      expect(can({ userId: "u1", role: "single_channel_guest" }, "task:read")).toBe(true);
    });

    it("restricts editing a task to its creator or an admin", () => {
      expect(can({ userId: "u1", role: "member" }, "task:update", { isOwnResource: true })).toBe(true);
      expect(can({ userId: "u1", role: "member" }, "task:update", { isOwnResource: false })).toBe(false);
      expect(can({ userId: "u1", role: "admin" }, "task:update", { isOwnResource: false })).toBe(true);
    });

    it("lets any member delete a task, and a guest only delete their own", () => {
      expect(can({ userId: "u1", role: "member" }, "task:delete", { isOwnResource: false })).toBe(true);
      expect(can({ userId: "u1", role: "single_channel_guest" }, "task:delete", { isOwnResource: true })).toBe(true);
      expect(can({ userId: "u1", role: "single_channel_guest" }, "task:delete", { isOwnResource: false })).toBe(false);
    });

    it("lets any member move a task's status, and a guest only move their own", () => {
      expect(can({ userId: "u1", role: "member" }, "task:update_status", { isOwnResource: false })).toBe(true);
      expect(can({ userId: "u1", role: "single_channel_guest" }, "task:update_status", { isOwnResource: true })).toBe(
        true,
      );
      expect(can({ userId: "u1", role: "single_channel_guest" }, "task:update_status", { isOwnResource: false })).toBe(
        false,
      );
    });

    it("requires member rank to assign a task", () => {
      expect(can({ userId: "u1", role: "member" }, "task:assign")).toBe(true);
      expect(can({ userId: "u1", role: "single_channel_guest" }, "task:assign")).toBe(false);
    });
  });

  describe("calendar events", () => {
    it("lets a guest read and RSVP but not schedule", () => {
      expect(can({ userId: "u1", role: "single_channel_guest" }, "event:read")).toBe(true);
      expect(can({ userId: "u1", role: "single_channel_guest" }, "event:rsvp")).toBe(true);
      expect(can({ userId: "u1", role: "single_channel_guest" }, "event:create")).toBe(false);
      expect(can({ userId: "u1", role: "member" }, "event:create")).toBe(true);
    });

    it("restricts editing, inviting and deleting to the organiser or an admin", () => {
      for (const action of ["event:update", "event:invite", "event:delete"] as const) {
        expect(can({ userId: "u1", role: "member" }, action, { isOwnResource: true })).toBe(true);
        expect(can({ userId: "u1", role: "member" }, action, { isOwnResource: false })).toBe(false);
        expect(can({ userId: "u1", role: "admin" }, action, { isOwnResource: false })).toBe(true);
      }
    });
  });

  describe("calls", () => {
    it("lets a guest answer and read but not start a call", () => {
      expect(can({ userId: "u1", role: "single_channel_guest" }, "call:read")).toBe(true);
      expect(can({ userId: "u1", role: "single_channel_guest" }, "call:join")).toBe(true);
      expect(can({ userId: "u1", role: "single_channel_guest" }, "call:start")).toBe(false);
      expect(can({ userId: "u1", role: "member" }, "call:start")).toBe(true);
    });

    it("lets anyone on a call hang up, without owning it", () => {
      // Deliberately not gated on isOwnResource: the initiator is not the only
      // person entitled to end a call they are all on.
      expect(can({ userId: "u1", role: "single_channel_guest" }, "call:end", { isOwnResource: false })).toBe(true);
      expect(can({ userId: "u1", role: "member" }, "call:end", { isOwnResource: false })).toBe(true);
    });

    it("does not let a bot start or answer calls", () => {
      expect(can({ userId: "u1", role: "bot" }, "call:start")).toBe(false);
      expect(can({ userId: "u1", role: "bot" }, "call:join")).toBe(false);
    });
  });
});
