import { afterEach, describe, expect, it, vi } from "vitest";

import { fetchGatewayRoute } from "./gatewayRoute";
import type { RouteRequest } from "../../modules/mapmap-react-native/src/MapmapNav.types";

/** Minimal Google/Valhalla polyline6 encoder — inverse of decodePolyline6, so
 * we can build fixture geometry the parser will actually decode. */
function encodePolyline6(coords: Array<[number, number]>): string {
  const enc = (value: number): string => {
    let v = value < 0 ? ~(value << 1) : value << 1;
    let out = "";
    while (v >= 0x20) {
      out += String.fromCharCode((0x20 | (v & 0x1f)) + 63);
      v >>= 5;
    }
    return out + String.fromCharCode(v + 63);
  };
  let lastLat = 0;
  let lastLng = 0;
  let out = "";
  for (const [lat, lng] of coords) {
    const la = Math.round(lat * 1e6);
    const lo = Math.round(lng * 1e6);
    out += enc(la - lastLat) + enc(lo - lastLng);
    lastLat = la;
    lastLng = lo;
  }
  return out;
}

function trip(shape: Array<[number, number]>, lengthKm: number, timeS: number) {
  return {
    legs: [
      {
        shape: encodePolyline6(shape),
        maneuvers: [
          { type: 1, instruction: "Head north", length: lengthKm, begin_shape_index: 0, street_names: ["A24"] },
          { type: 4, instruction: "Arrive", length: 0, begin_shape_index: shape.length - 1 },
        ],
      },
    ],
    summary: { length: lengthKm, time: timeS },
  };
}

const REQUEST: RouteRequest = {
  profile: "car",
  origin: { latitude: 51, longitude: -0.1 },
  destination: { latitude: 51.1, longitude: -0.2 },
};

function mockFetch(body: unknown, ok = true) {
  const fn = vi.fn(async () => ({ ok, json: async () => body }) as unknown as Response);
  vi.stubGlobal("fetch", fn);
  return fn;
}

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

describe("fetchGatewayRoute — alternatives", () => {
  it("requests alternates and returns the primary plus each alternate, densely indexed", async () => {
    const fetchFn = mockFetch({
      trip: trip([[51, -0.1], [51.1, -0.2]], 12, 900),
      alternates: [
        { trip: trip([[51, -0.1], [51.05, -0.15], [51.1, -0.2]], 13, 960) },
        { trip: trip([[51, -0.1], [51.06, -0.12], [51.1, -0.2]], 14, 1010) },
      ],
    });

    const result = await fetchGatewayRoute("key", REQUEST);
    expect(result).not.toBeNull();
    expect(result!.routes).toHaveLength(3);
    expect(result!.routes.map((r) => r.index)).toEqual([0, 1, 2]);
    expect(result!.routes[0].distanceMeters).toBe(12_000);
    expect(result!.routes[1].durationSeconds).toBe(960);

    // The request body asked the engine for alternates.
    const [, init] = fetchFn.mock.calls[0] as unknown as [string, RequestInit];
    const body = JSON.parse(init.body as string);
    expect(body.alternates).toBe(2);
  });

  it("still works when the engine returns no alternates (single route)", async () => {
    mockFetch({ trip: trip([[51, -0.1], [51.1, -0.2]], 10, 800) });
    const result = await fetchGatewayRoute("key", REQUEST);
    expect(result!.routes).toHaveLength(1);
    expect(result!.routes[0].index).toBe(0);
  });

  it("drops an unusable alternate but keeps the valid ones re-indexed", async () => {
    mockFetch({
      trip: trip([[51, -0.1], [51.1, -0.2]], 10, 800),
      alternates: [
        { trip: { legs: [{ shape: "" }] } }, // no geometry -> dropped
        { trip: trip([[51, -0.1], [51.07, -0.13], [51.1, -0.2]], 11, 850) },
      ],
    });
    const result = await fetchGatewayRoute("key", REQUEST);
    expect(result!.routes).toHaveLength(2);
    expect(result!.routes.map((r) => r.index)).toEqual([0, 1]);
  });

  it("returns null when the primary trip has no geometry", async () => {
    mockFetch({ trip: { legs: [{ shape: "" }] } });
    expect(await fetchGatewayRoute("key", REQUEST)).toBeNull();
  });
});
