import { describe, expect, it } from 'vitest';
import { ShippingService } from '../application/shipping-service.js';
import { InvalidShippingAddressError, ShippingZoneNotFoundError } from '../domain/errors.js';
import type { ShippingRepository } from '../domain/ports.js';

function repo(
  zoneId: string | undefined,
  methods: Array<{
    id: string;
    name: string;
    baseCostCents: number;
    freeShippingThresholdCents: number | null;
    active: boolean;
    maxWeightKg?: number | null;
    freeShippingMaxWeightKg?: number | null;
  }>,
): ShippingRepository {
  return {
    findMatchingZone: async () =>
      zoneId
        ? {
            zoneId,
            methods: methods.map((method) => ({
              maxWeightKg: null,
              freeShippingMaxWeightKg: null,
              ...method,
            })),
          }
        : undefined,
  };
}

describe('ShippingService', () => {
  it('returns the cheapest active method for a known zone (AC1)', async () => {
    const service = new ShippingService(
      repo('zone-1', [
        {
          id: 'm-1',
          name: 'Standard',
          baseCostCents: 500,
          freeShippingThresholdCents: null,
          active: true,
        },
        {
          id: 'm-2',
          name: 'Express',
          baseCostCents: 1200,
          freeShippingThresholdCents: null,
          active: true,
        },
      ]),
    );
    const quote = await service.calculate(1000, { country: 'ES', postalCode: '28001' });
    expect(quote).toMatchObject({
      zoneId: 'zone-1',
      methodId: 'm-1',
      costCents: 500,
      freeApplied: false,
    });
  });

  it('returns zero cost when cart is above free shipping threshold (AC3)', async () => {
    const service = new ShippingService(
      repo('zone-1', [
        {
          id: 'm-1',
          name: 'Standard',
          baseCostCents: 500,
          freeShippingThresholdCents: 3000,
          active: true,
        },
      ]),
    );
    const quote = await service.calculate(5000, { country: 'ES', postalCode: '28001' });
    expect(quote).toMatchObject({ costCents: 0, freeApplied: true });
  });

  it('returns SHIPPING_ZONE_NOT_FOUND when no zone matches (AC2)', async () => {
    const service = new ShippingService(repo(undefined, []));
    await expect(
      service.calculate(1000, { country: 'ES', postalCode: '99999' }),
    ).rejects.toBeInstanceOf(ShippingZoneNotFoundError);
  });

  it('validates address and total', async () => {
    const service = new ShippingService(repo('zone-1', []));
    await expect(service.calculate(0, { country: '', postalCode: '' })).rejects.toBeInstanceOf(
      InvalidShippingAddressError,
    );
    await expect(
      service.calculate(-1, { country: 'ES', postalCode: '28001' }),
    ).rejects.toBeInstanceOf(InvalidShippingAddressError);
  });

  it('excludes methods whose max weight is below the cart weight (F-102)', async () => {
    const service = new ShippingService(
      repo('zone-1', [
        {
          id: 'm-light',
          name: 'Ligero',
          baseCostCents: 300,
          freeShippingThresholdCents: null,
          active: true,
          maxWeightKg: 2,
        },
        {
          id: 'm-heavy',
          name: 'Pesado',
          baseCostCents: 900,
          freeShippingThresholdCents: null,
          active: true,
          maxWeightKg: 30,
        },
      ]),
    );
    const quote = await service.calculate(1000, { country: 'ES', postalCode: '28001' }, 5);
    expect(quote).toMatchObject({ methodId: 'm-heavy', costCents: 900 });
  });

  it('denies free shipping when cart weight exceeds the free-shipping range (F-102)', async () => {
    const service = new ShippingService(
      repo('zone-1', [
        {
          id: 'm-1',
          name: 'Standard',
          baseCostCents: 500,
          freeShippingThresholdCents: 3000,
          active: true,
          freeShippingMaxWeightKg: 4,
        },
      ]),
    );
    const free = await service.calculate(5000, { country: 'ES', postalCode: '28001' }, 3);
    expect(free).toMatchObject({ costCents: 0, freeApplied: true });
    const paid = await service.calculate(5000, { country: 'ES', postalCode: '28001' }, 6);
    expect(paid).toMatchObject({ costCents: 500, freeApplied: false });
  });
});
