import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import { ProfitCenterNotFoundError, HierarchyNodeNotFoundError } from "../lib/errors.generated";
import {
  baseProfitCenter,
  activeProfitCenter,
  rootProfitCenterNode,
  childProfitCenterNode,
  baseProfitCenterHierarchy,
  baseProfitCenterHierarchyAssignment,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./assignProfitCenterToHierarchyNode";

describe("assignProfitCenterToHierarchyNode", () => {
  it("assigns a profit center to a hierarchy node (creates new assignment)", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseProfitCenter) // profit center exists
      .mockReturnValueOnce(childProfitCenterNode) // node exists
      .mockReturnValueOnce(undefined); // no existing assignment
    spies.insert.mockReturnValueOnce(baseProfitCenterHierarchyAssignment);

    const result = await run(
      db,
      { profitCenterId: baseProfitCenter.id, hierarchyNodeId: childProfitCenterNode.id },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.profitCenterHierarchyAssignment).toEqual(baseProfitCenterHierarchyAssignment);
    expect(spies.insert).toHaveBeenCalled();
    expect(spies.update).not.toHaveBeenCalled();
  });

  it("replaces existing assignment when profit center is already assigned in the same hierarchy", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const existingAssignment = {
      ...baseProfitCenterHierarchyAssignment,
      hierarchyNodeId: rootProfitCenterNode.id,
    };
    spies.select
      .mockReturnValueOnce(activeProfitCenter) // profit center with existing assignment
      .mockReturnValueOnce(childProfitCenterNode) // new node exists
      .mockReturnValueOnce(existingAssignment); // existing assignment found
    spies.update.mockReturnValueOnce({
      ...existingAssignment,
      hierarchyNodeId: childProfitCenterNode.id,
    });

    const result = await run(
      db,
      { profitCenterId: activeProfitCenter.id, hierarchyNodeId: childProfitCenterNode.id },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.profitCenterHierarchyAssignment.hierarchyNodeId).toBe(childProfitCenterNode.id);
    expect(spies.update).toHaveBeenCalled();
    expect(spies.insert).not.toHaveBeenCalled();
  });

  it("allows a profit center to be assigned to nodes in multiple hierarchies", async () => {
    const secondHierarchyNode = {
      id: "pc-node-second-hierarchy",
      name: "Second Hierarchy Node",
      hierarchyId: "pc-hierarchy-2",
      parentNodeId: null,
      sortOrder: 1,
      level: 0,
      createdAt: baseProfitCenterHierarchy.createdAt,
      updatedAt: null,
    };
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseProfitCenter) // profit center exists
      .mockReturnValueOnce(secondHierarchyNode) // node in second hierarchy exists
      .mockReturnValueOnce(undefined); // no existing assignment in second hierarchy
    spies.insert.mockReturnValueOnce({
      ...baseProfitCenterHierarchyAssignment,
      hierarchyId: secondHierarchyNode.hierarchyId,
      hierarchyNodeId: secondHierarchyNode.id,
    });

    const result = await run(
      db,
      { profitCenterId: baseProfitCenter.id, hierarchyNodeId: secondHierarchyNode.id },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.profitCenterHierarchyAssignment.hierarchyNodeId).toBe(secondHierarchyNode.id);
    expect(spies.insert).toHaveBeenCalled();
    expect(spies.update).not.toHaveBeenCalled();
  });

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

    const result = await run(
      db,
      { profitCenterId: "missing-pc", hierarchyNodeId: childProfitCenterNode.id },
      commandCtx,
    );

    expectErr(result, ProfitCenterNotFoundError);
  });

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

    const result = await run(
      db,
      { profitCenterId: baseProfitCenter.id, hierarchyNodeId: "missing-node" },
      commandCtx,
    );

    expectErr(result, HierarchyNodeNotFoundError);
  });
});
