import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  DescriptionRequiredError,
  EmptyPurchaseRequisitionLinesError,
  InvalidEstimatedUnitPriceError,
  InvalidQuantityError,
  ItemNotFoundError,
  PartnerNotSupplierError,
  SupplierNotActiveError,
  SupplierNotFoundError,
} from "../lib/errors.generated";
import {
  baseCompany,
  baseItem,
  basePurchaseRequisition,
  basePurchaseRequisitionLines,
  baseSupplier,
  inactiveSupplier,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk, mockBPQueries } from "../testing/commandTestUtils";
import { run } from "./createPurchaseRequisition";

describe("createPurchaseRequisition", () => {
  it("creates a draft purchase requisition with valid requester and lines", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseItem); // selectOne Item
    spies.insert.mockReturnValueOnce(basePurchaseRequisition);

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        requesterId: "requester-1",
        lines: basePurchaseRequisitionLines,
      },
      commandCtx,
      mockBPQueries(),
    );

    expect(expectOk(result).purchaseRequisition.status).toBe("DRAFT");
  });

  it("creates a requisition with free-text lines without item references", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.insert.mockReturnValueOnce(basePurchaseRequisition);

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        requesterId: "requester-1",
        lines: [
          {
            description: "Custom item description",
            quantity: "5",
            estimatedUnitPrice: "10",
          },
        ],
      },
      commandCtx,
      mockBPQueries(),
    );

    expect(expectOk(result).purchaseRequisition.status).toBe("DRAFT");
  });

  it("creates a requisition with optional suggested supplier on lines", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const bpQ = mockBPQueries({ partner: baseSupplier, partnerRole: { role: "SUPPLIER" } });
    spies.select.mockReturnValueOnce(baseItem); // selectOne Item
    spies.insert.mockReturnValueOnce(basePurchaseRequisition);

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        requesterId: "requester-1",
        lines: [
          {
            ...basePurchaseRequisitionLines[0],
            suggestedSupplierId: baseSupplier.id,
          },
        ],
      },
      commandCtx,
      bpQ,
    );

    expect(expectOk(result).purchaseRequisition.status).toBe("DRAFT");
  });

  it("returns error when no lines are provided", async () => {
    const { db } = createMockDb<Transaction>();

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        requesterId: "requester-1",
        lines: [],
      },
      commandCtx,
      mockBPQueries(),
    );

    expectErr(result, EmptyPurchaseRequisitionLinesError);
  });

  it("returns error when a line is missing a description", async () => {
    const { db } = createMockDb<Transaction>();

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        requesterId: "requester-1",
        lines: [{ ...basePurchaseRequisitionLines[0], description: "" }],
      },
      commandCtx,
      mockBPQueries(),
    );

    expectErr(result, DescriptionRequiredError);
  });

  it("returns error when quantity is zero or negative", async () => {
    const { db } = createMockDb<Transaction>();

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        requesterId: "requester-1",
        lines: [{ ...basePurchaseRequisitionLines[0], quantity: "0" }],
      },
      commandCtx,
      mockBPQueries(),
    );

    expectErr(result, InvalidQuantityError);
  });

  it("returns error when estimated unit price is negative", async () => {
    const { db } = createMockDb<Transaction>();

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        requesterId: "requester-1",
        lines: [{ ...basePurchaseRequisitionLines[0], estimatedUnitPrice: "-1" }],
      },
      commandCtx,
      mockBPQueries(),
    );

    expectErr(result, InvalidEstimatedUnitPriceError);
  });

  it("returns error when a referenced item does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(undefined); // selectOne Item

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        requesterId: "requester-1",
        lines: [{ ...basePurchaseRequisitionLines[0], itemId: "missing-item" }],
      },
      commandCtx,
      mockBPQueries(),
    );

    expectErr(result, ItemNotFoundError);
  });

  it("returns error when a suggested supplier does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const bpQ = mockBPQueries({ partner: undefined });
    spies.select.mockReturnValueOnce(baseItem); // selectOne Item

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        requesterId: "requester-1",
        lines: [
          {
            ...basePurchaseRequisitionLines[0],
            suggestedSupplierId: "missing-supplier",
          },
        ],
      },
      commandCtx,
      bpQ,
    );

    expectErr(result, SupplierNotFoundError);
  });

  it("returns error when a suggested supplier is inactive", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const bpQ = mockBPQueries({ partner: inactiveSupplier });
    spies.select.mockReturnValueOnce(baseItem); // selectOne Item

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        requesterId: "requester-1",
        lines: [
          {
            ...basePurchaseRequisitionLines[0],
            suggestedSupplierId: inactiveSupplier.id,
          },
        ],
      },
      commandCtx,
      bpQ,
    );

    expectErr(result, SupplierNotActiveError);
  });

  it("returns error when a suggested supplier is not classified as a supplier", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const bpQ = mockBPQueries({ partner: baseSupplier, partnerRole: undefined });
    spies.select.mockReturnValueOnce(baseItem); // selectOne Item

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        requesterId: "requester-1",
        lines: [
          {
            ...basePurchaseRequisitionLines[0],
            suggestedSupplierId: baseSupplier.id,
          },
        ],
      },
      commandCtx,
      bpQ,
    );

    expectErr(result, PartnerNotSupplierError);
  });
});
