import { listNights, nightsBetween, type StayRange } from "@/domain/date-ranges";

export type NightlyRateSource = "base" | "season" | "override";

export type NightlyRate = {
  date: string;
  amountMinor: number;
  source: NightlyRateSource;
};

export type PriceLine = {
  code: string;
  label: string;
  amountMinor: number;
};

export type PercentageTax = {
  code: string;
  label: string;
  percentageBps: number;
};

export type QuoteInput = {
  range: StayRange;
  currency: string;
  baseNightlyMinor: number;
  overrides?: Record<string, number>;
  fees?: PriceLine[];
  taxes?: PercentageTax[];
  discounts?: PriceLine[];
  depositMinor?: number;
};

export type Quote = {
  nights: number;
  currency: string;
  nightlyPrices: NightlyRate[];
  subtotalMinor: number;
  fees: PriceLine[];
  taxes: PriceLine[];
  discounts: PriceLine[];
  depositMinor: number;
  totalMinor: number;
};

function assertMinorUnit(value: number, label: string) {
  if (!Number.isInteger(value) || value < 0) {
    throw new Error(`${label} must be a non-negative integer minor-unit amount`);
  }
}

export function calculateQuote(input: QuoteInput): Quote {
  assertMinorUnit(input.baseNightlyMinor, "baseNightlyMinor");

  const nights = nightsBetween(input.range);
  const nightlyPrices = listNights(input.range).map<NightlyRate>((date) => {
    const override = input.overrides?.[date];

    if (override !== undefined) {
      assertMinorUnit(override, `override ${date}`);
      return { date, amountMinor: override, source: "override" };
    }

    return {
      date,
      amountMinor: input.baseNightlyMinor,
      source: "base",
    };
  });

  const subtotalMinor = nightlyPrices.reduce(
    (total, night) => total + night.amountMinor,
    0
  );

  const fees = input.fees ?? [];
  const discounts = input.discounts ?? [];
  const feeTotal = fees.reduce((total, fee) => {
    assertMinorUnit(fee.amountMinor, fee.code);
    return total + fee.amountMinor;
  }, 0);
  const discountTotal = discounts.reduce((total, discount) => {
    assertMinorUnit(discount.amountMinor, discount.code);
    return total + discount.amountMinor;
  }, 0);

  const taxableMinor = Math.max(subtotalMinor + feeTotal - discountTotal, 0);
  const taxes = (input.taxes ?? []).map<PriceLine>((tax) => {
    if (!Number.isInteger(tax.percentageBps) || tax.percentageBps < 0) {
      throw new Error(`${tax.code} percentage must be non-negative basis points`);
    }

    return {
      code: tax.code,
      label: tax.label,
      amountMinor: Math.round((taxableMinor * tax.percentageBps) / 10_000),
    };
  });

  const taxTotal = taxes.reduce((total, tax) => total + tax.amountMinor, 0);
  const depositMinor = input.depositMinor ?? 0;
  assertMinorUnit(depositMinor, "depositMinor");

  return {
    nights,
    currency: input.currency,
    nightlyPrices,
    subtotalMinor,
    fees,
    taxes,
    discounts,
    depositMinor,
    totalMinor: Math.max(taxableMinor + taxTotal, 0),
  };
}
