import type { DB } from "../generated/kysely-tailordb";
import { InvalidPricingContextError } from "../lib/errors.generated";
import { effectiveDateInRange } from "../lib/domain";
import { err, ok, type ReadonlyDB } from "@tailor-platform/erp-kit/module";

export interface GetApplicableSalesPriceInput {
  companyId: string;
  itemId: string;
  customerId?: string | null;
  channelId?: string | null;
  currencyId: string;
  unitId: string;
  quantity: number;
  pricingDate?: Date;
}

export async function run(db: ReadonlyDB<DB>, input: GetApplicableSalesPriceInput) {
  if (
    !input.companyId ||
    !input.itemId ||
    !input.currencyId ||
    !input.unitId ||
    input.quantity === undefined ||
    input.quantity < 0
  ) {
    return err(
      new InvalidPricingContextError(input.companyId || input.itemId || "pricing-context"),
    );
  }

  const pricingDate = input.pricingDate ?? new Date();
  const lists = await db
    .selectFrom("SalesPriceList")
    .selectAll()
    .where("companyId", "=", input.companyId)
    .where("status", "=", "ACTIVE")
    .execute();
  const rules = await db
    .selectFrom("SalesPriceRule")
    .selectAll()
    .where("itemId", "=", input.itemId)
    .where("status", "=", "ACTIVE")
    .execute();

  const listById = new Map(lists.map((list) => [list.id, list]));
  const matchingRules = rules
    .filter((rule) => listById.has(rule.salesPriceListId))
    .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))
    .filter((rule) => !rule.customerId || rule.customerId === input.customerId)
    .filter((rule) => !rule.channelId || rule.channelId === input.channelId)
    .sort((left, right) => {
      const leftList = listById.get(left.salesPriceListId)!;
      const rightList = listById.get(right.salesPriceListId)!;
      const leftSpecificity = Number(Boolean(left.customerId)) + Number(Boolean(left.channelId));
      const rightSpecificity = Number(Boolean(right.customerId)) + Number(Boolean(right.channelId));
      if (leftSpecificity !== rightSpecificity) {
        return rightSpecificity - leftSpecificity;
      }
      if (left.minimumQuantity !== right.minimumQuantity) {
        return right.minimumQuantity - left.minimumQuantity;
      }
      if (leftList.priority !== rightList.priority) {
        return rightList.priority - leftList.priority;
      }
      return right.priority - left.priority;
    });

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

  return ok({ priceSuggestion, matchedRule });
}
