import { RateOfferGeneratorService, ServiceLevel } from './rate-offer-generator.service';
import { CsvRate } from '../entities/csv-rate.entity';
import { PortCode } from '../value-objects/port-code.vo';
import { ContainerType } from '../value-objects/container-type.vo';
import { DateRange } from '../value-objects/date-range.vo';

describe('RateOfferGeneratorService', () => {
  let service: RateOfferGeneratorService;
  let mockRate: CsvRate;

  beforeEach(() => {
    service = new RateOfferGeneratorService();

    // Mock minimal CsvRate compatible with new schema
    mockRate = {
      companyName: 'Test Carrier',
      companyEmail: 'test@carrier.com',
      originCFS: 'Fos Sur Mer',
      originCode: PortCode.create('FRFOS'),
      portOfLoading: 'FOS SUR MER',
      routing: 'Direct',
      destinationCFS: 'New York',
      destinationCode: PortCode.create('USNYC'),
      destinationCountry: 'USA',
      containerType: ContainerType.create('LCL'),
      freight: {
        freightCurrency: 'USD',
        freightRatePerCBM: 50,
        freightMinimum: 500,
      },
      fob: {
        fobCurrency: 'EUR',
        fobDocumentation: 55,
        fobISPS: 18,
        fobHandling: 22,
        fobHandlingUnit: 'W',
        fobHandlingMinimum: 110,
        fobSolas: 15,
        fobCustoms: 85,
        fobAMS_ACI: 35,
        fobISF5: 0,
        fobDGAdmin: 50,
      },
      dgSurcharge: {
        dgSurchargeCurrency: 'EUR',
        dgSurchargeRate: 20,
        dgSurchargeUnit: 'UP',
        dgSurchargeMin: 50,
      },
      remarks: '',
      frequency: 'Weekly',
      transitDays: 20,
      validity: DateRange.create(new Date('2026-01-01'), new Date('2026-12-31'), true),
      isValidForDate: () => true,
      isCurrentlyValid: () => true,
      matchesRoute: () => true,
      isDgAccepted: () => true,
      isDgOnRequest: () => false,
      isDirectRoute: () => true,
      getFrequencyScore: () => 4,
      getRouteDescription: () => 'FRFOS → USNYC',
      getSummary: () => 'Test Carrier: FRFOS → USNYC',
      toString: () => 'Test Carrier: FRFOS → USNYC',
    } as any;
  });

  describe('generateOffers', () => {
    it('generates exactly 3 offers (RAPID, STANDARD, ECONOMIC)', () => {
      const offers = service.generateOffers(mockRate);
      expect(offers).toHaveLength(3);
      expect(offers.map(o => o.serviceLevel)).toEqual(
        expect.arrayContaining([ServiceLevel.RAPID, ServiceLevel.STANDARD, ServiceLevel.ECONOMIC])
      );
    });

    it('ECONOMIC has the lowest price multiplier (0.85)', () => {
      const offers = service.generateOffers(mockRate);
      const economic = offers.find(o => o.serviceLevel === ServiceLevel.ECONOMIC)!;
      expect(economic.priceMultiplier).toBe(0.85);
      expect(economic.priceAdjustmentPercent).toBe(-15);
    });

    it('RAPID has the highest price multiplier (1.2)', () => {
      const offers = service.generateOffers(mockRate);
      const rapid = offers.find(o => o.serviceLevel === ServiceLevel.RAPID)!;
      expect(rapid.priceMultiplier).toBe(1.2);
      expect(rapid.priceAdjustmentPercent).toBe(20);
    });

    it('STANDARD has no price adjustment (multiplier = 1.0)', () => {
      const offers = service.generateOffers(mockRate);
      const standard = offers.find(o => o.serviceLevel === ServiceLevel.STANDARD)!;
      expect(standard.priceMultiplier).toBe(1.0);
      expect(standard.priceAdjustmentPercent).toBe(0);
    });

    it('RAPID has the shortest transit time', () => {
      const offers = service.generateOffers(mockRate);
      const economic = offers.find(o => o.serviceLevel === ServiceLevel.ECONOMIC)!;
      const standard = offers.find(o => o.serviceLevel === ServiceLevel.STANDARD)!;
      const rapid = offers.find(o => o.serviceLevel === ServiceLevel.RAPID)!;

      expect(rapid.adjustedTransitDays).toBeLessThan(standard.adjustedTransitDays);
      expect(rapid.adjustedTransitDays).toBeLessThan(economic.adjustedTransitDays);
      // 20 * 0.70 = 14
      expect(rapid.adjustedTransitDays).toBe(14);
    });

    it('ECONOMIC has the longest transit time', () => {
      const offers = service.generateOffers(mockRate);
      const economic = offers.find(o => o.serviceLevel === ServiceLevel.ECONOMIC)!;
      const standard = offers.find(o => o.serviceLevel === ServiceLevel.STANDARD)!;

      expect(economic.adjustedTransitDays).toBeGreaterThan(standard.adjustedTransitDays);
      // 20 * 1.50 = 30
      expect(economic.adjustedTransitDays).toBe(30);
    });

    it('STANDARD has no transit adjustment', () => {
      const offers = service.generateOffers(mockRate);
      const standard = offers.find(o => o.serviceLevel === ServiceLevel.STANDARD)!;
      expect(standard.adjustedTransitDays).toBe(20);
      expect(standard.transitAdjustmentPercent).toBe(0);
    });

    it('offers are sorted by priceMultiplier (ECONOMIC → STANDARD → RAPID)', () => {
      const offers = service.generateOffers(mockRate);
      expect(offers[0].serviceLevel).toBe(ServiceLevel.ECONOMIC);
      expect(offers[1].serviceLevel).toBe(ServiceLevel.STANDARD);
      expect(offers[2].serviceLevel).toBe(ServiceLevel.RAPID);
    });

    it('clamps transit time to minimum (5 days)', () => {
      const shortTransitRate = { ...mockRate, transitDays: 3 } as any;
      const offers = service.generateOffers(shortTransitRate);
      const rapid = offers.find(o => o.serviceLevel === ServiceLevel.RAPID)!;
      expect(rapid.adjustedTransitDays).toBe(5);
    });

    it('clamps transit time to maximum (90 days)', () => {
      const longTransitRate = { ...mockRate, transitDays: 80 } as any;
      const offers = service.generateOffers(longTransitRate);
      const economic = offers.find(o => o.serviceLevel === ServiceLevel.ECONOMIC)!;
      expect(economic.adjustedTransitDays).toBe(90);
    });

    it('preserves the original rate reference', () => {
      const offers = service.generateOffers(mockRate);
      for (const offer of offers) {
        expect(offer.rate).toBe(mockRate);
        expect(offer.originalTransitDays).toBe(20);
      }
    });
  });

  describe('generateOffersForRates', () => {
    it('generates 3 offers per rate', () => {
      const rate2 = { ...mockRate, companyName: 'Another Carrier' } as any;
      const offers = service.generateOffersForRates([mockRate, rate2]);
      expect(offers).toHaveLength(6);
    });
  });

  describe('generateOffersForServiceLevel', () => {
    it('generates only RAPID offers', () => {
      const offers = service.generateOffersForServiceLevel([mockRate], ServiceLevel.RAPID);
      expect(offers).toHaveLength(1);
      expect(offers[0].serviceLevel).toBe(ServiceLevel.RAPID);
    });

    it('generates only ECONOMIC offers', () => {
      const offers = service.generateOffersForServiceLevel([mockRate], ServiceLevel.ECONOMIC);
      expect(offers).toHaveLength(1);
      expect(offers[0].serviceLevel).toBe(ServiceLevel.ECONOMIC);
    });
  });

  describe('getBestOffersPerServiceLevel', () => {
    it('returns one offer per service level', () => {
      const best = service.getBestOffersPerServiceLevel([mockRate]);
      expect(best.rapid).not.toBeNull();
      expect(best.standard).not.toBeNull();
      expect(best.economic).not.toBeNull();
    });

    it('returns null for all levels when no rates', () => {
      const best = service.getBestOffersPerServiceLevel([]);
      expect(best.rapid).toBeNull();
      expect(best.standard).toBeNull();
      expect(best.economic).toBeNull();
    });
  });

  describe('isRateEligible', () => {
    it('accepts a valid rate', () => {
      expect(service.isRateEligible(mockRate)).toBe(true);
    });

    it('rejects a rate with transitDays = 0', () => {
      const invalid = { ...mockRate, transitDays: 0 } as any;
      expect(service.isRateEligible(invalid)).toBe(false);
    });

    it('rejects a rate with freightRatePerCBM = 0 and freightMinimum = 0', () => {
      const invalid = {
        ...mockRate,
        freight: { ...mockRate.freight, freightRatePerCBM: 0, freightMinimum: 0 },
      } as any;
      expect(service.isRateEligible(invalid)).toBe(false);
    });

    it('rejects an expired rate', () => {
      const expired = { ...mockRate, isValidForDate: () => false } as any;
      expect(service.isRateEligible(expired)).toBe(false);
    });
  });

  describe('Business logic invariants', () => {
    it('RAPID priceMultiplier always > ECONOMIC priceMultiplier', () => {
      const offers = service.generateOffers(mockRate);
      const economic = offers.find(o => o.serviceLevel === ServiceLevel.ECONOMIC)!;
      const rapid = offers.find(o => o.serviceLevel === ServiceLevel.RAPID)!;
      expect(rapid.priceMultiplier).toBeGreaterThan(economic.priceMultiplier);
    });

    it('RAPID transit always < ECONOMIC transit for different base days', () => {
      for (const days of [5, 10, 20, 30, 60]) {
        const rate = { ...mockRate, transitDays: days } as any;
        const offers = service.generateOffers(rate);
        const economic = offers.find(o => o.serviceLevel === ServiceLevel.ECONOMIC)!;
        const rapid = offers.find(o => o.serviceLevel === ServiceLevel.RAPID)!;
        expect(rapid.adjustedTransitDays).toBeLessThan(economic.adjustedTransitDays);
      }
    });
  });
});
