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

const createCaller = createCallerFactory(userRouter);

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.ADMIN ? "user_admin" : "user_1",
          systemRole: role,
          permissionOverrides: null,
        }
      : null,
    roleDefaults: null,
  };
}

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

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

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

  it("requires authentication for dashboard layout reads", async () => {
    const findUnique = vi.fn();
    const caller = createCaller(createContext({
      user: {
        findUnique,
      },
    }, { session: false }));

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

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

  it("requires authentication for favorite project toggles", async () => {
    const findUnique = vi.fn();
    const update = vi.fn();
    const caller = createCaller(createContext({
      user: {
        findUnique,
        update,
      },
    }, { session: false }));

    await expect(caller.toggleFavoriteProject({ projectId: "project_1" })).rejects.toMatchObject({
      code: "UNAUTHORIZED",
      message: "Authentication required",
    });

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

  it("requires authentication for dashboard layout saves", async () => {
    const update = vi.fn();
    const caller = createCaller(createContext({
      user: {
        update,
      },
    }, { session: false }));

    await expect(caller.saveDashboardLayout({
      layout: { version: 2, gridCols: 12, widgets: [] },
    })).rejects.toMatchObject({
      code: "UNAUTHORIZED",
      message: "Authentication required",
    });

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

  it("requires authentication for favorite project reads", async () => {
    const findUnique = vi.fn();
    const caller = createCaller(createContext({
      user: {
        findUnique,
      },
    }, { session: false }));

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

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

  it("requires authentication for column preference reads and writes", async () => {
    const findUnique = vi.fn();
    const update = vi.fn();
    const caller = createCaller(createContext({
      user: {
        findUnique,
        update,
      },
    }, { session: false }));

    await expect(caller.getColumnPreferences()).rejects.toMatchObject({
      code: "UNAUTHORIZED",
      message: "Authentication required",
    });
    await expect(caller.setColumnPreferences({
      view: "resources",
      visible: ["name"],
    })).rejects.toMatchObject({
      code: "UNAUTHORIZED",
      message: "Authentication required",
    });

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

  it("requires authentication for MFA status lookups", async () => {
    const findUniqueOrThrow = vi.fn();
    const caller = createCaller(createContext({
      user: {
        findUniqueOrThrow,
      },
    }, { session: false }));

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

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

  it("forbids regular users from listing assignable users", async () => {
    const findMany = vi.fn();
    const caller = createCaller(createContext({
      user: {
        findMany,
      },
    }));

    await expect(caller.listAssignable()).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Manager or Admin role required",
    });

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

  it("allows managers to list assignable users", async () => {
    const findMany = vi.fn().mockResolvedValue([
      { id: "user_1", name: "Alice", email: "alice@example.com" },
    ]);
    const caller = createCaller(createContext({
      user: {
        findMany,
      },
    }, { role: SystemRole.MANAGER }));

    const result = await caller.listAssignable();

    expect(result).toHaveLength(1);
    expect(findMany).toHaveBeenCalledWith({
      select: {
        id: true,
        name: true,
        email: true,
      },
      orderBy: { name: "asc" },
    });
  });

  it("forbids non-admin users from reading effective permissions", async () => {
    const findUnique = vi.fn();
    const caller = createCaller(createContext({
      user: {
        findUnique,
      },
    }, { role: SystemRole.MANAGER }));

    await expect(caller.getEffectivePermissions({ userId: "user_2" })).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Admin role required",
    });

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

  it("forbids non-admin users from setting explicit permission overrides", async () => {
    const findUnique = vi.fn();
    const update = vi.fn();
    const caller = createCaller(createContext({
      user: {
        findUnique,
        update,
      },
    }, { role: SystemRole.MANAGER }));

    await expect(caller.setPermissions({
      userId: "user_2",
      overrides: {
        granted: ["manageProjects"],
      },
    })).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Admin role required",
    });

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

  it("forbids non-admin users from resetting permission overrides", async () => {
    const findUnique = vi.fn();
    const update = vi.fn();
    const caller = createCaller(createContext({
      user: {
        findUnique,
        update,
      },
    }, { role: SystemRole.MANAGER }));

    await expect(caller.resetPermissions({ userId: "user_2" })).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Admin role required",
    });

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

  it("forbids non-admin users from disabling TOTP for other users", async () => {
    const findUnique = vi.fn();
    const caller = createCaller(createContext({
      user: {
        findUnique,
      },
    }, { role: SystemRole.MANAGER }));

    await expect(caller.disableTotp({ userId: "user_2" })).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Admin role required",
    });

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

  it("forbids non-admin users from linking resources", async () => {
    const userFindUnique = vi.fn();
    const resourceFindUnique = vi.fn();
    const updateMany = vi.fn();
    const update = vi.fn();
    const caller = createCaller(createContext({
      user: {
        findUnique: userFindUnique,
      },
      resource: {
        findUnique: resourceFindUnique,
        updateMany,
        update,
      },
    }, { role: SystemRole.MANAGER }));

    await expect(caller.linkResource({
      userId: "user_2",
      resourceId: "resource_1",
    })).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Admin role required",
    });

    expect(userFindUnique).not.toHaveBeenCalled();
    expect(resourceFindUnique).not.toHaveBeenCalled();
    expect(updateMany).not.toHaveBeenCalled();
    expect(update).not.toHaveBeenCalled();
  });

  it("forbids non-admin users from auto-linking users by email", async () => {
    const userFindMany = vi.fn();
    const resourceFindFirst = vi.fn();
    const resourceUpdate = vi.fn();
    const caller = createCaller(createContext({
      user: {
        findMany: userFindMany,
      },
      resource: {
        findFirst: resourceFindFirst,
        update: resourceUpdate,
      },
    }, { role: SystemRole.MANAGER }));

    await expect(caller.autoLinkAllByEmail()).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Admin role required",
    });

    expect(userFindMany).not.toHaveBeenCalled();
    expect(resourceFindFirst).not.toHaveBeenCalled();
    expect(resourceUpdate).not.toHaveBeenCalled();
  });

  it("keeps TOTP verification public for the login flow", async () => {
    const findUnique = vi.fn().mockResolvedValue({
      id: "user_1",
      totpEnabled: false,
      totpSecret: null,
    });
    const caller = createCaller(createContext({
      user: {
        findUnique,
      },
    }, { session: false }));

    await expect(caller.verifyTotp({ userId: "user_1", token: "123456" })).rejects.toMatchObject({
      code: "BAD_REQUEST",
      message: "TOTP is not enabled for this user.",
    });

    expect(findUnique).toHaveBeenCalledWith({
      where: { id: "user_1" },
      select: { id: true, totpSecret: true, totpEnabled: true },
    });
  });
});
