import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import {
  validateAddressWithShippo,
  PREFERRED_CARRIERS,
  computeParcel,
  getShippingRatesFromShippo,
  selectBestRate,
} from "./shippo";
import type { ValidatedCartItem } from "./checkout";
import type { ShippoRate } from "./checkout";
import type { Id } from "../_generated/dataModel";

const validInput = {
  addressLine1: "10 Downing Street",
  city: "London",
  postalCode: "SW1A 2AA",
  country: "GB",
};

const shippoValidResponse = {
  original_address: {
    address_line_1: "10 Downing Street",
    address_line_2: undefined,
    city_locality: "London",
    state_province: "Westminster",
    postal_code: "SW1A 2AA",
    country_code: "GB",
  },
  analysis: {
    validation_result: {
      value: "valid",
      reasons: [],
    },
    address_type: "unknown",
    changed_attributes: [],
  },
};

const shippoPartiallyValidResponse = {
  original_address: {
    address_line_1: "10 Downing St",
    city_locality: "London",
    state_province: "Westminster",
    postal_code: "SW1A 2AA",
    country_code: "GB",
  },
  recommended_address: {
    address_line_1: "10 Downing Street",
    address_line_2: "Flat 1",
    city_locality: "London",
    state_province: "Westminster",
    postal_code: "SW1A 2AA",
    country_code: "GB",
    complete_address: "10 Downing Street;Flat 1;LONDON;SW1A 2AA;UNITED KINGDOM",
    confidence_result: {
      score: "high",
      code: "postal_data_match",
      description: "Matched via postal data",
    },
  },
  analysis: {
    validation_result: {
      value: "partially_valid",
      reasons: [{ code: "street_suffix", description: "Street suffix corrected" }],
    },
    address_type: "unknown",
    changed_attributes: ["address_line_1", "address_line_2"],
  },
};

const shippoInvalidResponse = {
  original_address: {
    address_line_1: "999 Nowhere Lane",
    city_locality: "Faketown",
    state_province: "",
    postal_code: "ZZ99 9ZZ",
    country_code: "GB",
  },
  analysis: {
    validation_result: {
      value: "invalid",
      reasons: [
        { code: "address_not_found", description: "Address could not be found" },
        { code: "invalid_postal_code", description: "Postal code is not valid" },
      ],
    },
    address_type: "unknown",
  },
};

let fetchSpy: ReturnType<typeof vi.fn>;

beforeEach(() => {
  fetchSpy = vi.fn();
  vi.stubGlobal("fetch", fetchSpy);
  vi.stubEnv("SHIPPO_API_KEY", "shippo_test_key_123");
});

afterEach(() => {
  vi.unstubAllGlobals();
  vi.unstubAllEnvs();
});

function mockFetchOk(body: unknown) {
  fetchSpy.mockResolvedValue({
    ok: true,
    status: 200,
    json: () => Promise.resolve(body),
  });
}

describe("validateAddressWithShippo", () => {
  // ── Request construction ────────────────────────────────────────────

  it("builds correct URL with mapped query params (no state_province)", async () => {
    mockFetchOk(shippoValidResponse);

    await validateAddressWithShippo({
      addressLine1: "10 Downing Street",
      additionalInformation: "Flat 1",
      city: "London",
      postalCode: "SW1A 2AA",
      country: "GB",
      name: "Alice Smith",
    });

    expect(fetchSpy).toHaveBeenCalledTimes(1);
    const url = new URL(fetchSpy.mock.calls[0][0]);
    expect(url.origin + url.pathname).toBe(
      "https://api.goshippo.com/v2/addresses/validate",
    );
    expect(url.searchParams.get("address_line_1")).toBe("10 Downing Street");
    expect(url.searchParams.get("address_line_2")).toBe("Flat 1");
    expect(url.searchParams.get("city_locality")).toBe("London");
    expect(url.searchParams.get("postal_code")).toBe("SW1A 2AA");
    expect(url.searchParams.get("country_code")).toBe("GB");
    expect(url.searchParams.get("name")).toBe("Alice Smith");
    expect(url.searchParams.has("state_province")).toBe(false);
  });

  it("omits optional params when not provided", async () => {
    mockFetchOk(shippoValidResponse);
    await validateAddressWithShippo(validInput);

    const url = new URL(fetchSpy.mock.calls[0][0]);
    expect(url.searchParams.has("address_line_2")).toBe(false);
    expect(url.searchParams.has("name")).toBe(false);
    expect(url.searchParams.has("state_province")).toBe(false);
  });

  it("sends ShippoToken authorization header", async () => {
    mockFetchOk(shippoValidResponse);
    await validateAddressWithShippo(validInput);

    const opts = fetchSpy.mock.calls[0][1];
    expect(opts.headers.Authorization).toBe("ShippoToken shippo_test_key_123");
    expect(opts.method).toBe("GET");
  });

  // ── Valid address (no corrections) ──────────────────────────────────

  it("returns isValid: true for a fully valid address", async () => {
    mockFetchOk(shippoValidResponse);
    const result = await validateAddressWithShippo(validInput);

    expect(result.isValid).toBe(true);
    expect(result.validationValue).toBe("valid");
    expect(result.reasons).toEqual([]);
    expect(result.addressType).toBe("unknown");
    expect(result.changedAttributes).toEqual([]);
    expect(result.recommendedAddress).toBeUndefined();
  });

  it("maps originalAddress without state field", async () => {
    mockFetchOk(shippoValidResponse);
    const result = await validateAddressWithShippo(validInput);

    expect(result.originalAddress).toEqual({
      addressLine1: "10 Downing Street",
      additionalInformation: undefined,
      city: "London",
      postalCode: "SW1A 2AA",
      country: "GB",
    });
    expect(result.originalAddress).not.toHaveProperty("state");
  });

  // ── Partially valid address (with recommended) ─────────────────────

  it("returns partially_valid result with recommendedAddress mapped", async () => {
    mockFetchOk(shippoPartiallyValidResponse);
    const result = await validateAddressWithShippo(validInput);

    expect(result.isValid).toBe(false);
    expect(result.validationValue).toBe("partially_valid");
    expect(result.addressType).toBe("unknown");
    expect(result.changedAttributes).toEqual(["address_line_1", "address_line_2"]);
    expect(result.reasons).toEqual([
      { code: "street_suffix", description: "Street suffix corrected" },
    ]);
  });

  it("maps recommended address with additionalInformation (no state)", async () => {
    mockFetchOk(shippoPartiallyValidResponse);
    const result = await validateAddressWithShippo(validInput);
    const rec = result.recommendedAddress;

    expect(rec).toBeDefined();
    expect(rec!.addressLine1).toBe("10 Downing Street");
    expect(rec!.additionalInformation).toBe("Flat 1");
    expect(rec!.city).toBe("London");
    expect(rec!.postalCode).toBe("SW1A 2AA");
    expect(rec!.country).toBe("GB");
    expect(rec!.completeAddress).toBe(
      "10 Downing Street;Flat 1;LONDON;SW1A 2AA;UNITED KINGDOM",
    );
    expect(rec!.confidenceScore).toBe("high");
    expect(rec!.confidenceCode).toBe("postal_data_match");
    expect(rec!.confidenceDescription).toBe("Matched via postal data");
    expect(rec).not.toHaveProperty("state");
  });

  // ── Invalid address ────────────────────────────────────────────────

  it("returns isValid: false with reasons for an invalid address", async () => {
    mockFetchOk(shippoInvalidResponse);

    const result = await validateAddressWithShippo({
      addressLine1: "999 Nowhere Lane",
      city: "Faketown",
      postalCode: "ZZ99 9ZZ",
      country: "GB",
    });

    expect(result.isValid).toBe(false);
    expect(result.validationValue).toBe("invalid");
    expect(result.addressType).toBe("unknown");
    expect(result.recommendedAddress).toBeUndefined();
    expect(result.reasons).toHaveLength(2);
    expect(result.reasons[0].code).toBe("address_not_found");
    expect(result.reasons[1].code).toBe("invalid_postal_code");
  });

  it("defaults changedAttributes to [] when missing from response", async () => {
    mockFetchOk(shippoInvalidResponse);
    const result = await validateAddressWithShippo({
      addressLine1: "999 Nowhere Lane",
      city: "Faketown",
      postalCode: "ZZ99 9ZZ",
      country: "GB",
    });
    expect(result.changedAttributes).toEqual([]);
  });

  // ── Error handling ─────────────────────────────────────────────────

  it("throws when SHIPPO_API_KEY is missing", async () => {
    vi.stubEnv("SHIPPO_API_KEY", "");
    await expect(validateAddressWithShippo(validInput)).rejects.toThrow(
      /missing API key/i,
    );
  });

  it("throws when fetch rejects (network error)", async () => {
    fetchSpy.mockRejectedValue(new TypeError("Failed to fetch"));
    await expect(validateAddressWithShippo(validInput)).rejects.toThrow(
      /unreachable/i,
    );
  });

  it("throws when Shippo returns non-200 status", async () => {
    fetchSpy.mockResolvedValue({
      ok: false,
      status: 503,
      json: () => Promise.resolve({}),
    });
    await expect(validateAddressWithShippo(validInput)).rejects.toThrow(
      /unavailable.*503/i,
    );
  });

  it("throws when response body is not valid JSON", async () => {
    fetchSpy.mockResolvedValue({
      ok: true,
      status: 200,
      json: () => Promise.reject(new SyntaxError("Unexpected token")),
    });
    await expect(validateAddressWithShippo(validInput)).rejects.toThrow(
      /unexpected response/i,
    );
  });

  it("throws when response is missing analysis.validation_result", async () => {
    fetchSpy.mockResolvedValue({
      ok: true,
      status: 200,
      json: () => Promise.resolve({ original_address: {}, analysis: {} }),
    });
    await expect(validateAddressWithShippo(validInput)).rejects.toThrow(
      /malformed/i,
    );
  });
});

// ─── Test helpers ───────────────────────────────────────────────────────────

function makeCartItem(overrides: Partial<ValidatedCartItem> = {}): ValidatedCartItem {
  return {
    variantId: "variant1" as Id<"productVariants">,
    productId: "product1" as Id<"products">,
    quantity: 1,
    unitPrice: 1000,
    originalPrice: 1000,
    productName: "Test Product",
    variantName: "Default",
    sku: "TST-001",
    imageUrl: undefined,
    stockQuantity: 10,
    weight: 500,
    weightUnit: "g",
    length: undefined,
    width: undefined,
    height: undefined,
    dimensionUnit: undefined,
    productSlug: "test-product",
    parentCategorySlug: "parent",
    childCategorySlug: "child",
    ...overrides,
  };
}

function makeShippoRate(overrides: Partial<ShippoRate> = {}): ShippoRate {
  return {
    objectId: "rate_abc123",
    provider: "DPD UK",
    servicelevelName: "Next Day",
    servicelevelToken: "dpd_uk_next_day",
    amount: "5.50",
    currency: "GBP",
    estimatedDays: 1,
    durationTerms: "1-2 business days",
    arrivesBy: null,
    carrierAccount: "ca_abc123",
    ...overrides,
  };
}

// ─── PREFERRED_CARRIERS ─────────────────────────────────────────────────────

describe("PREFERRED_CARRIERS", () => {
  it("contains the four expected carriers", () => {
    expect(PREFERRED_CARRIERS).toEqual(["DPD UK", "Evri UK", "UPS", "UDS"]);
  });
});

// ─── computeParcel ──────────────────────────────────────────────────────────

describe("computeParcel", () => {
  // ── Weight normalization ────────────────────────────────────────────

  it("sums weights in grams for items with weightUnit 'g'", () => {
    const items = [
      makeCartItem({ weight: 200, weightUnit: "g", quantity: 2 }),
      makeCartItem({ weight: 300, weightUnit: "g", quantity: 1 }),
    ];
    const result = computeParcel(items);
    expect(result.weight).toBe("700");
    expect(result.mass_unit).toBe("g");
  });

  it("converts kg to grams", () => {
    const items = [makeCartItem({ weight: 1.5, weightUnit: "kg", quantity: 1 })];
    const result = computeParcel(items);
    expect(result.weight).toBe("1500");
  });

  it("converts lb to grams", () => {
    const items = [makeCartItem({ weight: 1, weightUnit: "lb", quantity: 1 })];
    const result = computeParcel(items);
    expect(result.weight).toBe("454");
  });

  it("converts oz to grams", () => {
    const items = [makeCartItem({ weight: 1, weightUnit: "oz", quantity: 1 })];
    const result = computeParcel(items);
    expect(result.weight).toBe("28");
  });

  it("multiplies weight by quantity", () => {
    const items = [makeCartItem({ weight: 100, weightUnit: "g", quantity: 5 })];
    const result = computeParcel(items);
    expect(result.weight).toBe("500");
  });

  it("handles mixed weight units across items", () => {
    const items = [
      makeCartItem({ weight: 500, weightUnit: "g", quantity: 1 }),
      makeCartItem({ weight: 1, weightUnit: "kg", quantity: 2 }),
    ];
    const result = computeParcel(items);
    // 500g + (1kg * 2) = 500 + 2000 = 2500g
    expect(result.weight).toBe("2500");
  });

  // ── No dimensions ──────────────────────────────────────────────────

  it("omits dimension fields when no items have dimensions", () => {
    const items = [makeCartItem({ length: undefined, width: undefined, height: undefined, dimensionUnit: undefined })];
    const result = computeParcel(items);
    expect(result).not.toHaveProperty("length");
    expect(result).not.toHaveProperty("width");
    expect(result).not.toHaveProperty("height");
    expect(result).not.toHaveProperty("distance_unit");
  });

  it("omits dimensions when only some dimension fields present", () => {
    const items = [makeCartItem({ length: 10, width: undefined, height: 5, dimensionUnit: "cm" })];
    const result = computeParcel(items);
    expect(result).not.toHaveProperty("length");
  });

  // ── With dimensions ────────────────────────────────────────────────

  it("computes dimensions in cm: max length, max width, sum height", () => {
    const items = [
      makeCartItem({ length: 30, width: 20, height: 5, dimensionUnit: "cm", quantity: 2 }),
      makeCartItem({ length: 25, width: 25, height: 3, dimensionUnit: "cm", quantity: 1 }),
    ];
    const result = computeParcel(items);
    expect(result.length).toBe("30");
    expect(result.width).toBe("25");
    // height: (5*2) + (3*1) = 13
    expect(result.height).toBe("13");
    expect(result.distance_unit).toBe("cm");
  });

  it("converts inches to cm for dimensions", () => {
    const items = [
      makeCartItem({ length: 10, width: 8, height: 4, dimensionUnit: "in", quantity: 1 }),
    ];
    const result = computeParcel(items);
    expect(result.length).toBe("25.4");
    expect(result.width).toBe("20.32");
    expect(result.height).toBe("10.16");
    expect(result.distance_unit).toBe("cm");
  });

  it("ignores items without full dimensions when computing parcel dimensions", () => {
    const withDims = makeCartItem({ length: 20, width: 15, height: 10, dimensionUnit: "cm", quantity: 1, weight: 200, weightUnit: "g" });
    const withoutDims = makeCartItem({ length: undefined, width: undefined, height: undefined, dimensionUnit: undefined, quantity: 1, weight: 300, weightUnit: "g" });
    const result = computeParcel([withDims, withoutDims]);
    expect(result.length).toBe("20");
    expect(result.width).toBe("15");
    expect(result.height).toBe("10");
    // weight still sums both items
    expect(result.weight).toBe("500");
  });

  it("handles mixed dimension units across items", () => {
    const items = [
      makeCartItem({ length: 10, width: 10, height: 5, dimensionUnit: "in", quantity: 1 }),
      makeCartItem({ length: 30, width: 20, height: 10, dimensionUnit: "cm", quantity: 1 }),
    ];
    const result = computeParcel(items);
    // max length: max(25.4, 30) = 30
    expect(result.length).toBe("30");
    // max width: max(25.4, 20) = 25.4
    expect(result.width).toBe("25.4");
    // total height: 12.7 + 10 = 22.7
    expect(result.height).toBe("22.7");
  });

  it("handles a single item with quantity > 1 stacking height", () => {
    const items = [makeCartItem({ length: 20, width: 15, height: 3, dimensionUnit: "cm", quantity: 4 })];
    const result = computeParcel(items);
    expect(result.height).toBe("12");
  });
});

// ─── getShippingRatesFromShippo ─────────────────────────────────────────────

describe("getShippingRatesFromShippo", () => {
  const validShipmentsInput = {
    sourceAddressId: "addr_source_123",
    destinationAddress: {
      name: "John Doe",
      street1: "10 Downing Street",
      city: "London",
      zip: "SW1A 2AA",
      country: "GB",
    },
    parcels: [{ weight: "500", mass_unit: "g" }],
  };

  const shippoShipmentsResponse = {
    object_id: "shp_abc123",
    rates: [
      {
        object_id: "rate_001",
        provider: "DPD UK",
        servicelevel: { name: "Next Day", token: "dpd_uk_next_day" },
        amount: "5.50",
        currency: "GBP",
        estimated_days: 1,
        duration_terms: "1-2 business days",
        arrives_by: null,
        carrier_account: "ca_dpd_001",
      },
      {
        object_id: "rate_002",
        provider: "UPS",
        servicelevel: { name: "Standard", token: "ups_standard" },
        amount: "7.99",
        currency: "GBP",
        estimated_days: 3,
        duration_terms: "3-5 business days",
        carrier_account: "ca_ups_001",
      },
    ],
  };

  it("sends correct POST request to Shippo /shipments/ endpoint", async () => {
    fetchSpy.mockResolvedValue({
      ok: true,
      status: 200,
      json: () => Promise.resolve(shippoShipmentsResponse),
    });

    await getShippingRatesFromShippo(validShipmentsInput);

    expect(fetchSpy).toHaveBeenCalledTimes(1);
    const [url, opts] = fetchSpy.mock.calls[0];
    expect(url).toBe("https://api.goshippo.com/shipments/");
    expect(opts.method).toBe("POST");
    expect(opts.headers.Authorization).toBe("ShippoToken shippo_test_key_123");
    expect(opts.headers["Content-Type"]).toBe("application/json");

    const body = JSON.parse(opts.body);
    expect(body.address_from).toBe("addr_source_123");
    expect(body.address_to.name).toBe("John Doe");
    expect(body.address_to.street1).toBe("10 Downing Street");
    expect(body.parcels).toHaveLength(1);
    expect(body.async).toBe(false);
  });

  it("returns shipmentObjectId and mapped rates", async () => {
    fetchSpy.mockResolvedValue({
      ok: true,
      status: 200,
      json: () => Promise.resolve(shippoShipmentsResponse),
    });

    const result = await getShippingRatesFromShippo(validShipmentsInput);

    expect(result.shipmentObjectId).toBe("shp_abc123");
    expect(result.rates).toHaveLength(2);

    const rate1 = result.rates[0];
    expect(rate1.objectId).toBe("rate_001");
    expect(rate1.provider).toBe("DPD UK");
    expect(rate1.servicelevelName).toBe("Next Day");
    expect(rate1.servicelevelToken).toBe("dpd_uk_next_day");
    expect(rate1.amount).toBe("5.50");
    expect(rate1.currency).toBe("GBP");
    expect(rate1.estimatedDays).toBe(1);
    expect(rate1.durationTerms).toBe("1-2 business days");
    expect(rate1.arrivesBy).toBeNull();
    expect(rate1.carrierAccount).toBe("ca_dpd_001");
  });

  it("maps arrives_by to null when absent from response", async () => {
    fetchSpy.mockResolvedValue({
      ok: true,
      status: 200,
      json: () => Promise.resolve(shippoShipmentsResponse),
    });

    const result = await getShippingRatesFromShippo(validShipmentsInput);
    expect(result.rates[1].arrivesBy).toBeNull();
  });

  it("maps arrives_by when present in response", async () => {
    const responseWithArrival = {
      object_id: "shp_abc123",
      rates: [
        {
          ...shippoShipmentsResponse.rates[0],
          arrives_by: "2025-03-05T18:00:00Z",
        },
      ],
    };
    fetchSpy.mockResolvedValue({
      ok: true,
      status: 200,
      json: () => Promise.resolve(responseWithArrival),
    });

    const result = await getShippingRatesFromShippo(validShipmentsInput);
    expect(result.rates[0].arrivesBy).toBe("2025-03-05T18:00:00Z");
  });

  it("throws when SHIPPO_API_KEY is missing", async () => {
    vi.stubEnv("SHIPPO_API_KEY", "");
    await expect(getShippingRatesFromShippo(validShipmentsInput)).rejects.toThrow(
      /missing API key/i,
    );
  });

  it("throws when fetch rejects (network error)", async () => {
    fetchSpy.mockRejectedValue(new TypeError("Failed to fetch"));
    await expect(getShippingRatesFromShippo(validShipmentsInput)).rejects.toThrow(
      /unreachable/i,
    );
  });

  it("throws when Shippo returns non-200 status", async () => {
    fetchSpy.mockResolvedValue({
      ok: false,
      status: 422,
      json: () => Promise.resolve({}),
    });
    await expect(getShippingRatesFromShippo(validShipmentsInput)).rejects.toThrow(
      /unavailable.*422/i,
    );
  });

  it("throws when response body is not valid JSON", async () => {
    fetchSpy.mockResolvedValue({
      ok: true,
      status: 200,
      json: () => Promise.reject(new SyntaxError("Unexpected token")),
    });
    await expect(getShippingRatesFromShippo(validShipmentsInput)).rejects.toThrow(
      /unexpected response/i,
    );
  });

  it("returns empty rates array when Shippo returns no rates", async () => {
    fetchSpy.mockResolvedValue({
      ok: true,
      status: 200,
      json: () => Promise.resolve({ object_id: "shp_empty", rates: [] }),
    });

    const result = await getShippingRatesFromShippo(validShipmentsInput);
    expect(result.shipmentObjectId).toBe("shp_empty");
    expect(result.rates).toEqual([]);
  });
});

// ─── selectBestRate ─────────────────────────────────────────────────────────

describe("selectBestRate", () => {
  it("throws when rates array is empty", () => {
    expect(() => selectBestRate([])).toThrow(
      /no shipping rates available/i,
    );
  });

  it("selects preferred carrier rate with fewest transit days", () => {
    const rates = [
      makeShippoRate({ provider: "DPD UK", estimatedDays: 2, amount: "4.00" }),
      makeShippoRate({ provider: "Evri UK", estimatedDays: 1, amount: "6.00" }),
      makeShippoRate({ provider: "Royal Mail", estimatedDays: 1, amount: "3.00" }),
    ];

    const { selected } = selectBestRate(rates);
    expect(selected.provider).toBe("Evri UK");
    expect(selected.estimatedDays).toBe(1);
  });

  it("breaks ties by cheapest amount among same transit days", () => {
    const rates = [
      makeShippoRate({ provider: "UPS", estimatedDays: 2, amount: "8.00" }),
      makeShippoRate({ provider: "DPD UK", estimatedDays: 2, amount: "5.50" }),
      makeShippoRate({ provider: "Evri UK", estimatedDays: 2, amount: "6.00" }),
    ];

    const { selected } = selectBestRate(rates);
    expect(selected.provider).toBe("DPD UK");
    expect(selected.amount).toBe("5.50");
  });

  it("returns up to 2 alternatives from preferred carriers", () => {
    const rates = [
      makeShippoRate({ provider: "DPD UK", estimatedDays: 1, amount: "5.50" }),
      makeShippoRate({ provider: "UPS", estimatedDays: 2, amount: "7.00" }),
      makeShippoRate({ provider: "Evri UK", estimatedDays: 3, amount: "4.00" }),
      makeShippoRate({ provider: "UDS", estimatedDays: 4, amount: "3.50" }),
    ];

    const { selected, alternatives } = selectBestRate(rates);
    expect(selected.provider).toBe("DPD UK");
    expect(alternatives).toHaveLength(2);
    expect(alternatives[0].provider).toBe("UPS");
    expect(alternatives[1].provider).toBe("Evri UK");
  });

  it("uses case-insensitive matching for carrier names", () => {
    const rates = [
      makeShippoRate({ provider: "dpd uk", estimatedDays: 1, amount: "5.50" }),
      makeShippoRate({ provider: "EVRI UK", estimatedDays: 2, amount: "4.00" }),
    ];

    const { selected } = selectBestRate(rates);
    expect(selected.provider).toBe("dpd uk");
  });

  it("falls back to all carriers when no preferred carriers present", () => {
    const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});

    const rates = [
      makeShippoRate({ provider: "Royal Mail", estimatedDays: 3, amount: "3.00" }),
      makeShippoRate({ provider: "Parcelforce", estimatedDays: 1, amount: "9.00" }),
    ];

    const { selected } = selectBestRate(rates);
    expect(selected.provider).toBe("Parcelforce");
    expect(selected.estimatedDays).toBe(1);
    expect(warnSpy).toHaveBeenCalledWith(
      "No preferred carriers returned rates. Falling back to all carriers.",
    );

    warnSpy.mockRestore();
  });

  it("fallback sorts all carriers by days then price", () => {
    const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});

    const rates = [
      makeShippoRate({ provider: "Royal Mail", estimatedDays: 2, amount: "7.00" }),
      makeShippoRate({ provider: "Parcelforce", estimatedDays: 2, amount: "5.00" }),
      makeShippoRate({ provider: "Hermes", estimatedDays: 3, amount: "3.00" }),
    ];

    const { selected, alternatives } = selectBestRate(rates);
    expect(selected.provider).toBe("Parcelforce");
    expect(selected.amount).toBe("5.00");
    expect(alternatives).toHaveLength(2);
    expect(alternatives[0].provider).toBe("Royal Mail");
    expect(alternatives[1].provider).toBe("Hermes");

    warnSpy.mockRestore();
  });

  it("returns single preferred rate with empty alternatives", () => {
    const rates = [
      makeShippoRate({ provider: "DPD UK", estimatedDays: 1, amount: "5.50" }),
      makeShippoRate({ provider: "Royal Mail", estimatedDays: 2, amount: "3.00" }),
    ];

    const { selected, alternatives } = selectBestRate(rates);
    expect(selected.provider).toBe("DPD UK");
    expect(alternatives).toHaveLength(0);
  });

  it("filters out non-preferred carriers from selection when preferred exist", () => {
    const rates = [
      makeShippoRate({ provider: "Royal Mail", estimatedDays: 1, amount: "2.00" }),
      makeShippoRate({ provider: "DPD UK", estimatedDays: 2, amount: "5.50" }),
    ];

    const { selected } = selectBestRate(rates);
    // DPD UK is preferred, even though Royal Mail is faster and cheaper
    expect(selected.provider).toBe("DPD UK");
  });

  it("handles single rate in the array", () => {
    const rates = [makeShippoRate({ provider: "UPS", estimatedDays: 3, amount: "10.00" })];

    const { selected, alternatives } = selectBestRate(rates);
    expect(selected.provider).toBe("UPS");
    expect(alternatives).toHaveLength(0);
  });
});
