import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";
import { Decimal } from "decimal.js";
import type { InventorySupplyPlanSourceType } from "../generated/enums";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  InvalidSupplyPlanQuantitiesError,
  ItemNotFoundError,
  SiteNotFoundError,
  SupplyPlanUnitMismatchError,
} from "../lib/errors.generated";
import type { ItemManagementQueries, OrganizationQueries } from "../module";

export interface CreateInventorySupplyPlanLineInput {
  sourceLineId: string;
  itemId: string;
  siteId: string;
  expectedQuantity: string;
  unitId: string;
  expectedDate: Date;
  lotId?: string | null;
  serialNumberId?: string | null;
}

export interface CreateInventorySupplyPlanInput<CF extends object = object> {
  sourceType: InventorySupplyPlanSourceType;
  sourceId: string;
  supplyPlans: Array<CreateInventorySupplyPlanLineInput & CF>;
}

export async function run<CF extends object = object>(
  db: Transaction,
  input: CreateInventorySupplyPlanInput<CF>,
  ctx: CommandContext,
  itemManagementQueries: Pick<ItemManagementQueries, "getItem">,
  organizationQueries: Pick<OrganizationQueries, "getSite">,
) {
  if (input.supplyPlans.length === 0) {
    return ok({ supplyPlans: [] });
  }

  const values = [];
  for (const supplyPlanInput of input.supplyPlans) {
    const {
      sourceLineId,
      itemId,
      siteId,
      expectedQuantity: inputExpectedQuantity,
      unitId,
      expectedDate,
      lotId,
      serialNumberId,
      ...customFields
    } = supplyPlanInput;

    const expectedQuantity = new Decimal(inputExpectedQuantity);
    if (expectedQuantity.lt(0)) {
      return err(new InvalidSupplyPlanQuantitiesError(inputExpectedQuantity));
    }

    const { item } = (await itemManagementQueries.getItem(db, { id: itemId }, ctx)).value;
    if (!item) {
      return err(new ItemNotFoundError(itemId));
    }
    if (unitId !== item.unitId) {
      return err(new SupplyPlanUnitMismatchError(`${unitId} expected ${item.unitId}`));
    }

    const { site } = (await organizationQueries.getSite(db, { id: siteId }, ctx)).value;
    if (!site) {
      return err(new SiteNotFoundError(siteId));
    }

    values.push({
      ...(customFields as Record<string, unknown>),
      sourceType: input.sourceType,
      sourceId: input.sourceId,
      sourceLineId,
      itemId,
      siteId,
      expectedQuantity: inputExpectedQuantity,
      receivedQuantity: "0",
      status: "OPEN" as const,
      expectedDate,
      lotId: lotId ?? null,
      serialNumberId: serialNumberId ?? null,
      createdAt: new Date(),
      updatedAt: null,
    });
  }

  const supplyPlans = await db
    .insertInto("InventorySupplyPlan")
    .values(values)
    .returningAll()
    .execute();

  return ok({ supplyPlans });
}
