import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import {
  InventorySupplyPlanNotFoundError,
  InvalidSupplyPlanQuantitiesError,
  SupplyPlanNotOpenError,
} from "../lib/errors.generated";
import { expectOk } from "../testing/commandTestUtils";
import { baseSupplyPlan } from "../testing/fixtures";
import {
  consumeInventorySupplyPlanInternal,
  consumeReceiptInventorySupplyPlansInternal,
  getOpenInventorySupplyPlanQuantity,
} from "./inventorySupplyPlan";

describe("inventorySupplyPlan internal helpers", () => {
  it("calculates open supply plan quantity", () => {
    expect(
      getOpenInventorySupplyPlanQuantity({
        expectedQuantity: "100",
        receivedQuantity: "40",
      }).toString(),
    ).toBe("60");
    expect(
      getOpenInventorySupplyPlanQuantity({
        expectedQuantity: "40",
        receivedQuantity: "100",
      }).toString(),
    ).toBe("0");
  });

  it("consumes supply plan internally", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updated = { ...baseSupplyPlan, receivedQuantity: "30", status: "OPEN" };
    mock.enqueueResults([baseSupplyPlan], [updated]);

    const result = await mock.withTx((trx) =>
      consumeInventorySupplyPlanInternal(trx, {
        id: "supply-plan-1",
        consumedQuantity: "30",
      }),
    );
    const { supplyPlan } = expectOk<{ supplyPlan: { receivedQuantity: string; status: string } }>(
      result as never,
    );

    expect(supplyPlan.receivedQuantity).toBe("30");
    expect(supplyPlan.status).toBe("OPEN");
  });

  it("returns error when supply plan not found", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);

    const result = await mock.withTx((trx) =>
      consumeInventorySupplyPlanInternal(trx, {
        id: "supply-plan-1",
        consumedQuantity: "1",
      }),
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InventorySupplyPlanNotFoundError);
    }
  });

  it("returns error when consumed quantity is invalid", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) =>
      consumeInventorySupplyPlanInternal(trx, {
        id: "supply-plan-1",
        consumedQuantity: "0",
      }),
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidSupplyPlanQuantitiesError);
    }
  });

  it("returns error when supply plan is not open", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([{ ...baseSupplyPlan, status: "CLOSED" }]);

    const result = await mock.withTx((trx) =>
      consumeInventorySupplyPlanInternal(trx, {
        id: "supply-plan-1",
        consumedQuantity: "1",
      }),
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(SupplyPlanNotOpenError);
    }
  });

  it("consumes matching receipt supply plans internally", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const supplyPlan = { ...baseSupplyPlan, expectedQuantity: "10", receivedQuantity: "2" };
    const updated = { ...supplyPlan, receivedQuantity: "7", status: "OPEN" };

    mock.enqueueResults([supplyPlan], [supplyPlan], [updated]);

    const result = await mock.withTx((trx) =>
      consumeReceiptInventorySupplyPlansInternal(trx, {
        itemId: "item-1",
        siteId: "site-1",
        quantity: "5",
        sourceDocumentType: "PURCHASE_ORDER",
        sourceDocumentId: "po-1",
        sourceLineId: "po-line-1",
        lotId: null,
        serialNumberId: null,
      }),
    );
    const { supplyPlans } = expectOk<{ supplyPlans: { receivedQuantity: string }[] }>(
      result as never,
    );

    expect(supplyPlans).toHaveLength(1);
    expect(supplyPlans[0]?.receivedQuantity).toBe("7");
    expect(mock.updates[0].updateValues()).toEqual(
      expect.objectContaining({
        receivedQuantity: "7",
        status: "OPEN",
      }),
    );
  });

  it("skips receipt supply matching when source linkage is missing", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) =>
      consumeReceiptInventorySupplyPlansInternal(trx, {
        itemId: "item-1",
        siteId: "site-1",
        quantity: "5",
        sourceDocumentType: "PURCHASE_ORDER",
        sourceDocumentId: "po-1",
        sourceLineId: null,
        lotId: null,
        serialNumberId: null,
      }),
    );
    const { supplyPlans } = expectOk<{ supplyPlans: unknown[] }>(result as never);

    expect(supplyPlans).toEqual([]);
    expect(mock.selects).toHaveLength(0);
    expect(mock.updates).toHaveLength(0);
  });

  it("consumes open supply quantity when receipt quantity exceeds the supply plan", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const supplyPlan = { ...baseSupplyPlan, expectedQuantity: "4" };
    const updated = { ...supplyPlan, receivedQuantity: "4", status: "OPEN" };

    mock.enqueueResults([supplyPlan], [supplyPlan], [updated]);

    const result = await mock.withTx((trx) =>
      consumeReceiptInventorySupplyPlansInternal(trx, {
        itemId: "item-1",
        siteId: "site-1",
        quantity: "5",
        sourceDocumentType: "PURCHASE_ORDER",
        sourceDocumentId: "po-1",
        sourceLineId: "po-line-1",
        lotId: null,
        serialNumberId: null,
      }),
    );
    const { supplyPlans } = expectOk<{ supplyPlans: { receivedQuantity: string }[] }>(
      result as never,
    );

    expect(supplyPlans).toHaveLength(1);
    expect(supplyPlans[0]?.receivedQuantity).toBe("4");
  });

  it("skips receipt supply matching when no supply plan matches", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);

    const result = await mock.withTx((trx) =>
      consumeReceiptInventorySupplyPlansInternal(trx, {
        itemId: "item-1",
        siteId: "site-1",
        quantity: "5",
        sourceDocumentType: "PURCHASE_ORDER",
        sourceDocumentId: "po-1",
        sourceLineId: "po-line-1",
        lotId: null,
        serialNumberId: null,
      }),
    );
    const { supplyPlans } = expectOk<{ supplyPlans: unknown[] }>(result as never);

    expect(supplyPlans).toEqual([]);
    expect(mock.updates).toHaveLength(0);
  });
});
