import type { CommandContext } from "@tailor-platform/erp-kit/core";
import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } 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";

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

  // Success cases
  it("updates own 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, { name: "New Name" }, ctx));

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.user.name).toBe("New Name");
    }
    expect(mock.updates.length).toBeGreaterThan(0);
  });

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

    // First select: find the caller user; second select: email uniqueness check finds no user
    mock.enqueueResults([activeUser], [], [updatedUser]);

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

    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 own name and email", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updatedUser = { ...activeUser, name: "New Name", email: "newemail@example.com" };

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

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

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

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

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

    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 caller user does not exist", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);

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

    const pendingCtx = { actorId: pendingUser.id } as CommandContext;
    const result = await mock.withTx((trx) => run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([inactiveUser]);

    const inactiveCtx = { actorId: inactiveUser.id } as CommandContext;
    const result = await mock.withTx((trx) => run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([activeUser]);

    const result = await mock.withTx((trx) => run(trx, {}, ctx));

    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"]>();
    mock.enqueueResults([activeUser]);

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

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

    const result = await mock.withTx((trx) => run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    const otherUser = { ...activeUser, id: "other-user", email: "taken@example.com" };

    // First select: find the caller user; second select: email uniqueness check finds another user
    mock.enqueueResults([activeUser], [otherUser]);

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

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