import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";
import { Decimal } from "decimal.js";
import type { Transaction, Updateable } from "../generated/kysely-tailordb";
import {
  CurrencyNotFoundError,
  DuplicateRulePrecedenceError,
  InvalidEffectiveRangeError,
  InvalidMinimumQuantityError,
  InvalidUnitPriceError,
  ItemNotActiveError,
  ItemNotFoundError,
  PriceRuleNotFoundError,
  UnitNotFoundError,
} from "../lib/errors.generated";
import type { ItemManagementQueries, PrimitivesQueries } from "../module";

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">,
  primitivesQueries: Pick<PrimitivesQueries, "getCurrency" | "getUnit">,
) {
  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 : 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));
  }
  const { currency } = (await primitivesQueries.getCurrency(db, { id: currencyId }, ctx)).value;
  if (!currency) {
    return err(new CurrencyNotFoundError(currencyId));
  }
  const { unit } = (await primitivesQueries.getUnit(db, { id: unitId }, ctx)).value;
  if (!unit) {
    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 updates: Updateable<"PurchasePriceRule"> = {
    ...(customFields as Updateable<"PurchasePriceRule">),
  };
  if (inputItemId !== undefined) updates.itemId = inputItemId;
  if (inputCurrencyId !== undefined) updates.currencyId = inputCurrencyId;
  if (inputUnitId !== undefined) updates.unitId = inputUnitId;
  if (inputMinimumQuantity !== undefined) updates.minimumQuantity = inputMinimumQuantity;
  if (inputUnitPrice !== undefined) updates.unitPrice = inputUnitPrice;
  if (inputEffectiveFrom !== undefined) updates.effectiveFrom = inputEffectiveFrom;
  if (inputEffectiveTo !== undefined) updates.effectiveTo = inputEffectiveTo;
  if (priority !== undefined) updates.priority = priority;
  if (notes !== undefined) updates.notes = notes;

  if (Object.keys(updates).length === 0) {
    return ok({ purchasePriceRule: existing });
  }

  const purchasePriceRule = await db
    .updateTable("PurchasePriceRule")
    .set(updates)
    .where("id", "=", id)
    .returningAll()
    .executeTakeFirstOrThrow();

  return ok({ purchasePriceRule });
}
