import { describe, it, expect, vi, beforeEach } from "vitest";
import { TRPCError } from "@trpc/server";

const mockFindFirst = vi.fn();
const mockSelectFromWhereLimit = vi.fn();
const mockUpdateSetWhereReturning = vi.fn();

vi.mock("~/server/db", () => ({
  db: {
    query: {
      users: {
        findFirst: mockFindFirst,
      },
    },
    select: vi.fn(() => ({
      from: vi.fn(() => ({
        where: vi.fn(() => ({
          limit: mockSelectFromWhereLimit,
        })),
      })),
    })),
    update: vi.fn(() => ({
      set: vi.fn(() => ({
        where: vi.fn(() => ({
          returning: mockUpdateSetWhereReturning,
        })),
      })),
    })),
  },
}));

const mockUser = {
  id: "user-1",
  email: "test@example.com",
  name: "Test User",
  image: null,
  role: "user",
  emailVerified: null,
  deletedAt: null,
  createdAt: new Date(),
  updatedAt: new Date(),
};

beforeEach(() => {
  vi.clearAllMocks();
});

describe("getUserById", () => {
  it("returns user with correct relations", async () => {
    const userWithRelations = {
      ...mockUser,
      accounts: [],
      sessions: [],
      deviceTokens: [],
      familyGroups: [],
      familyGroupOwned: [],
      subscriptions: [],
    };
    mockFindFirst.mockResolvedValue(userWithRelations);

    const { getUserById } = await import("./user.service");
    const result = await getUserById("user-1");

    expect(result).toEqual(userWithRelations);
    expect(mockFindFirst).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.anything(),
        with: expect.objectContaining({
          accounts: true,
          sessions: true,
          deviceTokens: true,
          familyGroups: true,
          familyGroupOwned: true,
          subscriptions: true,
        }),
      }),
    );
  });

  it("throws NOT_FOUND when user does not exist", async () => {
    mockFindFirst.mockResolvedValue(undefined);

    const { getUserById } = await import("./user.service");
    await expect(getUserById("nonexistent")).rejects.toThrow(TRPCError);
    await expect(getUserById("nonexistent")).rejects.toMatchObject({
      code: "NOT_FOUND",
    });
  });
});

describe("updateUser", () => {
  it("applies changes and returns updated record", async () => {
    mockSelectFromWhereLimit.mockResolvedValue([mockUser]);
    const updatedUser = { ...mockUser, name: "Updated Name" };
    mockUpdateSetWhereReturning.mockResolvedValue([updatedUser]);

    const { updateUser } = await import("./user.service");
    const result = await updateUser("user-1", { name: "Updated Name" });

    expect(result).toEqual(updatedUser);
  });

  it("throws NOT_FOUND when user does not exist", async () => {
    mockSelectFromWhereLimit.mockResolvedValue([]);

    const { updateUser } = await import("./user.service");
    await expect(updateUser("nonexistent", { name: "New" })).rejects.toThrow(TRPCError);
  });

  it("throws CONFLICT when email is already in use", async () => {
    mockSelectFromWhereLimit
      .mockResolvedValueOnce([mockUser])
      .mockResolvedValueOnce([{ id: "other-user", email: "other@example.com" }]);

    const { updateUser } = await import("./user.service");
    await expect(
      updateUser("user-1", { email: "other@example.com" }),
    ).rejects.toMatchObject({ code: "CONFLICT" });
  });
});

describe("deleteUser", () => {
  it("soft-deletes user by setting deletedAt", async () => {
    mockSelectFromWhereLimit.mockResolvedValue([mockUser]);
    const deletedUser = { ...mockUser, deletedAt: new Date() };
    mockUpdateSetWhereReturning.mockResolvedValue([deletedUser]);

    const { deleteUser } = await import("./user.service");
    const result = await deleteUser("user-1");

    expect(result.deletedAt).toBeInstanceOf(Date);
  });

  it("throws NOT_FOUND when user does not exist", async () => {
    mockSelectFromWhereLimit.mockResolvedValue([]);

    const { deleteUser } = await import("./user.service");
    await expect(deleteUser("nonexistent")).rejects.toThrow(TRPCError);
  });
});
