import { SystemRole } from "@capakraken/shared";
import { describe, expect, it, vi } from "vitest";
import { notificationRouter } from "../router/notification.js";
import { createCallerFactory } from "../trpc.js";

const createCaller = createCallerFactory(notificationRouter);

function createContext(
  db: Record<string, unknown>,
  options: {
    role?: SystemRole;
    session?: boolean;
  } = {},
) {
  const { role = SystemRole.USER, session = true } = options;

  return {
    session: session
      ? {
          user: { email: "user@example.com", name: "User", image: null },
          expires: "2099-01-01T00:00:00.000Z",
        }
      : null,
    db: db as never,
    dbUser: session
      ? {
          id: role === SystemRole.MANAGER ? "user_mgr" : "user_1",
          systemRole: role,
          permissionOverrides: null,
        }
      : null,
  };
}

describe("notification router authorization", () => {
  it("requires authentication for self-service notification lists", async () => {
    const findMany = vi.fn();
    const caller = createCaller(createContext({
      notification: {
        findMany,
      },
      user: {
        findUnique: vi.fn(),
      },
    }, { session: false }));

    await expect(caller.list({ limit: 20 })).rejects.toMatchObject({
      code: "UNAUTHORIZED",
      message: "Authentication required",
    });

    expect(findMany).not.toHaveBeenCalled();
  });

  it("requires authentication for unread counts, reminders, and self-service deletes", async () => {
    const count = vi.fn();
    const findFirst = vi.fn();
    const deleteFn = vi.fn();
    const caller = createCaller(createContext({
      notification: {
        count,
        findMany: vi.fn(),
        findFirst,
        delete: deleteFn,
      },
      user: {
        findUnique: vi.fn(),
      },
    }, { session: false }));

    await expect(caller.unreadCount()).rejects.toMatchObject({
      code: "UNAUTHORIZED",
      message: "Authentication required",
    });
    await expect(caller.listReminders({ limit: 10 })).rejects.toMatchObject({
      code: "UNAUTHORIZED",
      message: "Authentication required",
    });
    await expect(caller.delete({ id: "notif_1" })).rejects.toMatchObject({
      code: "UNAUTHORIZED",
      message: "Authentication required",
    });

    expect(count).not.toHaveBeenCalled();
    expect(findFirst).not.toHaveBeenCalled();
    expect(deleteFn).not.toHaveBeenCalled();
  });

  it("forbids regular users from creating broadcasts", async () => {
    const create = vi.fn();
    const caller = createCaller(createContext({
      notificationBroadcast: {
        create,
      },
    }));

    await expect(caller.createBroadcast({
      title: "Ops update",
      targetType: "all",
    })).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Manager or Admin role required",
    });

    expect(create).not.toHaveBeenCalled();
  });

  it("forbids regular users from reading broadcasts or creating tasks", async () => {
    const findUnique = vi.fn();
    const create = vi.fn();
    const caller = createCaller(createContext({
      notificationBroadcast: {
        findUnique,
      },
      notification: {
        create,
      },
    }));

    await expect(caller.getBroadcastById({ id: "broadcast_1" })).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Manager or Admin role required",
    });
    await expect(caller.createTask({
      userId: "user_2",
      title: "Review proposal",
    })).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Manager or Admin role required",
    });

    expect(findUnique).not.toHaveBeenCalled();
    expect(create).not.toHaveBeenCalled();
  });

  it("forbids regular users from reassigning tasks", async () => {
    const findUnique = vi.fn();
    const caller = createCaller(createContext({
      notification: {
        findUnique,
      },
    }));

    await expect(caller.assignTask({ id: "task_1", assigneeId: "user_2" })).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Manager or Admin role required",
    });

    expect(findUnique).not.toHaveBeenCalled();
  });

  it("allows managers to list broadcasts", async () => {
    const findMany = vi.fn().mockResolvedValue([
      {
        id: "broadcast_1",
        title: "Ops update",
        createdAt: new Date("2026-03-30T10:00:00Z"),
        sender: { id: "user_mgr", name: "Manager", email: "mgr@example.com" },
      },
    ]);
    const caller = createCaller(createContext({
      notificationBroadcast: {
        findMany,
      },
    }, { role: SystemRole.MANAGER }));

    const result = await caller.listBroadcasts({ limit: 10 });

    expect(result).toHaveLength(1);
    expect(findMany).toHaveBeenCalledWith({
      orderBy: { createdAt: "desc" },
      take: 10,
      include: {
        sender: { select: { id: true, name: true, email: true } },
      },
    });
  });

  it("allows managers to read individual broadcasts", async () => {
    const findUnique = vi.fn().mockResolvedValue({
      id: "broadcast_1",
      title: "Ops update",
      sender: { id: "user_mgr", name: "Manager", email: "mgr@example.com" },
    });
    const caller = createCaller(createContext({
      notificationBroadcast: {
        findUnique,
      },
    }, { role: SystemRole.MANAGER }));

    const result = await caller.getBroadcastById({ id: "broadcast_1" });

    expect(result).toEqual({
      id: "broadcast_1",
      title: "Ops update",
      sender: { id: "user_mgr", name: "Manager", email: "mgr@example.com" },
    });
    expect(findUnique).toHaveBeenCalledWith({
      where: { id: "broadcast_1" },
      include: {
        sender: { select: { id: true, name: true, email: true } },
      },
    });
  });
});
