import type { DB } from "../generated/kysely-tailordb";
import {
  CurrencyNotFoundError,
  IncompatibleUnitsError,
  ItemNotActiveError,
  ItemNotFoundError,
  UnitNotFoundError,
} from "../lib/errors.generated";
import { effectiveDateInRange, isActiveStatus } from "../lib/domain";
import { err, ok, type ReadonlyDB, type QueryContext } from "@tailor-platform/erp-kit/module";

export interface CalculatePurchasePriceInput {
  companyId: string;
  supplierId: string;
  itemId: string;
  unitId: string;
  currencyId: string;
  quantity: number;
  pricingDate?: Date;
}

export async function run(
  db: ReadonlyDB<DB>,
  input: CalculatePurchasePriceInput,
  ctx?: QueryContext,
) {
  void ctx;
  const item = await db
    .selectFrom("Item")
    .selectAll()
    .where("id", "=", input.itemId)
    .executeTakeFirst();
  if (!item) {
    return err(new ItemNotFoundError(input.itemId));
  }
  if (!isActiveStatus(item.status)) {
    return err(new ItemNotActiveError(input.itemId));
  }

  const requestedUnit = await db
    .selectFrom("Unit")
    .selectAll()
    .where("id", "=", input.unitId)
    .executeTakeFirst();
  if (!requestedUnit) {
    return err(new UnitNotFoundError(input.unitId));
  }
  const currency = await db
    .selectFrom("Currency")
    .selectAll()
    .where("id", "=", input.currencyId)
    .executeTakeFirst();
  if (!currency) {
    return err(new CurrencyNotFoundError(input.currencyId));
  }

  let baseUnit = requestedUnit;
  if (item.unitId) {
    const itemUnit = await db
      .selectFrom("Unit")
      .selectAll()
      .where("id", "=", item.unitId)
      .executeTakeFirst();
    if (itemUnit) {
      baseUnit = itemUnit;
    }
  }
  if (baseUnit.categoryId !== requestedUnit.categoryId) {
    return err(new IncompatibleUnitsError(`${item.unitId ?? "unknown"}:${input.unitId}`));
  }

  const rules = await db
    .selectFrom("PurchasePriceRule")
    .selectAll()
    .where("itemId", "=", input.itemId)
    .execute();
  const lists = await db
    .selectFrom("PurchasePriceList")
    .selectAll()
    .where("companyId", "=", input.companyId)
    .where("supplierId", "=", input.supplierId)
    .execute();

  const activeListIds = new Set(
    lists.filter((list) => list.status === "ACTIVE").map((list) => list.id),
  );
  const pricingDate = input.pricingDate ?? new Date();

  const matchingRules = rules
    .filter((rule) => rule.status === "ACTIVE")
    .filter((rule) => activeListIds.has(rule.priceListId))
    .filter((rule) => rule.currencyId === input.currencyId)
    .filter((rule) => rule.unitId === input.unitId)
    .filter((rule) => input.quantity >= rule.minimumQuantity)
    .filter((rule) => effectiveDateInRange(pricingDate, rule.effectiveFrom, rule.effectiveTo))
    .sort((left, right) => {
      if (left.minimumQuantity !== right.minimumQuantity) {
        return right.minimumQuantity - left.minimumQuantity;
      }
      return right.priority - left.priority;
    });

  const matchedRule = matchingRules[0] ?? null;
  const priceSuggestion = matchedRule
    ? {
        ruleId: matchedRule.id,
        priceListId: matchedRule.priceListId,
        unitPrice: matchedRule.unitPrice,
      }
    : null;

  return ok({ priceSuggestion, matchedRule });
}
