import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  InvalidEmailError,
  InvalidNameError,
  InvalidStatusTransitionError,
  MissingRequiredFieldError,
  UserAlreadyExistsError,
  UserNotFoundError,
} from "../lib/errors.generated";
import { activeUser, inactiveUser, pendingUser } from "../testing/fixtures";
import { run } from "./updateOwnProfile";
import type { CommandContext } from "@tailor-platform/erp-kit/module";

describe("updateOwnProfile", () => {
  const ctx = { actorId: activeUser.id } as CommandContext;

  // Success cases
  it("updates own name", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updatedUser = { ...activeUser, name: "New Name" };

    spies.select.mockReturnValue(activeUser);
    spies.update.mockReturnValue(updatedUser);

    const result = await run(db, { name: "New Name" }, ctx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.user.name).toBe("New Name");
    }
    expect(spies.update).toHaveBeenCalled();
  });

  it("updates own email", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updatedUser = { ...activeUser, email: "newemail@example.com" };

    spies.select.mockReturnValue(activeUser);
    // Email uniqueness check returns no existing user
    spies.select.mockReturnValueOnce(activeUser).mockReturnValueOnce(undefined);
    spies.update.mockReturnValue(updatedUser);

    const result = await run(db, { email: "newemail@example.com" }, ctx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.user.email).toBe("newemail@example.com");
    }
    expect(spies.update).toHaveBeenCalled();
  });

  it("updates both own name and email", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updatedUser = { ...activeUser, name: "New Name", email: "newemail@example.com" };

    spies.select.mockReturnValueOnce(activeUser).mockReturnValueOnce(undefined);
    spies.update.mockReturnValue(updatedUser);

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

    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(spies.update).toHaveBeenCalled();
  });

  it("succeeds when email is unchanged (same as current)", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updatedUser = { ...activeUser };

    spies.select.mockReturnValue(activeUser);
    spies.update.mockReturnValue(updatedUser);

    // Using the same email as the active user — no uniqueness check needed
    const result = await run(db, { email: activeUser.email }, ctx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.user.email).toBe(activeUser.email);
    }
    expect(spies.update).toHaveBeenCalled();
  });

  // Error cases
  it("returns error when caller user does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValue(undefined);

    const result = await run(db, { name: "New Name" }, {
      actorId: "nonexistent",
    } as CommandContext);

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(UserNotFoundError);
    }
  });

  it("returns error when caller is in PENDING status", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValue(pendingUser);

    const pendingCtx = { actorId: pendingUser.id } as CommandContext;
    const result = await run(db, { name: "New Name" }, pendingCtx);

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidStatusTransitionError);
    }
  });

  it("returns error when caller is in INACTIVE status", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValue(inactiveUser);

    const inactiveCtx = { actorId: inactiveUser.id } as CommandContext;
    const result = await run(db, { name: "New Name" }, inactiveCtx);

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidStatusTransitionError);
    }
  });

  it("returns error when no fields are provided", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValue(activeUser);

    const result = await run(db, {}, ctx);

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(MissingRequiredFieldError);
    }
  });

  it("returns error when name is empty", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValue(activeUser);

    const result = await run(db, { name: "" }, ctx);

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidNameError);
    }
  });

  it("returns error when name is whitespace only", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValue(activeUser);

    const result = await run(db, { name: "   " }, ctx);

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidNameError);
    }
  });

  it("returns error when email format is invalid", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValue(activeUser);

    const result = await run(db, { email: "invalidemail" }, ctx);

    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 { db, spies } = createMockDb<Transaction>();
    const otherUser = { ...activeUser, id: "other-user", email: "taken@example.com" };

    // First select: find the caller user; second select: email uniqueness check finds another user
    spies.select.mockReturnValueOnce(activeUser).mockReturnValueOnce(otherUser);

    const result = await run(db, { email: "taken@example.com" }, ctx);

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(UserAlreadyExistsError);
    }
  });
});
