import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import { baseRootCategory, baseChildCategory } from "../testing/fixtures";
import {
  MissingRequiredFieldError,
  DuplicateCodeError,
  ParentNotFoundError,
  MaxDepthExceededError,
} from "../lib/errors.generated";
import { run } from "./createProductCategory";
import type { CommandContext } from "@tailor-platform/erp-kit/module";

describe("createProductCategory", () => {
  const ctx: CommandContext = {
    actorId: "test-actor",
    permissions: ["product-management:createProductCategory"],
  };

  const deps = { maxDepth: 3 };

  it("should return MissingRequiredFieldError when code is empty", async () => {
    const { db } = createMockDb<Transaction>();
    const input = { code: "", name: "Clothing" };
    const result = await run(db, input, ctx, deps);
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(MissingRequiredFieldError);
    }
  });

  it("should return MissingRequiredFieldError when name is empty", async () => {
    const { db } = createMockDb<Transaction>();
    const input = { code: "CLOTHING", name: "" };
    const result = await run(db, input, ctx, deps);
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(MissingRequiredFieldError);
    }
  });

  it("should return DuplicateCodeError when code already exists", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // getProductCategory by code returns existing
    spies.select.mockReturnValueOnce(baseRootCategory);
    const input = { code: "CLOTHING", name: "Clothing" };
    const result = await run(db, input, ctx, deps);
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(DuplicateCodeError);
    }
  });

  it("should return ParentNotFoundError when parent does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // 1st select: code uniqueness check -> null (no duplicate)
    spies.select.mockReturnValueOnce(undefined);
    // 2nd select: getProductCategory by parentId -> null (not found)
    spies.select.mockReturnValueOnce(undefined);
    const input = {
      code: "TOPS",
      name: "Tops",
      parentId: "non-existent",
    };
    const result = await run(db, input, ctx, deps);
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(ParentNotFoundError);
    }
  });

  it("should return MaxDepthExceededError when depth exceeds maxDepth", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // 1st select: code uniqueness -> null
    spies.select.mockReturnValueOnce(undefined);
    // 2nd select: getProductCategory by parentId -> found
    spies.select.mockReturnValueOnce(baseChildCategory);
    // calculateCategoryDepth walks up the parent chain:
    // 1st iteration: categoryId=category-2, returns { parentId: "category-1" }
    spies.select.mockReturnValueOnce({ parentId: "category-1" });
    // 2nd iteration: categoryId=category-1, returns { parentId: null }
    spies.select.mockReturnValueOnce({ parentId: null });
    // depth = 2, new child depth = 3, but maxDepth = 2
    const result = await run(
      db,
      { code: "VNECKS", name: "V-Necks", parentId: baseChildCategory.id },
      ctx,
      { maxDepth: 2 },
    );
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(MaxDepthExceededError);
    }
  });

  it("should create a root category when no parentId is provided", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // code uniqueness -> null
    spies.select.mockReturnValueOnce(undefined);
    // insert returns the new category
    const newCategory = { ...baseRootCategory, id: "new-id" };
    spies.insert.mockReturnValueOnce(newCategory);

    const input = { code: "CLOTHING", name: "Clothing" };
    const result = await run(db, input, ctx, deps);
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.productCategory).toEqual(newCategory);
    }
  });

  it("should create a child category when parentId is provided", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // code uniqueness -> null
    spies.select.mockReturnValueOnce(undefined);
    // getProductCategory by parentId -> found
    spies.select.mockReturnValueOnce(baseRootCategory);
    // calculateCategoryDepth: categoryId=category-1, returns { parentId: null }
    spies.select.mockReturnValueOnce({ parentId: null });
    // depth = 1, child depth = 2, maxDepth = 3 -> ok
    const newCategory = { ...baseChildCategory, id: "new-child-id" };
    spies.insert.mockReturnValueOnce(newCategory);

    const input = {
      code: "TOPS",
      name: "Tops",
      parentId: baseRootCategory.id,
    };
    const result = await run(db, input, ctx, deps);
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.productCategory).toEqual(newCategory);
    }
  });
});
