import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } 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 mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) => run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) => run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) => run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) => run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([activeUser]);

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    const createdUser = {
      ...pendingUser,
      id: "new-user-id",
      email: "new@example.com",
      name: "New User",
    };

    mock.enqueueResults([], [createdUser]);

    const result = await mock.withTx((trx) =>
      run(trx, {
        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(mock.inserts.length).toBeGreaterThan(0);
  });

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

    mock.enqueueResults([], [createdUser]);

    const result = await mock.withTx((trx) =>
      run(trx, {
        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(mock.inserts).toHaveLength(1);

    // User insert includes custom field
    expect(mock.inserts[0].insertValues()).toEqual(
      expect.objectContaining({ department: "Engineering" }),
    );
  });
});
