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

export interface UpdateInventorySupplyPlanInput {
  id: string;
  sourceType?: InventorySupplyPlanSourceType;
  sourceLineId?: string;
  sourceId?: string;
  itemId?: string;
  siteId?: string;
  expectedQuantity?: string;
  unitId?: string;
  expectedDate?: Date;
  lotId?: string | null;
  serialNumberId?: string | null;
}

export async function run<CF extends object = object>(
  db: Transaction,
  input: UpdateInventorySupplyPlanInput & Omit<Partial<CF>, "status" | "receivedQuantity">,
  ctx: CommandContext,
  itemManagementQueries: Pick<ItemManagementQueries, "getItem">,
  organizationQueries: Pick<OrganizationQueries, "getSite">,
) {
  const {
    id,
    sourceType,
    sourceLineId,
    sourceId,
    itemId,
    siteId,
    expectedQuantity: inputExpectedQuantity,
    unitId,
    expectedDate,
    lotId,
    serialNumberId,
    ...customFields
  } = input;

  const existing = await db
    .selectFrom("InventorySupplyPlan")
    .selectAll()
    .where("id", "=", id)
    .forUpdate()
    .executeTakeFirst();
  if (!existing) {
    return err(new InventorySupplyPlanNotFoundError(id));
  }
  const nextStatus = inventorySupplyPlanLifecycle.tryTransition(existing.status, "update");
  if (!nextStatus) {
    return err(new SupplyPlanNotOpenError(existing.id));
  }

  const updateSet: Record<string, unknown> = { ...customFields, status: nextStatus };

  if (sourceId !== undefined) {
    updateSet.sourceId = sourceId;
  }
  if (sourceType !== undefined) {
    updateSet.sourceType = sourceType;
  }
  if (sourceLineId !== undefined) {
    updateSet.sourceLineId = sourceLineId;
  }

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

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

  const effectiveExpectedQuantity = inputExpectedQuantity ?? existing.expectedQuantity;
  const expectedQuantity = new Decimal(effectiveExpectedQuantity);
  if (expectedQuantity.lt(0)) {
    return err(new InvalidSupplyPlanQuantitiesError(effectiveExpectedQuantity));
  }
  if (inputExpectedQuantity !== undefined) {
    updateSet.expectedQuantity = inputExpectedQuantity;
  }
  if (expectedDate !== undefined) {
    updateSet.expectedDate = expectedDate;
  }
  if (lotId !== undefined) {
    updateSet.lotId = lotId;
  }
  if (serialNumberId !== undefined) {
    updateSet.serialNumberId = serialNumberId;
  }

  const supplyPlan = await db
    .updateTable("InventorySupplyPlan")
    .set(updateSet)
    .where("id", "=", existing.id)
    .returningAll()
    .executeTakeFirstOrThrow();

  return ok({ supplyPlan });
}
