import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  CompanyNotFoundError,
  DuplicateCostCenterCodeError,
  InvalidCategoryError,
  HierarchyNodeNotFoundError,
  CompanyMismatchError,
  InvalidValidityPeriodError,
  InvalidNameError,
} from "../lib/errors.generated";
import {
  baseCompany,
  baseCostCenter,
  baseCostCenterHierarchy,
  childCostCenterNode,
  baseDepartment,
  baseUser,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./createCostCenter";

const validInput = {
  companyId: baseCompany.id,
  code: "CC-NEW",
  name: "New Cost Center",
  category: "overhead",
  hierarchyNodeId: childCostCenterNode.id,
  validFrom: new Date("2024-01-01"),
  responsiblePersonId: baseUser.id,
  departmentId: baseDepartment.id,
};

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

    const result = await run(db, { ...validInput, companyId: "missing" }, commandCtx);

    expectErr(result, CompanyNotFoundError);
  });

  it("returns error when company is not ACTIVE", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce({ ...baseCompany, status: "INACTIVE" });

    const result = await run(db, validInput, commandCtx);

    expectErr(result, CompanyNotFoundError);
  });

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

    const result = await run(db, { ...validInput, name: "" }, commandCtx);

    expectErr(result, InvalidNameError);
  });

  it("returns error when code already exists within company", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseCompany) // company exists
      .mockReturnValueOnce(baseCostCenter); // duplicate code found

    const result = await run(db, { ...validInput, code: baseCostCenter.code }, commandCtx);

    expectErr(result, DuplicateCostCenterCodeError);
  });

  it("returns error when category is invalid", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseCompany) // company exists
      .mockReturnValueOnce(undefined); // no duplicate code

    const result = await run(db, { ...validInput, category: "invalid_category" }, commandCtx);

    expectErr(result, InvalidCategoryError);
  });

  it("returns error when hierarchy node does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseCompany) // company exists
      .mockReturnValueOnce(undefined) // no duplicate code
      .mockReturnValueOnce(undefined); // hierarchy node not found

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

    expectErr(result, HierarchyNodeNotFoundError);
  });

  it("returns error when hierarchy node belongs to different company", async () => {
    const otherCompanyHierarchy = { ...baseCostCenterHierarchy, companyId: "other-company" };

    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseCompany) // company exists
      .mockReturnValueOnce(undefined) // no duplicate code
      .mockReturnValueOnce(childCostCenterNode) // hierarchy node found
      .mockReturnValueOnce(otherCompanyHierarchy); // hierarchy belongs to different company

    const result = await run(db, validInput, commandCtx);

    expectErr(result, CompanyMismatchError);
  });

  it("returns error when validTo is before validFrom", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseCompany) // company exists
      .mockReturnValueOnce(undefined) // no duplicate code
      .mockReturnValueOnce(childCostCenterNode) // hierarchy node found
      .mockReturnValueOnce(baseCostCenterHierarchy); // hierarchy in same company

    const result = await run(
      db,
      {
        ...validInput,
        validFrom: new Date("2024-12-31"),
        validTo: new Date("2024-01-01"),
      },
      commandCtx,
    );

    expectErr(result, InvalidValidityPeriodError);
  });

  it("creates cost center with valid inputs", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseCompany) // company exists
      .mockReturnValueOnce(undefined) // no duplicate code
      .mockReturnValueOnce(childCostCenterNode) // hierarchy node found
      .mockReturnValueOnce(baseCostCenterHierarchy); // hierarchy in same company
    spies.insert.mockReturnValueOnce(baseCostCenter);

    const result = await run(db, validInput, commandCtx);

    const value = expectOk(result);
    expect(value.costCenter).toEqual(baseCostCenter);
  });

  it("creates cost center without optional fields", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseCompany)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce(childCostCenterNode)
      .mockReturnValueOnce(baseCostCenterHierarchy);
    spies.insert.mockReturnValueOnce({
      ...baseCostCenter,
      responsiblePersonId: null,
      departmentId: null,
      description: null,
      validTo: null,
    });

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        code: "CC-NEW",
        name: "New Cost Center",
        category: "overhead",
        hierarchyNodeId: childCostCenterNode.id,
        validFrom: new Date("2024-01-01"),
      },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.costCenter).toBeDefined();
  });
});
