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

import { ContainerTypesService } from '../rule-engine/services/container-types.service';
import { RatesService } from '../rule-engine/services/rates.service';
import { ServiceTypesService } from '../rule-engine/services/service-types.service';
import { Rate } from '../rule-engine/entities/rate.entity';
import {
  AppliedCargoModifier,
  BookingEvaluationInput,
  RuleEngineService,
} from '../rule-engine/rule-engine.service';
import { BookingsRepository } from './bookings.repository';
import { GeneratePriceResponseDto, PriceLineItemDto } from './dto/generate-price-response.dto';
import { Booking } from './entities/booking.entity';
import { assertBookingStatus } from './booking-status.util';

@Injectable()
export class BookingPricingService {
  constructor(
    private readonly bookingsRepository: BookingsRepository,
    private readonly ruleEngineService: RuleEngineService,
    private readonly containerTypesService: ContainerTypesService,
    private readonly ratesService: RatesService,
    private readonly serviceTypesService: ServiceTypesService,
  ) {}

  async generatePrice(bookingId: string): Promise<GeneratePriceResponseDto> {
    const booking = await this.requireBooking(bookingId);
    assertBookingStatus(booking, ['DRAFT']);

    const evalInput = await this.buildEvalInputForBooking(booking);
    console.log('evalInput----', evalInput);
    const ruleResult = await this.ruleEngineService.evaluate(evalInput);
    this.ruleEngineService.assertNoHardBlocks(ruleResult);

    const lineItems: PriceLineItemDto[] = [];
    let total = 0;

    const baseLines = await this.computeBaseRailLines(booking, evalInput);
    for (const line of baseLines) {
      lineItems.push(line);
      total += line.amount;
    }

    for (const mod of ruleResult.appliedModifiers) {
      const item: PriceLineItemDto = {
        code: mod.surchargeTypeCode,
        description: `Surcharge: ${mod.surchargeTypeCode}`,
        amount: mod.calculatedAmount,
        currency: mod.currency,
      };
      lineItems.push(item);
      total += mod.calculatedAmount;
    }

    await this.persistPriceRun(bookingId, ruleResult.appliedModifiers, total);

    await this.bookingsRepository.update(bookingId, {
      totalAmount: total,
      priorityScore: ruleResult.priorityScore,
      pricingBreakdown: {
        lineItems,
        totalAmount: total,
        currency: booking.paymentCurrency,
        generatedAt: new Date().toISOString(),
      },
    } as never);

    return {
      bookingId,
      totalAmount: total,
      currency: booking.paymentCurrency,
      lineItems,
      warnings: ruleResult.warnings,
    };
  }

  async buildEvalInputForBooking(booking: Booking): Promise<BookingEvaluationInput> {
    const containers = await Promise.all(
      (booking.bookingContainers ?? []).map(async (bc) => {
        const ct = await this.containerTypesService.findById(bc.containerTypeId);
        const vgm = Number(bc.vgmPerUnitTons);
        const qty = bc.quantity;
        return {
          containerTypeId: bc.containerTypeId,
          quantity: qty,
          vgmPerUnitTons: vgm,
          totalVgmTons: qty * vgm,
          isReefer: ct.isReefer,
        };
      }),
    );
    return {
      freightType: booking.freightType as 'CONTAINER' | 'BULK',
      cargoTypeId: booking.cargoTypeId ?? null,
      serviceTypeId: booking.serviceTypeId,
      paymentCurrency: booking.paymentCurrency,
      tradeDirection: booking.tradeDirection,
      isHazardous: booking.isHazardous,
      allowConsolidation: booking.allowConsolidation,
      shippingLineId: booking.shippingLineId,
      containers,
    };
  }

  private async requireBooking(id: string): Promise<Booking> {
    const booking = await this.bookingsRepository.findByIdWithFiles(id);
    if (!booking) throw new NotFoundException(`Booking ${id} not found`);
    return booking;
  }

  /** Line items for contract schedule (uses stored breakdown or recomputes). */
  async computeContractLineItems(booking: Booking): Promise<{
    lineItems: PriceLineItemDto[];
    totalAmount: number;
    currency: string;
  }> {
    const stored = booking.pricingBreakdown as {
      lineItems?: PriceLineItemDto[];
      totalAmount?: number;
      currency?: string;
    } | null;

    if (stored?.lineItems?.length) {
      return {
        lineItems: stored.lineItems,
        totalAmount: Number(stored.totalAmount ?? booking.totalAmount),
        currency: stored.currency ?? booking.paymentCurrency,
      };
    }

    const evalInput = await this.buildEvalInputForBooking(booking);
    const ruleResult = await this.ruleEngineService.evaluate(evalInput);
    const lineItems: PriceLineItemDto[] = [];
    let total = 0;

    const baseLines = await this.computeBaseRailLines(booking, evalInput);
    for (const line of baseLines) {
      lineItems.push(line);
      total += line.amount;
    }

    for (const mod of ruleResult.appliedModifiers) {
      lineItems.push({
        code: mod.surchargeTypeCode,
        description: `Surcharge: ${mod.surchargeTypeCode}`,
        amount: mod.calculatedAmount,
        currency: mod.currency,
      });
      total += mod.calculatedAmount;
    }

    if (lineItems.length === 0) {
      total = Number(booking.totalAmount);
      lineItems.push({
        code: 'TOTAL',
        description: 'Contract total',
        amount: total,
        currency: booking.paymentCurrency,
      });
    }

    return {
      lineItems,
      totalAmount: total || Number(booking.totalAmount),
      currency: booking.paymentCurrency,
    };
  }

  /** Recompute priority on submit (USD + service tier). */
  async computeSubmitPriorityScore(booking: Booking): Promise<number> {
    const evalInput = await this.buildEvalInputForBooking(booking);
    const ruleResult = await this.ruleEngineService.evaluate(evalInput);
    let score = ruleResult.priorityScore;

    const serviceType = await this.serviceTypesService.findById(booking.serviceTypeId);
    if (booking.paymentCurrency === 'USD' && serviceType) {
      const code = (serviceType.code ?? '').toUpperCase();
      const hasForwarding =
        serviceType.includesFirstMile ||
        serviceType.includesLastMile ||
        code.includes('FORWARD') ||
        code.includes('Y');
      const railOnly = code.includes('RAIL') && !hasForwarding;

      if (hasForwarding) score += 1000;
      else if (railOnly || code.includes('X')) score += 500;
    }

    return score;
  }

  private async computeBaseRailLines(
    booking: Booking,
    evalInput: BookingEvaluationInput,
  ): Promise<PriceLineItemDto[]> {
    const liveRates = await this.ratesService.findLiveRates();
    const currency = booking.paymentCurrency;
    const isBulk = booking.freightType === 'BULK';
console.log('liveRates----', liveRates);
    const rateType =
      booking.tradeDirection === 'IMPORT'
        ? isBulk
          ? 'BULK_IMPORT'
          : 'CONTAINER_IMPORT'
        : booking.tradeDirection === 'EXPORT'
          ? isBulk
            ? 'BULK_EXPORT'
            : 'CONTAINER_EXPORT'
          : 'INTERCITY_CONTAINER';


          console.log('rateType----', rateType);

    const lines: PriceLineItemDto[] = [];
    const wagonCount = await this.bookingsRepository.calculateWagonCount(booking.id);

    for (const container of evalInput.containers) {
      console.log('container----', container);
      const rate = this.pickRate(liveRates, rateType, container.containerTypeId, currency);
      console.log('rate----', rate);
      if (!rate) continue;

      const amount = this.amountForRate(rate, container.quantity, wagonCount);
      lines.push({
        code: rateType,
        description: `Base rail (${rateType})`,
        amount,
        currency: rate.currency,
      });
    }

    if (lines.length === 0) {
      const fallback = liveRates.find(
        (r) => r.rateType === rateType && r.currency === currency && r.status === 'LIVE',
      );
      if (fallback) {
        const amount = this.amountForRate(fallback, 1, wagonCount);
        lines.push({
          code: rateType,
          description: `Base rail (${rateType})`,
          amount,
          currency: fallback.currency,
        });
      }
    }

    return lines;
  }

  private pickRate(
    rates: Rate[],
    rateType: string,
    containerTypeId: string,
    currency: string,
  ): Rate | undefined {
    return (
      rates.find(
        (r) =>
          r.rateType === rateType &&
          r.currency === currency &&
          r.containerTypeId === containerTypeId,
      ) ??
      rates.find((r) => r.rateType === rateType && r.currency === currency && !r.containerTypeId)
    );
  }

  private amountForRate(rate: Rate, quantity: number, wagonCount: number): number {
    const value = Number(rate.rateValue);
    switch (rate.rateUnit) {
      case 'PER_CONTAINER':
        return value * quantity;
      case 'PER_WAGON':
        return value * wagonCount;
      case 'PER_TON':
        return value * quantity;
      case 'FLAT':
        return value;
      default:
        return value * quantity;
    }
  }

  private async persistPriceRun(
    bookingId: string,
    modifiers: AppliedCargoModifier[],
    _total: number,
  ): Promise<void> {
    await this.bookingsRepository.clearPricingArtifacts(bookingId);
    const snapshots = await this.ruleEngineService.snapshotLiveRates(bookingId);

    const snapshotByRateId = new Map(snapshots.map((s) => [s.rateId, s.id]));
    const rows = modifiers
      .map((m) => {
        const snapshotId = snapshotByRateId.get(m.rateId);
        if (!snapshotId) return null;
        return {
          bookingId,
          surchargeTypeId: m.surchargeTypeId,
          triggerValue: m.triggerValue,
          calculatedAmount: m.calculatedAmount,
          rateSnapshotId: snapshotId,
        };
      })
      .filter((r): r is NonNullable<typeof r> => r !== null);

    if (rows.length > 0) {
      await this.bookingsRepository.createCargoModifiers(rows);
    }
  }
}
