import { InvalidShippingAddressError, ShippingZoneNotFoundError } from '../domain/errors.js';
import type {
  ShippingRepository,
  ShippingService as ShippingServicePort,
} from '../domain/ports.js';
import type { ShippingAddress, ShippingQuote } from '../domain/shipping.js';

export class ShippingService implements ShippingServicePort {
  constructor(private readonly repo: ShippingRepository) {}

  async calculate(
    cartTotalCents: number,
    address: ShippingAddress,
    cartWeightKg = 0,
  ): Promise<ShippingQuote> {
    ensureValidAddress(address);
    ensureNonNegativeTotal(cartTotalCents);

    const match = await this.repo.findMatchingZone(address.country, address.postalCode);
    if (!match) throw new ShippingZoneNotFoundError();

    const candidates = match.methods
      .filter((method) => method.active)
      // Max weight per method: carts above the limit cannot use the method.
      .filter(
        (method) => method.maxWeightKg === null || cartWeightKg <= method.maxWeightKg,
      )
      .map((method) => {
        // Free shipping applies by total and only within the free-shipping
        // weight range configured on the method.
        const withinFreeWeightRange =
          method.freeShippingMaxWeightKg === null || cartWeightKg <= method.freeShippingMaxWeightKg;
        const freeApplied =
          method.freeShippingThresholdCents !== null &&
          cartTotalCents >= method.freeShippingThresholdCents &&
          withinFreeWeightRange;
        return { method, freeApplied, costCents: freeApplied ? 0 : method.baseCostCents };
      });
    if (candidates.length === 0) throw new ShippingZoneNotFoundError();

    candidates.sort((a, b) => a.costCents - b.costCents);
    const chosen = candidates[0];
    if (!chosen) throw new ShippingZoneNotFoundError();
    return {
      zoneId: match.zoneId,
      methodId: chosen.method.id,
      methodName: chosen.method.name,
      costCents: chosen.costCents,
      freeApplied: chosen.freeApplied,
    };
  }
}

function ensureValidAddress(address: ShippingAddress): void {
  if (!address || typeof address !== 'object') throw new InvalidShippingAddressError();
  if (typeof address.country !== 'string' || address.country.length === 0)
    throw new InvalidShippingAddressError('country is required');
  if (typeof address.postalCode !== 'string' || address.postalCode.length === 0)
    throw new InvalidShippingAddressError('postalCode is required');
}

function ensureNonNegativeTotal(total: number): void {
  if (!Number.isInteger(total) || total < 0)
    throw new InvalidShippingAddressError('cartTotalCents must be a non-negative integer');
}
