import { resolvePermissions, SystemRole } from "@capakraken/shared";
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
  countActiveUsers,
  getEffectiveUserPermissions,
  linkUserResource,
  listAssignableUsers,
} from "../router/user-procedure-support.js";
import {
  getCurrentMfaStatus,
  getCurrentUserProfile,
  getDashboardLayout,
  setColumnPreferences,
  toggleFavoriteProject,
} from "../router/user-self-service-procedure-support.js";

vi.mock("../lib/audit.js", () => ({
  createAuditEntry: vi.fn(),
}));

function createContext(db: Record<string, unknown>, overrides: Record<string, unknown> = {}) {
  return {
    db: db as never,
    dbUser: {
      id: "user_admin",
      systemRole: SystemRole.ADMIN,
      permissionOverrides: null,
    },
    session: {
      user: { email: "admin@example.com", name: "Admin", image: null },
      expires: "2099-01-01T00:00:00.000Z",
    },
    ...overrides,
  };
}

describe("user-procedure-support", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("lists assignable users with the expected lightweight selection", async () => {
    const findMany = vi.fn().mockResolvedValue([
      { id: "user_1", name: "Alice", email: "alice@example.com" },
    ]);

    const result = await listAssignableUsers(createContext({
      user: { findMany },
    }));

    expect(result).toEqual([{ id: "user_1", name: "Alice", email: "alice@example.com" }]);
    expect(findMany).toHaveBeenCalledWith({
      select: { id: true, name: true, email: true },
      orderBy: { name: "asc" },
    });
  });

  it("counts only users active within the trailing five minute window", async () => {
    const nowSpy = vi.spyOn(Date, "now").mockReturnValue(new Date("2026-03-30T20:00:00.000Z").valueOf());
    const count = vi.fn().mockResolvedValue(4);

    const result = await countActiveUsers(createContext({
      user: { count },
    }));

    expect(result).toEqual({ count: 4 });
    expect(count).toHaveBeenCalledWith({
      where: { lastActiveAt: { gte: new Date("2026-03-30T19:55:00.000Z") } },
    });
    nowSpy.mockRestore();
  });

  it("reads the current user profile by db user id", async () => {
    const findUnique = vi.fn().mockResolvedValue({
      id: "user_admin",
      name: "Admin",
      email: "admin@example.com",
      systemRole: SystemRole.ADMIN,
      permissionOverrides: null,
      createdAt: new Date("2026-03-30T08:00:00.000Z"),
    });

    const result = await getCurrentUserProfile(createContext({
      user: { findUnique },
    }));

    expect(result).toEqual({
      id: "user_admin",
      name: "Admin",
      email: "admin@example.com",
      systemRole: SystemRole.ADMIN,
      permissionOverrides: null,
      createdAt: new Date("2026-03-30T08:00:00.000Z"),
    });
    expect(findUnique).toHaveBeenCalledWith({
      where: { id: "user_admin" },
      select: {
        id: true,
        name: true,
        email: true,
        systemRole: true,
        permissionOverrides: true,
        createdAt: true,
      },
    });
  });

  it("unlinks an existing resource before linking the requested one", async () => {
    const userFindUnique = vi.fn().mockResolvedValue({ id: "user_1" });
    const resourceFindUnique = vi.fn().mockResolvedValue({ id: "resource_1", userId: null });
    const updateMany = vi.fn()
      .mockResolvedValueOnce({ count: 1 })
      .mockResolvedValueOnce({ count: 1 });

    const result = await linkUserResource(createContext({
      user: { findUnique: userFindUnique },
      resource: { findUnique: resourceFindUnique, updateMany },
    }), {
      userId: "user_1",
      resourceId: "resource_1",
    });

    expect(result).toEqual({ success: true });
    expect(updateMany).toHaveBeenNthCalledWith(1, {
      where: { userId: "user_1", NOT: { id: "resource_1" } },
      data: { userId: null },
    });
    expect(updateMany).toHaveBeenNthCalledWith(2, {
      where: {
        id: "resource_1",
        OR: [{ userId: null }, { userId: "user_1" }],
      },
      data: { userId: "user_1" },
    });
  });

  it("returns null layout when stored widgets have no valid type (bug #27 guard)", async () => {
    const findUnique = vi.fn().mockResolvedValue({
      dashboardLayout: {
        widgets: [{ id: "peakTimes", position: { x: 0, y: 0, w: 4, h: 3 } }],
      },
      updatedAt: new Date("2026-03-30T18:00:00.000Z"),
    });

    const result = await getDashboardLayout(createContext({
      user: { findUnique },
    }));

    // Widgets with unknown types normalise to empty → return null so client uses default
    expect(result).toEqual({
      layout: null,
      updatedAt: new Date("2026-03-30T18:00:00.000Z"),
    });
  });

  it("toggles favorite projects without losing the existing list", async () => {
    const findUnique = vi.fn().mockResolvedValue({
      favoriteProjectIds: ["project_1"],
    });
    const update = vi.fn().mockResolvedValue({});

    const result = await toggleFavoriteProject(createContext({
      user: { findUnique, update },
    }), {
      projectId: "project_2",
    });

    expect(result).toEqual({
      favoriteProjectIds: ["project_1", "project_2"],
      added: true,
    });
    expect(update).toHaveBeenCalledWith({
      where: { id: "user_admin" },
      data: { favoriteProjectIds: ["project_1", "project_2"] },
    });
  });

  it("merges column preferences while preserving untouched sort and row order", async () => {
    const findUnique = vi.fn().mockResolvedValue({
      columnPreferences: {
        resources: {
          visible: ["name", "role"],
          sort: { field: "name", dir: "asc" },
          rowOrder: ["name", "role", "email"],
        },
      },
    });
    const update = vi.fn().mockResolvedValue({ id: "user_admin" });

    const result = await setColumnPreferences(createContext({
      user: { findUnique, update },
    }), {
      view: "resources",
      visible: ["name", "email"],
    });

    expect(result).toEqual({ ok: true });
    expect(update).toHaveBeenCalledWith({
      where: { id: "user_admin" },
      data: {
        columnPreferences: {
          resources: {
            visible: ["name", "email"],
            sort: { field: "name", dir: "asc" },
            rowOrder: ["name", "role", "email"],
          },
        },
      },
    });
  });

  it("returns effective permissions alongside stored overrides", async () => {
    const overrides = {
      granted: ["manageProjects"],
      denied: ["viewCosts"],
      chapterIds: ["chapter_design"],
    };
    const findUnique = vi.fn().mockResolvedValue({
      systemRole: SystemRole.MANAGER,
      permissionOverrides: overrides,
    });

    const result = await getEffectiveUserPermissions(createContext({
      user: { findUnique },
    }), {
      userId: "user_2",
    });

    expect(result).toEqual({
      systemRole: SystemRole.MANAGER,
      effectivePermissions: Array.from(resolvePermissions(SystemRole.MANAGER, overrides)),
      overrides,
    });
  });

  it("reports MFA status for the current user and throws when the user no longer exists", async () => {
    const findUnique = vi.fn()
      .mockResolvedValueOnce({ totpEnabled: true })
      .mockResolvedValueOnce(null);
    const ctx = createContext({
      user: { findUnique },
    });

    await expect(getCurrentMfaStatus(ctx)).resolves.toEqual({ totpEnabled: true });
    await expect(getCurrentMfaStatus(ctx)).rejects.toMatchObject({
      code: "NOT_FOUND",
      message: "User not found",
    });
  });
});
