import { Injectable } from '@nestjs/common';

import { RatesService } from '../modules/rule-engine/services/rates.service';
import { Rate } from '../modules/rule-engine/entities/rate.entity';
import {
  ContractDirection,
  ContractFreight,
} from './contract-template.types';

/** One priced line in the contract's rate schedule. */
export interface RateScheduleRow {
  /** "Negad → Mojo Dry Port" for base freight, service name otherwise. */
  route: string;
  /** "40ft GP", "Wheat", or "—" when the rate is not scoped to a type. */
  cargo: string;
  currency: string;
  /** Pre-formatted amount, e.g. "200" (grouped, no trailing zeros). */
  amount: string;
  /** Human unit, e.g. "per container", "per wagon", "per ton". */
  unit: string;
}

/**
 * The origin → destination rate schedule shown in a generated contract's
 * pricing article. Grouped so the reader sees rail freight lanes first, then
 * pickup/delivery legs, then trigger-based surcharges and demurrage.
 */
export interface RateSchedule {
  /** Base rail freight lanes matching this contract's direction + freight. */
  freightLanes: RateScheduleRow[];
  /** First-mile / last-mile truck legs (route-agnostic). */
  additionalServices: RateScheduleRow[];
  /** Hazard, reefer, overweight, demurrage, customs, etc. */
  surcharges: RateScheduleRow[];
  /** True when every group is empty — the template falls back to prose. */
  isEmpty: boolean;
  /** Currencies present across the schedule, e.g. "USD" or "USD, ETB". */
  currencyLabel: string;
}

const UNIT_LABELS: Record<string, string> = {
  PER_WAGON: 'per wagon',
  PER_TON: 'per ton',
  PER_CONTAINER: 'per container',
  PER_KM: 'per km',
  PER_INVOICE: 'per invoice',
  FLAT: 'flat',
};

const SERVICE_ROUTE_LABELS: Partial<Record<Rate['appliesTo'], string>> = {
  FIRST_MILE: 'First-mile pickup by truck',
  LAST_MILE: 'Last-mile delivery by truck',
};

/** Friendly wording for the trigger-based charges shown in the surcharge group. */
const TRIGGER_ROUTE_LABELS: Partial<Record<Rate['trigger'], string>> = {
  HAZARDOUS: 'Hazardous cargo surcharge',
  OVERWEIGHT: 'Overweight surcharge',
  REEFER: 'Reefer (refrigerated) surcharge',
  WITH_RETURN: 'Empty-container return service',
  SHIPPING_LINE: 'Shipping line handling',
  CONSOLIDATION: 'Penalty (container consolidation)',
  LASHING: 'Cargo lashing and securing',
  CANCELLATION: 'Booking cancellation fee',
  DEMURRAGE: 'Demurrage / wagon detention',
  PIL_EXTRA_FEE: 'PIL shipping line extra fee',
  CUSTOMS_CLEARANCE: 'Customs clearance service',
};

@Injectable()
export class ContractRateScheduleBuilder {
  constructor(private readonly ratesService: RatesService) {}

  /**
   * Build the rate schedule for a contract of the given direction + freight.
   * Base-freight lanes are filtered to the matching trade direction / freight
   * kind so an import container contract shows import container lanes only;
   * additional services and surcharges are route-agnostic and always shown.
   */
  async build(
    direction: ContractDirection,
    freight: ContractFreight,
  ): Promise<RateSchedule> {
    const rates = await this.ratesService.findLiveRatesDetailed();

    const freightLanes: RateScheduleRow[] = [];
    const additionalServices: RateScheduleRow[] = [];
    const surcharges: RateScheduleRow[] = [];

    for (const rate of rates) {
      if (this.isBaseFreight(rate)) {
        if (this.baseFreightMatches(rate, direction, freight)) {
          freightLanes.push(this.laneRow(rate));
        }
        continue;
      }

      if (rate.appliesTo === 'FIRST_MILE' || rate.appliesTo === 'LAST_MILE') {
        additionalServices.push(this.serviceRow(rate));
        continue;
      }

      // Everything left is a trigger-based charge (surcharge / demurrage / customs).
      surcharges.push(this.surchargeRow(rate));
    }

    const currencyLabel = this.currencyLabel([
      ...freightLanes,
      ...additionalServices,
      ...surcharges,
    ]);

    return {
      freightLanes,
      additionalServices,
      surcharges,
      isEmpty:
        freightLanes.length === 0 &&
        additionalServices.length === 0 &&
        surcharges.length === 0,
      currencyLabel,
    };
  }

  private isBaseFreight(rate: Rate): boolean {
    return (
      rate.trigger === 'ALWAYS' &&
      (rate.appliesTo === 'BULK' ||
        rate.appliesTo === 'CONTAINER' ||
        rate.appliesTo === 'INTERCITY')
    );
  }

  private baseFreightMatches(
    rate: Rate,
    direction: ContractDirection,
    freight: ContractFreight,
  ): boolean {
    // Domestic contracts price off intercity rates; the freight kind is carried
    // in the derived rateType (INTERCITY_BULK vs INTERCITY_CONTAINER).
    if (direction === 'DOM') {
      if (rate.appliesTo !== 'INTERCITY') return false;
      return freight === 'BULK'
        ? rate.rateType === 'INTERCITY_BULK'
        : rate.rateType === 'INTERCITY_CONTAINER';
    }

    // Import / export price off BULK or CONTAINER rates matching the direction.
    const wantAppliesTo = freight === 'BULK' ? 'BULK' : 'CONTAINER';
    if (rate.appliesTo !== wantAppliesTo) return false;
    const wantDirection = direction === 'IMP' ? 'IMPORT' : 'EXPORT';
    return rate.tradeDirection === wantDirection;
  }

  private laneRow(rate: Rate): RateScheduleRow {
    const origin = rate.originYard?.label ?? rate.originYard?.code ?? '—';
    const destination =
      rate.destinationYard?.label ?? rate.destinationYard?.code ?? '—';
    return {
      route: `${origin} → ${destination}`,
      cargo: this.cargoLabel(rate),
      currency: rate.currency,
      amount: this.formatAmount(rate.rateValue),
      unit: this.unitLabel(rate.rateUnit),
    };
  }

  private serviceRow(rate: Rate): RateScheduleRow {
    return {
      route: SERVICE_ROUTE_LABELS[rate.appliesTo] ?? rate.appliesTo,
      cargo: this.cargoLabel(rate),
      currency: rate.currency,
      amount: this.formatAmount(rate.rateValue),
      unit: this.unitLabel(rate.rateUnit),
    };
  }

  private surchargeRow(rate: Rate): RateScheduleRow {
    return {
      route: TRIGGER_ROUTE_LABELS[rate.trigger] ?? this.titleCase(rate.trigger),
      cargo: this.cargoLabel(rate),
      currency: rate.currency,
      amount: this.formatAmount(rate.rateValue),
      unit: this.unitLabel(rate.rateUnit),
    };
  }

  /** The type a rate is scoped to (container/cargo), or a dash when unscoped. */
  private cargoLabel(rate: Rate): string {
    return (
      rate.containerType?.label ??
      rate.containerType?.code ??
      rate.cargoType?.cargoTypeName ??
      '—'
    );
  }

  private unitLabel(unit: Rate['rateUnit']): string {
    return UNIT_LABELS[unit] ?? unit.toLowerCase().replace(/_/g, ' ');
  }

  /** Group thousands and drop the DB's trailing zeros: "200.0000" → "200". */
  private formatAmount(value: number | string): string {
    const num = Number(value);
    if (!Number.isFinite(num)) return String(value);
    return num.toLocaleString('en-US', {
      minimumFractionDigits: 0,
      maximumFractionDigits: 2,
    });
  }

  private currencyLabel(rows: RateScheduleRow[]): string {
    const seen: string[] = [];
    for (const row of rows) {
      if (!seen.includes(row.currency)) seen.push(row.currency);
    }
    return seen.join(', ') || 'USD';
  }

  private titleCase(value: string): string {
    return value
      .toLowerCase()
      .replace(/_/g, ' ')
      .replace(/\b\w/g, (c) => c.toUpperCase());
  }
}
