import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  HierarchyNotFoundError,
  ParentNodeNotFoundError,
  DuplicateRootNodeError,
  SelfReferenceError,
  CircularReferenceError,
} from "../lib/errors.generated";
import {
  baseProfitCenterHierarchy,
  rootProfitCenterNode,
  childProfitCenterNode,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./createProfitCenterHierarchyNode";

describe("createProfitCenterHierarchyNode", () => {
  it("creates a root node in an existing hierarchy", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // hierarchy exists, no existing root, insert returns node
    spies.select
      .mockReturnValueOnce(baseProfitCenterHierarchy) // hierarchy lookup
      .mockReturnValueOnce(undefined); // no existing root
    spies.insert.mockReturnValueOnce(rootProfitCenterNode);

    const result = await run(
      db,
      { hierarchyId: baseProfitCenterHierarchy.id, name: "Root" },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.profitCenterHierarchyNode).toEqual(rootProfitCenterNode);
  });

  it("creates a child node under an existing parent node", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseProfitCenterHierarchy) // hierarchy lookup
      .mockReturnValueOnce(rootProfitCenterNode) // parent node lookup
      .mockReturnValueOnce(rootProfitCenterNode); // ancestor walk: root has no parent, loop ends
    spies.insert.mockReturnValueOnce(childProfitCenterNode);

    const result = await run(
      db,
      {
        hierarchyId: baseProfitCenterHierarchy.id,
        name: "Product Line A",
        parentNodeId: rootProfitCenterNode.id,
      },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.profitCenterHierarchyNode).toEqual(childProfitCenterNode);
  });

  it("returns error when hierarchy does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(undefined);

    const result = await run(db, { hierarchyId: "missing-hierarchy", name: "Root" }, commandCtx);

    expectErr(result, HierarchyNotFoundError);
  });

  it("returns error when parent node does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseProfitCenterHierarchy).mockReturnValueOnce(undefined); // parent not found

    const result = await run(
      db,
      {
        hierarchyId: baseProfitCenterHierarchy.id,
        name: "Child",
        parentNodeId: "missing-parent",
      },
      commandCtx,
    );

    expectErr(result, ParentNodeNotFoundError);
  });

  it("returns error when attempting to create a second root node", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseProfitCenterHierarchy)
      .mockReturnValueOnce(rootProfitCenterNode); // existing root found

    const result = await run(
      db,
      { hierarchyId: baseProfitCenterHierarchy.id, name: "Second Root" },
      commandCtx,
    );

    expectErr(result, DuplicateRootNodeError);
  });

  it("returns error when node references itself as parent", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseProfitCenterHierarchy) // hierarchy exists
      .mockReturnValueOnce(rootProfitCenterNode) // parent node found
      .mockReturnValueOnce({ ...rootProfitCenterNode, parentNodeId: rootProfitCenterNode.id }); // ancestor walk: self-loop detected

    const result = await run(
      db,
      {
        hierarchyId: baseProfitCenterHierarchy.id,
        name: "Child",
        parentNodeId: rootProfitCenterNode.id,
      },
      commandCtx,
    );

    expectErr(result, SelfReferenceError);
  });

  it("returns error when node would create a circular reference", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // Setup: node-a -> node-b -> node-a (circular chain)
    const nodeA = { ...rootProfitCenterNode, id: "node-a", parentNodeId: "node-b" };
    const nodeB = { ...childProfitCenterNode, id: "node-b", parentNodeId: "node-a" };

    spies.select
      .mockReturnValueOnce(baseProfitCenterHierarchy) // hierarchy exists
      .mockReturnValueOnce(nodeA) // parent node (A) found
      .mockReturnValueOnce(nodeA) // ancestor walk iteration 1: fetch A, parentNodeId = "node-b"
      .mockReturnValueOnce(nodeB); // ancestor walk iteration 2: fetch B, parentNodeId = "node-a" (already visited) → circular

    const result = await run(
      db,
      {
        hierarchyId: baseProfitCenterHierarchy.id,
        name: "New Child",
        parentNodeId: "node-a",
      },
      commandCtx,
    );

    expectErr(result, CircularReferenceError);
  });

  it("emits audit event recording acting user, timestamp, and initial field values", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseProfitCenterHierarchy).mockReturnValueOnce(undefined);
    spies.insert.mockReturnValueOnce(rootProfitCenterNode);

    const result = await run(
      db,
      { hierarchyId: baseProfitCenterHierarchy.id, name: "Root" },
      commandCtx,
    );

    expectOk(result);
    expect(spies.insert).toHaveBeenCalled();
  });
});
