import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  CompanyNotFoundError,
  EmptyScheduleError,
  InvalidDiscountConfigurationError,
  InvalidInstallmentTotalError,
  InvalidScheduleLineError,
} from "../lib/errors.generated";
import {
  baseCompany,
  basePaymentTerm,
  baseScheduleLines,
  installmentScheduleLines,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./createPurchasePaymentTerm";

describe("createPurchasePaymentTerm", () => {
  it("creates an active payment term with one schedule line", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseCompany);
    spies.insert.mockReturnValueOnce(basePaymentTerm);

    const result = await run(
      db,
      { companyId: basePaymentTerm.companyId, name: "Net 30", scheduleLines: baseScheduleLines },
      commandCtx,
    );

    expect(expectOk(result).paymentTerm.status).toBe("ACTIVE");
  });

  it("creates an installment payment term when percentages total 100 percent", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseCompany);
    spies.insert.mockReturnValueOnce({
      ...basePaymentTerm,
      scheduleLines: JSON.stringify(installmentScheduleLines),
    });

    const result = await run(
      db,
      {
        companyId: basePaymentTerm.companyId,
        name: "Installment",
        scheduleLines: installmentScheduleLines,
      },
      commandCtx,
    );

    expect(expectOk(result).paymentTerm.scheduleLines).toContain("INSTALLMENT");
  });

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

    const result = await run(
      db,
      { companyId: "missing-company", name: "Net 30", scheduleLines: baseScheduleLines },
      commandCtx,
    );

    expectErr(result, CompanyNotFoundError);
  });

  it("returns error when no schedule lines are provided", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseCompany);

    const result = await run(
      db,
      { companyId: basePaymentTerm.companyId, name: "Empty", scheduleLines: [] },
      commandCtx,
    );

    expectErr(result, EmptyScheduleError);
  });

  it("returns error when installment percentages do not total 100 percent", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseCompany);

    const result = await run(
      db,
      {
        companyId: basePaymentTerm.companyId,
        name: "Broken",
        scheduleLines: [{ kind: "INSTALLMENT", days: 30, percentage: 90 }],
      },
      commandCtx,
    );

    expectErr(result, InvalidInstallmentTotalError);
  });

  it("returns error when a schedule line contains a negative amount or percentage", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseCompany);

    const result = await run(
      db,
      {
        companyId: basePaymentTerm.companyId,
        name: "Broken",
        scheduleLines: [{ kind: "INSTALLMENT", days: 30, percentage: -10 }],
      },
      commandCtx,
    );

    expectErr(result, InvalidScheduleLineError);
  });

  it("returns error when discount cutoff is after the final due date", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseCompany);

    const result = await run(
      db,
      {
        companyId: basePaymentTerm.companyId,
        name: "Broken",
        scheduleLines: [
          {
            kind: "NET_DAYS",
            days: 30,
            discountDays: 40,
            discountPercentage: 2,
            discountBasis: "GROSS_AMOUNT",
          },
        ],
      },
      commandCtx,
    );

    expectErr(result, InvalidDiscountConfigurationError);
  });
});
