import type { ReactElement, ReactNode } from "react";

import { WorkspaceMemberRole } from "@mosaic/shared";
import { render, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it, vi } from "vitest";

import {
  type AdminUser,
  deactivateUser,
  fetchAdminUsers,
  inviteUser,
  updateUser,
  type AdminUsersResponse,
} from "@/lib/api/admin";
import { useAuth } from "@/lib/auth/auth-context";
import { fetchUserWorkspaces, updateWorkspaceMemberRole } from "@/lib/api/workspaces";
import UsersSettingsPage from "./page";

vi.mock("next/link", () => ({
  default: function LinkMock({
    children,
    href,
  }: {
    children: ReactNode;
    href: string;
  }): ReactElement {
    return <a href={href}>{children}</a>;
  },
}));

vi.mock("@/lib/api/admin", () => ({
  fetchAdminUsers: vi.fn(),
  inviteUser: vi.fn(),
  updateUser: vi.fn(),
  deactivateUser: vi.fn(),
}));

vi.mock("@/lib/api/workspaces", () => ({
  fetchUserWorkspaces: vi.fn(),
  updateWorkspaceMemberRole: vi.fn(),
}));

vi.mock("@/lib/auth/auth-context", () => ({
  useAuth: vi.fn(),
}));

const fetchAdminUsersMock = vi.mocked(fetchAdminUsers);
const inviteUserMock = vi.mocked(inviteUser);
const updateUserMock = vi.mocked(updateUser);
const deactivateUserMock = vi.mocked(deactivateUser);
const fetchUserWorkspacesMock = vi.mocked(fetchUserWorkspaces);
const updateWorkspaceMemberRoleMock = vi.mocked(updateWorkspaceMemberRole);
const useAuthMock = vi.mocked(useAuth);

function makeAdminUser(overrides?: Partial<AdminUser>): AdminUser {
  return {
    id: "user-1",
    name: "Alice",
    email: "alice@example.com",
    emailVerified: true,
    image: null,
    createdAt: "2026-01-01T00:00:00.000Z",
    deactivatedAt: null,
    isLocalAuth: false,
    invitedAt: null,
    invitedBy: null,
    workspaceMemberships: [
      {
        workspaceId: "workspace-1",
        workspaceName: "Personal Workspace",
        role: WorkspaceMemberRole.ADMIN,
        joinedAt: "2026-01-01T00:00:00.000Z",
      },
    ],
    ...overrides,
  };
}

function makeAdminUsersResponse(options?: {
  data?: AdminUser[];
  page?: number;
  totalPages?: number;
  total?: number;
  limit?: number;
}): AdminUsersResponse {
  const data = options?.data ?? [makeAdminUser()];
  return {
    data,
    meta: {
      total: options?.total ?? data.length,
      page: options?.page ?? 1,
      limit: options?.limit ?? 50,
      totalPages: options?.totalPages ?? 1,
    },
  };
}

function makeAuthState(userId: string): ReturnType<typeof useAuth> {
  return {
    user: { id: userId, email: `${userId}@example.com`, name: "Current User" },
    isLoading: false,
    isAuthenticated: true,
    authError: null,
    sessionExpiring: false,
    sessionMinutesRemaining: 0,
    signOut: vi.fn(() => Promise.resolve()),
    refreshSession: vi.fn(() => Promise.resolve()),
  };
}

describe("UsersSettingsPage", () => {
  beforeEach(() => {
    vi.clearAllMocks();

    const adminUsersResponse = makeAdminUsersResponse();

    fetchAdminUsersMock.mockResolvedValue(adminUsersResponse);
    fetchUserWorkspacesMock.mockResolvedValue([
      {
        id: "workspace-1",
        name: "Personal Workspace",
        ownerId: "owner-1",
        role: WorkspaceMemberRole.OWNER,
        createdAt: "2026-01-01T00:00:00.000Z",
      },
    ]);
    inviteUserMock.mockResolvedValue({
      userId: "user-2",
      invitationToken: "token-1",
      email: "new@example.com",
      invitedAt: "2026-01-02T00:00:00.000Z",
    });
    const firstUser = adminUsersResponse.data[0] ?? makeAdminUser();

    updateUserMock.mockResolvedValue(firstUser);
    deactivateUserMock.mockResolvedValue(firstUser);
    updateWorkspaceMemberRoleMock.mockResolvedValue({
      workspaceId: "workspace-1",
      userId: "user-1",
      role: WorkspaceMemberRole.ADMIN,
      joinedAt: "2026-01-01T00:00:00.000Z",
      user: {
        id: "user-1",
        email: "alice@example.com",
        name: "Alice",
        image: null,
      },
    });

    useAuthMock.mockReturnValue(makeAuthState("user-current"));
  });

  it("shows access denied to non-admin users", async () => {
    fetchUserWorkspacesMock.mockResolvedValueOnce([
      {
        id: "workspace-1",
        name: "Personal Workspace",
        ownerId: "owner-1",
        role: WorkspaceMemberRole.MEMBER,
        createdAt: "2026-01-01T00:00:00.000Z",
      },
    ]);

    render(<UsersSettingsPage />);

    expect(await screen.findByText("Access Denied")).toBeInTheDocument();
    expect(fetchAdminUsersMock).not.toHaveBeenCalled();
  });

  it("invites a user with email and role from the dialog", async () => {
    const user = userEvent.setup();
    render(<UsersSettingsPage />);

    expect(await screen.findByText("User Directory")).toBeInTheDocument();

    await user.click(screen.getByRole("button", { name: "Invite User" }));
    await user.type(screen.getByLabelText("Email"), "new@example.com");
    await user.click(screen.getByRole("button", { name: "Send Invite" }));

    await waitFor(() => {
      expect(inviteUserMock).toHaveBeenCalledWith({
        email: "new@example.com",
        role: WorkspaceMemberRole.MEMBER,
        workspaceId: "workspace-1",
      });
    });
  });

  it("opens user detail dialog from row click and saves edited profile fields", async () => {
    const user = userEvent.setup();
    render(<UsersSettingsPage />);

    expect(await screen.findByText("alice@example.com")).toBeInTheDocument();

    await user.click(screen.getByText("Alice"));

    const nameInput = await screen.findByLabelText("Name");
    await user.clear(nameInput);
    await user.type(nameInput, "Alice Updated");

    await user.click(screen.getByRole("button", { name: "Save Changes" }));

    await waitFor(() => {
      expect(updateUserMock).toHaveBeenCalledWith("user-1", { name: "Alice Updated" });
    });

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

  it("caps pagination to the last valid page after deactivation shrinks the dataset", async () => {
    const user = userEvent.setup();
    const pageOneUser = makeAdminUser({
      id: "user-1",
      name: "Alice",
      email: "alice@example.com",
    });
    const pageTwoUser = makeAdminUser({
      id: "user-2",
      name: "Bob",
      email: "bob@example.com",
    });

    fetchAdminUsersMock.mockReset();
    const responses = [
      {
        expectedPage: 1,
        response: makeAdminUsersResponse({
          data: [pageOneUser],
          page: 1,
          totalPages: 2,
          total: 2,
        }),
      },
      {
        expectedPage: 2,
        response: makeAdminUsersResponse({
          data: [pageTwoUser],
          page: 2,
          totalPages: 2,
          total: 2,
        }),
      },
      {
        expectedPage: 2,
        response: makeAdminUsersResponse({
          data: [],
          page: 2,
          totalPages: 1,
          total: 1,
        }),
      },
      {
        expectedPage: 1,
        response: makeAdminUsersResponse({
          data: [pageOneUser],
          page: 1,
          totalPages: 1,
          total: 1,
        }),
      },
    ];

    fetchAdminUsersMock.mockImplementation((page = 1) => {
      const next = responses.shift();
      if (!next) {
        throw new Error("Unexpected fetchAdminUsers call in pagination-cap test");
      }

      expect(page).toBe(next.expectedPage);
      return Promise.resolve(next.response);
    });

    render(<UsersSettingsPage />);

    expect(await screen.findByText("alice@example.com")).toBeInTheDocument();

    await user.click(screen.getByRole("button", { name: "Next" }));
    expect(await screen.findByText("bob@example.com")).toBeInTheDocument();

    const pageTwoRow = screen.getByText("bob@example.com").closest('[role="button"]');
    if (!(pageTwoRow instanceof HTMLElement)) {
      throw new Error("Expected Bob's row to exist");
    }

    await user.click(within(pageTwoRow).getByRole("button", { name: "Deactivate" }));
    const deactivateButtons = await screen.findAllByRole("button", { name: "Deactivate" });
    const confirmDeactivateButton = deactivateButtons[deactivateButtons.length - 1];
    if (!confirmDeactivateButton) {
      throw new Error("Expected confirmation deactivate button to be rendered");
    }
    await user.click(confirmDeactivateButton);

    expect(await screen.findByText("alice@example.com")).toBeInTheDocument();
    expect(screen.queryByText("No Users Yet")).not.toBeInTheDocument();
    expect(deactivateUserMock).toHaveBeenCalledWith("user-2");
    const requestedPages = fetchAdminUsersMock.mock.calls.map(([requestedPage]) => requestedPage);
    expect(requestedPages.slice(-2)).toEqual([2, 1]);
  });

  it("shows the API error state without rendering the empty-state message", async () => {
    fetchAdminUsersMock.mockRejectedValueOnce(new Error("Unable to load users"));

    render(<UsersSettingsPage />);

    expect(await screen.findByText("Unable to load users")).toBeInTheDocument();
    expect(screen.queryByText("No Users Yet")).not.toBeInTheDocument();
    expect(screen.queryByText("Invite the first user to get started.")).not.toBeInTheDocument();
  });

  it("prevents the current user from deactivating their own account", async () => {
    useAuthMock.mockReturnValue(makeAuthState("user-1"));

    const selfUser = makeAdminUser({
      id: "user-1",
      name: "Alice",
      email: "alice@example.com",
    });
    const otherUser = makeAdminUser({
      id: "user-2",
      name: "Bob",
      email: "bob@example.com",
    });

    fetchAdminUsersMock.mockResolvedValueOnce(
      makeAdminUsersResponse({
        data: [selfUser, otherUser],
        page: 1,
        totalPages: 1,
        total: 2,
      })
    );

    render(<UsersSettingsPage />);

    expect(await screen.findByText("alice@example.com")).toBeInTheDocument();
    expect(screen.getByText("bob@example.com")).toBeInTheDocument();

    const selfRow = screen.getByText("alice@example.com").closest('[role="button"]');
    if (!(selfRow instanceof HTMLElement)) {
      throw new Error("Expected current-user row to exist");
    }
    expect(within(selfRow).queryByRole("button", { name: "Deactivate" })).not.toBeInTheDocument();

    const otherRow = screen.getByText("bob@example.com").closest('[role="button"]');
    if (!(otherRow instanceof HTMLElement)) {
      throw new Error("Expected other-user row to exist");
    }
    expect(within(otherRow).getByRole("button", { name: "Deactivate" })).toBeInTheDocument();
    expect(deactivateUserMock).not.toHaveBeenCalled();
  });
});
