import type { CommandContext } from "@tailor-platform/erp-kit/core";
import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it, vi } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import {
  InvalidSupplyPlanQuantitiesError,
  ItemNotFoundError,
  SiteNotFoundError,
  SupplyPlanUnitMismatchError,
} from "../lib/errors.generated";
import {
  mockItemManagementQueries,
  type ItemManagementQueryItem,
} from "../testing/commandTestUtils";
import { baseActiveItem, baseActiveSite, baseSupplyPlan } from "../testing/fixtures";
import {
  run,
  type CreateInventorySupplyPlanInput,
  type CreateInventorySupplyPlanLineInput,
} from "./createInventorySupplyPlan";

const ok = <T>(value: T) => ({ ok: true as const, value });

type OrganizationQueries = Parameters<typeof run>[4];

function mockOrganizationQueries(site: unknown): OrganizationQueries {
  return {
    getSite: vi.fn().mockResolvedValue(ok({ site })),
  } as unknown as OrganizationQueries;
}

function mockItemQueries(item: ItemManagementQueryItem) {
  return mockItemManagementQueries(item);
}

describe("createInventorySupplyPlan", () => {
  const ctx: CommandContext = {
    actorId: "test-actor",
    permissions: ["inventory:supplyPlan:createInventorySupplyPlan"],
  };

  const supplyPlanInput: CreateInventorySupplyPlanLineInput = {
    sourceLineId: "po-line-1",
    itemId: "item-1",
    siteId: "site-1",
    expectedQuantity: "100",
    unitId: "unit-ea",
    expectedDate: new Date("2026-06-01T00:00:00.000Z"),
  };
  const input: CreateInventorySupplyPlanInput = {
    sourceType: "PURCHASE_ORDER",
    sourceId: "po-1",
    supplyPlans: [supplyPlanInput],
  };

  it("creates a new open supply plan", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseSupplyPlan]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        input,
        ctx,
        mockItemQueries(baseActiveItem),
        mockOrganizationQueries(baseActiveSite),
      ),
    );
    expect(result.ok).toBe(true);
    if (!result.ok) {
      throw result.error;
    }
    const { supplyPlans } = result.value;

    expect(supplyPlans[0]?.receivedQuantity).toBe("0");
    expect(supplyPlans[0]?.status).toBe("OPEN");
    expect(mock.inserts.length).toBeGreaterThan(0);
  });

  it("creates a supply plan with custom fields", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([{ ...baseSupplyPlan, planningType: "firm" }]);

    const result = await mock.withTx((trx) =>
      run<{ planningType: string }>(
        trx,
        { ...input, supplyPlans: [{ ...supplyPlanInput, planningType: "firm" }] },
        ctx,
        mockItemQueries(baseActiveItem),
        mockOrganizationQueries(baseActiveSite),
      ),
    );
    expect(result.ok).toBe(true);
    if (!result.ok) {
      throw result.error;
    }
    const { supplyPlans } = result.value;

    expect(supplyPlans[0]).toHaveProperty("planningType", "firm");
    expect(mock.inserts[0].insertRows()).toEqual([
      expect.objectContaining({ planningType: "firm" }),
    ]);
  });

  it("creates a supply plan for another source line", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([{ ...baseSupplyPlan, id: "supply-plan-2", sourceLineId: "po-line-2" }]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          ...input,
          supplyPlans: [{ ...supplyPlanInput, sourceLineId: "po-line-2", expectedQuantity: "40" }],
        },
        ctx,
        mockItemQueries(baseActiveItem),
        mockOrganizationQueries(baseActiveSite),
      ),
    );

    expect(result.ok).toBe(true);
    if (!result.ok) {
      throw result.error;
    }
    const { supplyPlans } = result.value;

    expect(supplyPlans[0]?.id).toBe("supply-plan-2");
    expect(mock.inserts[0].insertRows()).toEqual([
      expect.objectContaining({ sourceLineId: "po-line-2" }),
    ]);
  });

  it("creates multiple supply plans in one insert", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([
      baseSupplyPlan,
      { ...baseSupplyPlan, id: "supply-plan-2", sourceLineId: "po-line-2" },
    ]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          sourceType: "PURCHASE_ORDER",
          sourceId: "po-1",
          supplyPlans: [
            supplyPlanInput,
            {
              ...supplyPlanInput,
              sourceLineId: "po-line-2",
              expectedQuantity: "40",
            },
          ],
        },
        ctx,
        mockItemQueries(baseActiveItem),
        mockOrganizationQueries(baseActiveSite),
      ),
    );
    expect(result.ok).toBe(true);
    if (!result.ok) {
      throw result.error;
    }
    const { supplyPlans } = result.value;

    expect(supplyPlans).toHaveLength(2);
    expect(mock.inserts[0].insertRows()).toEqual([
      expect.objectContaining({ sourceLineId: "po-line-1" }),
      expect.objectContaining({ sourceLineId: "po-line-2" }),
    ]);
  });

  it("returns no rows when supplyPlans is empty", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { sourceType: "PURCHASE_ORDER", sourceId: "po-1", supplyPlans: [] },
        ctx,
        mockItemQueries(baseActiveItem),
        mockOrganizationQueries(baseActiveSite),
      ),
    );
    expect(result.ok).toBe(true);
    if (!result.ok) {
      throw result.error;
    }
    const { supplyPlans } = result.value;

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

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

    const result = await mock.withTx((trx) =>
      run(trx, input, ctx, mockItemQueries(null), mockOrganizationQueries(baseActiveSite)),
    );

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

  it("returns error when quantity unit does not match the item unit", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { ...input, supplyPlans: [{ ...supplyPlanInput, unitId: "unit-box" }] },
        ctx,
        mockItemQueries(baseActiveItem),
        mockOrganizationQueries(baseActiveSite),
      ),
    );

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

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

    const result = await mock.withTx((trx) =>
      run(trx, input, ctx, mockItemQueries(baseActiveItem), mockOrganizationQueries(null)),
    );

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

  it("allows expected quantity to be zero", async () => {
    const zeroSupplyPlan = { ...baseSupplyPlan, expectedQuantity: "0" };
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([zeroSupplyPlan]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { ...input, supplyPlans: [{ ...supplyPlanInput, expectedQuantity: "0" }] },
        ctx,
        mockItemQueries(baseActiveItem),
        mockOrganizationQueries(baseActiveSite),
      ),
    );
    expect(result.ok).toBe(true);
    if (!result.ok) {
      throw result.error;
    }
    const { supplyPlans } = result.value;

    expect(supplyPlans[0]?.expectedQuantity).toBe("0");
  });

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

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { ...input, supplyPlans: [{ ...supplyPlanInput, expectedQuantity: "-1" }] },
        ctx,
        mockItemQueries(baseActiveItem),
        mockOrganizationQueries(baseActiveSite),
      ),
    );

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