import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import { Transaction } from "../generated/kysely-tailordb";
import {
  InvalidEmailError,
  MissingRequiredFieldError,
  UserAlreadyExistsError,
} from "../lib/errors.generated";
import { activeUser, pendingUser } from "../testing/fixtures";
import { run } from "./createUser";

describe("createUser", () => {
  // Error cases
  it("throws when email is empty", async () => {
    const { db } = createMockDb<Transaction>();

    const result = await run(db, { email: "", name: "Test User" });
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(MissingRequiredFieldError);
    }
  });

  it("throws when name is empty", async () => {
    const { db } = createMockDb<Transaction>();

    const result = await run(db, { email: "test@example.com", name: "" });
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(MissingRequiredFieldError);
    }
  });

  it("throws when name is whitespace only", async () => {
    const { db } = createMockDb<Transaction>();

    const result = await run(db, { email: "test@example.com", name: "   " });
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(MissingRequiredFieldError);
    }
  });

  it("throws when email format is invalid (no @)", async () => {
    const { db } = createMockDb<Transaction>();

    const result = await run(db, { email: "invalidemail", name: "Test User" });
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidEmailError);
    }
  });

  it("throws when email format is invalid (no domain)", async () => {
    const { db } = createMockDb<Transaction>();

    const result = await run(db, { email: "test@", name: "Test User" });
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidEmailError);
    }
  });

  it("throws when email already exists", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValue(activeUser);

    const result = await run(db, { email: activeUser.email, name: "New User" });
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(UserAlreadyExistsError);
    }
  });

  // Success cases
  it("creates user with PENDING status", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const createdUser = {
      ...pendingUser,
      id: "new-user-id",
      email: "new@example.com",
      name: "New User",
    };

    spies.select.mockReturnValue(undefined); // No existing user
    spies.insert.mockReturnValue(createdUser);

    const result = await run(db, {
      email: "new@example.com",
      name: "New User",
    });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.user.email).toBe("new@example.com");
      expect(result.value.user.name).toBe("New User");
      expect(result.value.user.status).toBe("PENDING");
    }
    expect(spies.insert).toHaveBeenCalled();
  });

  it("passes custom fields through to insert", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const createdUser = {
      ...pendingUser,
      id: "new-user-id",
      email: "new@example.com",
      name: "New User",
      department: "Engineering",
    };

    spies.select.mockReturnValue(undefined);
    spies.insert.mockReturnValue(createdUser);

    const result = await run(db, {
      email: "new@example.com",
      name: "New User",
      department: "Engineering",
    });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.user.id).toBe("new-user-id");
    }
    expect(spies.insert).toHaveBeenCalledTimes(1);

    // User insert includes custom field
    expect(spies.values).toHaveBeenNthCalledWith(
      1,
      expect.objectContaining({ department: "Engineering" }),
    );
  });
});
