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

import type { Route } from "../../modules/mapmap-react-native/src/MapmapNav.types";
import { distanceMeters } from "./geo";
import { synthesizeDriveCorpus, type DriveSample } from "./synthesizeDriveCorpus";

function parseLines(jsonl: string): DriveSample[] {
  return jsonl
    .split("\n")
    .filter((line) => line.length > 0)
    .map((line) => JSON.parse(line) as DriveSample);
}

const ORIGIN = { latitude: 51.5074, longitude: -0.1278 };
const DESTINATION = { latitude: 51.51, longitude: -0.13 };

function twoPointRoute(): Route {
  return {
    index: 0,
    geometry: [ORIGIN, DESTINATION],
    distanceMeters: distanceMeters(ORIGIN, DESTINATION),
    durationSeconds: 200,
    maneuvers: [
      { type: "depart", instruction: "Head out", distanceMeters: 0, position: ORIGIN },
      { type: "arrive", instruction: "You have arrived", distanceMeters: 0, position: DESTINATION },
    ],
  };
}

describe("synthesizeDriveCorpus — 2-point route", () => {
  it("produces monotonic ~1Hz timestamps", () => {
    const jsonl = synthesizeDriveCorpus(twoPointRoute(), { startEpochMs: 1_000_000 });
    const samples = parseLines(jsonl);

    expect(samples.length).toBeGreaterThan(1);
    for (let i = 1; i < samples.length; i++) {
      expect(samples[i].t).toBe(samples[i - 1].t + 1000);
    }
    expect(samples[0].t).toBe(1_000_000);
  });

  it("starts at the route origin and ends at the route destination", () => {
    const jsonl = synthesizeDriveCorpus(twoPointRoute());
    const samples = parseLines(jsonl);

    expect(samples[0].lat).toBe(ORIGIN.latitude);
    expect(samples[0].lon).toBe(ORIGIN.longitude);

    const last = samples[samples.length - 1];
    expect(distanceMeters({ latitude: last.lat, longitude: last.lon }, DESTINATION)).toBeLessThan(0.01);
  });

  it("keeps every speed within [slowSpeedMps, cruiseSpeedMps]", () => {
    const jsonl = synthesizeDriveCorpus(twoPointRoute(), { cruiseSpeedMps: 13.4, slowSpeedMps: 5 });
    const samples = parseLines(jsonl);

    for (const sample of samples) {
      expect(sample.speed).toBeGreaterThanOrEqual(5);
      expect(sample.speed).toBeLessThanOrEqual(13.4);
    }
  });

  it("reports a bearing in [0, 360) on every sample", () => {
    const jsonl = synthesizeDriveCorpus(twoPointRoute());
    const samples = parseLines(jsonl);
    for (const sample of samples) {
      expect(sample.bearing).toBeGreaterThanOrEqual(0);
      expect(sample.bearing).toBeLessThan(360);
    }
  });
});

describe("synthesizeDriveCorpus — maneuver slow-down", () => {
  it("slows to slowSpeedMps within slowRadiusMeters of a mid-route maneuver", () => {
    // A longer, three-point route so there's cruise distance either side
    // of the middle maneuver to slow down for.
    const midpoint = { latitude: 51.512, longitude: -0.129 };
    const farDestination = { latitude: 51.52, longitude: -0.135 };
    const route: Route = {
      index: 0,
      geometry: [ORIGIN, midpoint, farDestination],
      distanceMeters: distanceMeters(ORIGIN, midpoint) + distanceMeters(midpoint, farDestination),
      durationSeconds: 400,
      maneuvers: [
        { type: "depart", instruction: "Head out", distanceMeters: 0, position: ORIGIN },
        { type: "turn-left", instruction: "Turn left", distanceMeters: 0, position: midpoint },
        { type: "arrive", instruction: "You have arrived", distanceMeters: 0, position: farDestination },
      ],
    };

    const jsonl = synthesizeDriveCorpus(route, { cruiseSpeedMps: 13.4, slowSpeedMps: 5, slowRadiusMeters: 40 });
    const samples = parseLines(jsonl);

    const slowSamples = samples.filter((s) => s.speed === 5);
    const cruiseSamples = samples.filter((s) => s.speed === 13.4);
    expect(slowSamples.length).toBeGreaterThan(0);
    expect(cruiseSamples.length).toBeGreaterThan(0);
  });

  it("does not infinite-loop when origin and destination coincide", () => {
    const route: Route = {
      index: 0,
      geometry: [ORIGIN, ORIGIN],
      distanceMeters: 0,
      durationSeconds: 0,
      maneuvers: [
        { type: "depart", instruction: "Head out", distanceMeters: 0, position: ORIGIN },
        { type: "arrive", instruction: "You have arrived", distanceMeters: 0, position: ORIGIN },
      ],
    };

    const jsonl = synthesizeDriveCorpus(route);
    const samples = parseLines(jsonl);
    expect(samples).toHaveLength(1);
    expect(samples[0].lat).toBe(ORIGIN.latitude);
  });
});
