import { Decimal } from "decimal.js";
import type { defineModule as defineItemManagementModule } from "../../item-management";
import type { Transaction } from "../generated/kysely-tailordb";
type ItemManagementModule = ReturnType<typeof defineItemManagementModule>;
type ItemManagementQueries = ItemManagementModule["queries"];
import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";
import {
  CurrencyNotFoundError,
  DuplicateRulePrecedenceError,
  InvalidEffectiveRangeError,
  InvalidMinimumQuantityError,
  InvalidUnitPriceError,
  ItemNotActiveError,
  ItemNotFoundError,
  PriceRuleNotFoundError,
  UnitNotFoundError,
} from "../lib/errors.generated";

export type UpdatePurchasePriceRuleInput = {
  id: string;
  itemId?: string;
  currencyId?: string;
  unitId?: string;
  minimumQuantity?: string;
  unitPrice?: string;
  effectiveFrom?: Date;
  effectiveTo?: Date | null;
  priority?: number;
  notes?: string | null;
};
export async function run<CF extends Record<string, unknown>>(
  db: Transaction,
  input: UpdatePurchasePriceRuleInput & Partial<CF>,
  ctx: CommandContext,
  itemManagementQueries: Pick<ItemManagementQueries, "getItem">,
) {
  const {
    id,
    itemId: inputItemId,
    currencyId: inputCurrencyId,
    unitId: inputUnitId,
    minimumQuantity: inputMinimumQuantity,
    unitPrice: inputUnitPrice,
    effectiveFrom: inputEffectiveFrom,
    effectiveTo: inputEffectiveTo,
    priority,
    notes,
    ...customFields
  } = input;
  const existing = await db
    .selectFrom("PurchasePriceRule")
    .selectAll()
    .where("id", "=", id)
    .forUpdate()
    .executeTakeFirst();
  if (!existing) {
    return err(new PriceRuleNotFoundError(id));
  }

  const itemId = inputItemId ?? existing.itemId;
  const currencyId = inputCurrencyId ?? existing.currencyId;
  const unitId = inputUnitId ?? existing.unitId;
  const minimumQuantity = inputMinimumQuantity ?? existing.minimumQuantity;
  const unitPrice = inputUnitPrice ?? existing.unitPrice;
  const effectiveFrom = inputEffectiveFrom ?? existing.effectiveFrom;
  const effectiveTo =
    inputEffectiveTo === undefined ? (existing.effectiveTo ?? null) : inputEffectiveTo;

  const { item } = (await itemManagementQueries.getItem(db, { id: itemId }, ctx)).value;
  if (!item) {
    return err(new ItemNotFoundError(itemId));
  }
  if (item.status !== "ACTIVE") {
    return err(new ItemNotActiveError(itemId));
  }
  if (
    !(await db.selectFrom("Currency").selectAll().where("id", "=", currencyId).executeTakeFirst())
  ) {
    return err(new CurrencyNotFoundError(currencyId));
  }
  if (!(await db.selectFrom("Unit").selectAll().where("id", "=", unitId).executeTakeFirst())) {
    return err(new UnitNotFoundError(unitId));
  }
  if (new Decimal(unitPrice).lte(0)) {
    return err(new InvalidUnitPriceError(unitPrice));
  }
  if (new Decimal(minimumQuantity).lt(0)) {
    return err(new InvalidMinimumQuantityError(minimumQuantity));
  }
  if (effectiveTo && effectiveTo < effectiveFrom) {
    return err(new InvalidEffectiveRangeError(id));
  }

  const conflictingRule = await db
    .selectFrom("PurchasePriceRule")
    .selectAll()
    .where("itemId", "=", itemId)
    .where("currencyId", "=", currencyId)
    .where("unitId", "=", unitId)
    .where("minimumQuantity", "=", minimumQuantity)
    .where("effectiveFrom", "=", effectiveFrom)
    .where("status", "=", "ACTIVE")
    .executeTakeFirst();
  if (conflictingRule && conflictingRule.id !== id) {
    return err(new DuplicateRulePrecedenceError(id));
  }

  const purchasePriceRule = await db
    .updateTable("PurchasePriceRule")
    .set({
      ...(customFields as Record<string, unknown>),
      itemId,
      currencyId,
      unitId,
      minimumQuantity,
      unitPrice,
      effectiveFrom,
      effectiveTo,
      priority: priority ?? existing.priority,
      notes: notes === undefined ? existing.notes : notes,
      updatedAt: new Date(),
    })
    .where("id", "=", id)
    .returningAll()
    .executeTakeFirstOrThrow();

  return ok({ purchasePriceRule });
}
