import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import {
  InvalidEmailError,
  InvalidNameError,
  MissingRequiredFieldError,
  UserAlreadyExistsError,
  UserNotFoundError,
} from "../lib/errors.generated";
import { activeUser, inactiveUser, pendingUser } from "../testing/fixtures";
import { run } from "./updateUser";

describe("updateUser", () => {
  // Success cases
  it("updates user name", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updatedUser = { ...activeUser, name: "New Name" };

    mock.enqueueResults([activeUser], [updatedUser]);

    const result = await mock.withTx((trx) => run(trx, { id: activeUser.id, name: "New Name" }));
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.user.name).toBe("New Name");
    }
    expect(mock.updates.length).toBeGreaterThan(0);
  });

  it("updates user email", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updatedUser = { ...activeUser, email: "newemail@example.com" };

    // First select: find the user being updated; second select: no conflicting email
    mock.enqueueResults([activeUser], [], [updatedUser]);

    const result = await mock.withTx((trx) =>
      run(trx, { id: activeUser.id, email: "newemail@example.com" }),
    );
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.user.email).toBe("newemail@example.com");
    }
    expect(mock.updates.length).toBeGreaterThan(0);
  });

  it("updates both name and email", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updatedUser = { ...activeUser, name: "New Name", email: "newemail@example.com" };

    // First select: find the user being updated; second select: no conflicting email
    mock.enqueueResults([activeUser], [], [updatedUser]);

    const result = await mock.withTx((trx) =>
      run(trx, {
        id: activeUser.id,
        name: "New Name",
        email: "newemail@example.com",
      }),
    );
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.user.name).toBe("New Name");
      expect(result.value.user.email).toBe("newemail@example.com");
    }
    expect(mock.updates.length).toBeGreaterThan(0);
  });

  it("updates user in PENDING status", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updatedUser = { ...pendingUser, name: "Updated Pending" };

    mock.enqueueResults([pendingUser], [updatedUser]);

    const result = await mock.withTx((trx) =>
      run(trx, { id: pendingUser.id, name: "Updated Pending" }),
    );
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.user.name).toBe("Updated Pending");
    }
  });

  it("updates user in ACTIVE status", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updatedUser = { ...activeUser, name: "Updated Active" };

    mock.enqueueResults([activeUser], [updatedUser]);

    const result = await mock.withTx((trx) =>
      run(trx, { id: activeUser.id, name: "Updated Active" }),
    );
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.user.name).toBe("Updated Active");
    }
  });

  it("updates user in INACTIVE status", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updatedUser = { ...inactiveUser, name: "Updated Inactive" };

    mock.enqueueResults([inactiveUser], [updatedUser]);

    const result = await mock.withTx((trx) =>
      run(trx, { id: inactiveUser.id, name: "Updated Inactive" }),
    );
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.user.name).toBe("Updated Inactive");
    }
  });

  it("succeeds when email is unchanged (same as current)", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updatedUser = { ...activeUser };

    mock.enqueueResults([activeUser], [updatedUser]);

    // Email matches current — should skip uniqueness check
    const result = await mock.withTx((trx) =>
      run(trx, { id: activeUser.id, email: activeUser.email }),
    );
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.user.email).toBe(activeUser.email);
    }
    expect(mock.updates.length).toBeGreaterThan(0);
  });

  // Error cases
  it("returns error when user does not exist", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);

    const result = await mock.withTx((trx) => run(trx, { id: "nonexistent-user", name: "Test" }));
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(UserNotFoundError);
    }
  });

  it("returns error when no fields are provided", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) => run(trx, { id: "user-active-1" }));
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(MissingRequiredFieldError);
    }
  });

  it("returns error when name is empty", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) => run(trx, { id: "user-active-1", name: "" }));
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidNameError);
    }
  });

  it("returns error when name is whitespace only", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) => run(trx, { id: "user-active-1", name: "   " }));
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidNameError);
    }
  });

  it("returns error when email format is invalid", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) =>
      run(trx, { id: "user-active-1", email: "invalidemail" }),
    );
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidEmailError);
    }
  });

  it("returns error when email is already used by another user", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const anotherUser = { ...activeUser, id: "other-user", email: "taken@example.com" };

    // First select: find the user being updated
    // Second select: find the conflicting user with the same email
    mock.enqueueResults([activeUser], [anotherUser]);

    const result = await mock.withTx((trx) =>
      run(trx, { id: activeUser.id, email: "taken@example.com" }),
    );
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(UserAlreadyExistsError);
    }
  });
});
