import { describe, expect, it } from "vitest";
import { appModeReducer } from "./reducer";
import { initialAppModeState } from "./types";
import type {
  AppModeState,
  GuidanceModeState,
  PreviewModeState,
} from "./types";
import type {
  BridgeError,
  GuidanceEvent,
  RouteResult,
  SelectedPlace,
} from "../types";

const place: SelectedPlace = {
  name: "Tower Bridge",
  address: "London SE1",
  position: { latitude: 51.5055, longitude: -0.0754 },
};

const routeResult: RouteResult = {
  routes: [
    {
      index: 0,
      geometry: [
        { latitude: 51.5, longitude: -0.1 },
        { latitude: 51.5055, longitude: -0.0754 },
      ],
      distanceMeters: 2500,
      durationSeconds: 420,
      maneuvers: [
        {
          type: "depart",
          instruction: "Head east",
          distanceMeters: 2500,
          position: { latitude: 51.5, longitude: -0.1 },
        },
      ],
    },
  ],
};

function toPlace(state: AppModeState): AppModeState {
  return appModeReducer(
    appModeReducer(state, { type: "OPEN_SEARCH" }),
    { type: "SELECT_PLACE", place }
  );
}

function toPreview(state: AppModeState): PreviewModeState {
  const placeState = toPlace(state);
  const preview = appModeReducer(placeState, { type: "REQUEST_DIRECTIONS" });
  if (preview.mode !== "PREVIEW") throw new Error("expected PREVIEW");
  return preview;
}

function toGuidance(state: AppModeState): GuidanceModeState {
  const preview = toPreview(state);
  const computed = appModeReducer(preview, {
    type: "ROUTE_COMPUTED",
    result: routeResult,
  });
  const guidance = appModeReducer(computed, { type: "START" });
  if (guidance.mode !== "GUIDANCE") throw new Error("expected GUIDANCE");
  return guidance;
}

describe("appModeReducer — happy path", () => {
  it("starts in EXPLORE", () => {
    expect(initialAppModeState).toEqual({ mode: "EXPLORE" });
  });

  it("EXPLORE --OPEN_SEARCH--> SEARCH", () => {
    const next = appModeReducer(initialAppModeState, { type: "OPEN_SEARCH" });
    expect(next).toEqual({ mode: "SEARCH", query: "" });
  });

  it("SEARCH --UPDATE_SEARCH_QUERY--> SEARCH (query updates)", () => {
    const searching = appModeReducer(initialAppModeState, {
      type: "OPEN_SEARCH",
    });
    const next = appModeReducer(searching, {
      type: "UPDATE_SEARCH_QUERY",
      query: "tower",
    });
    expect(next).toEqual({ mode: "SEARCH", query: "tower" });
  });

  it("SEARCH --SELECT_PLACE--> PLACE", () => {
    const next = toPlace(initialAppModeState);
    expect(next).toEqual({ mode: "PLACE", place });
  });

  it("EXPLORE --SELECT_PLACE--> PLACE (map tap, no search first)", () => {
    const next = appModeReducer(initialAppModeState, {
      type: "SELECT_PLACE",
      place,
    });
    expect(next).toEqual({ mode: "PLACE", place });
  });

  it("PLACE --REQUEST_DIRECTIONS--> PREVIEW, defaults profile to car", () => {
    const preview = toPreview(initialAppModeState);
    expect(preview).toMatchObject({
      mode: "PREVIEW",
      place,
      profile: "car",
      activeRouteIndex: 0,
      isComputing: true,
    });
  });

  it("PREVIEW --ROUTE_COMPUTED--> PREVIEW with route result attached", () => {
    const preview = toPreview(initialAppModeState);
    const next = appModeReducer(preview, {
      type: "ROUTE_COMPUTED",
      result: routeResult,
    });
    expect(next).toMatchObject({
      mode: "PREVIEW",
      routeResult,
      isComputing: false,
      error: undefined,
    });
  });

  it("PREVIEW --ROUTE_FAILED--> PREVIEW carries the error, not computing", () => {
    const preview = toPreview(initialAppModeState);
    const error: BridgeError = { code: "E_NO_ROUTE", message: "no route" };
    const next = appModeReducer(preview, { type: "ROUTE_FAILED", error });
    expect(next).toMatchObject({ mode: "PREVIEW", isComputing: false, error });
  });

  it("PREVIEW --START--> GUIDANCE, carrying the active route index", () => {
    const preview = toPreview(initialAppModeState);
    const computed = appModeReducer(preview, {
      type: "ROUTE_COMPUTED",
      result: routeResult,
      activeRouteIndex: 0,
    });
    const guidance = appModeReducer(computed, { type: "START" });
    expect(guidance).toMatchObject({
      mode: "GUIDANCE",
      place,
      routeIndex: 0,
      isFollowingCamera: true,
      isOverview: false,
      isRerouting: false,
    });
  });

  it("GUIDANCE --GUIDANCE_UPDATE (rerouting)--> sets isRerouting", () => {
    const guidance = toGuidance(initialAppModeState);
    const event: GuidanceEvent = {
      state: "rerouting",
      location: { latitude: 51.5, longitude: -0.1, bearingDegrees: 90, speedMps: 10 },
      distanceToManeuverMeters: 50,
      remainingDistanceMeters: 2000,
      remainingDurationSeconds: 300,
      etaEpochMs: Date.now(),
      isOffRoute: true,
    };
    const next = appModeReducer(guidance, {
      type: "GUIDANCE_UPDATE",
      event,
    });
    expect(next).toMatchObject({ mode: "GUIDANCE", isRerouting: true, guidance: event });
  });

  it("GUIDANCE --ARRIVE--> ARRIVED with journey stats", () => {
    const guidance = toGuidance(initialAppModeState);
    const next = appModeReducer(guidance, {
      type: "ARRIVE",
      journey: { durationSeconds: 400, distanceMeters: 2500 },
    });
    expect(next).toEqual({
      mode: "ARRIVED",
      place,
      journey: { durationSeconds: 400, distanceMeters: 2500 },
    });
  });

  it("ARRIVED --DONE--> EXPLORE", () => {
    const guidance = toGuidance(initialAppModeState);
    const arrived = appModeReducer(guidance, {
      type: "ARRIVE",
      journey: { durationSeconds: 400, distanceMeters: 2500 },
    });
    const next = appModeReducer(arrived, { type: "DONE" });
    expect(next).toEqual({ mode: "EXPLORE" });
  });

  it("GUIDANCE --EXIT--> EXPLORE, instantly, no confirm state", () => {
    const guidance = toGuidance(initialAppModeState);
    const next = appModeReducer(guidance, { type: "EXIT" });
    expect(next).toEqual({ mode: "EXPLORE" });
  });
});

describe("appModeReducer — BACK is context sensitive", () => {
  it("PLACE --BACK--> EXPLORE", () => {
    const placeState = toPlace(initialAppModeState);
    const next = appModeReducer(placeState, { type: "BACK" });
    expect(next).toEqual({ mode: "EXPLORE" });
  });

  it("PREVIEW --BACK--> PLACE, keeping the place (S6 X returns to S5)", () => {
    const preview = toPreview(initialAppModeState);
    const next = appModeReducer(preview, { type: "BACK" });
    expect(next).toEqual({ mode: "PLACE", place });
  });

  it("EXPLORE --BACK--> EXPLORE (no-op)", () => {
    const next = appModeReducer(initialAppModeState, { type: "BACK" });
    expect(next).toEqual(initialAppModeState);
  });
});

describe("appModeReducer — invalid transitions are no-ops", () => {
  it("START from EXPLORE does nothing", () => {
    const next = appModeReducer(initialAppModeState, { type: "START" });
    expect(next).toBe(initialAppModeState);
  });

  it("CHANGE_PROFILE outside PREVIEW does nothing", () => {
    const next = appModeReducer(initialAppModeState, {
      type: "CHANGE_PROFILE",
      profile: "bicycle",
    });
    expect(next).toBe(initialAppModeState);
  });

  it("START from PREVIEW without a computed route does nothing", () => {
    const preview = toPreview(initialAppModeState);
    expect(preview.routeResult).toBeUndefined();
    const next = appModeReducer(preview, { type: "START" });
    expect(next).toBe(preview);
  });

  it("ARRIVE outside GUIDANCE does nothing", () => {
    const next = appModeReducer(initialAppModeState, {
      type: "ARRIVE",
      journey: { durationSeconds: 1, distanceMeters: 1 },
    });
    expect(next).toBe(initialAppModeState);
  });

  it("GUIDANCE_UPDATE outside GUIDANCE does nothing", () => {
    const preview = toPreview(initialAppModeState);
    const event: GuidanceEvent = {
      state: "navigating",
      location: { latitude: 0, longitude: 0, bearingDegrees: 0, speedMps: 0 },
      distanceToManeuverMeters: 0,
      remainingDistanceMeters: 0,
      remainingDurationSeconds: 0,
      etaEpochMs: 0,
      isOffRoute: false,
    };
    const next = appModeReducer(preview, { type: "GUIDANCE_UPDATE", event });
    expect(next).toBe(preview);
  });

  it("RESET returns to EXPLORE from any mode", () => {
    const guidance = toGuidance(initialAppModeState);
    const next = appModeReducer(guidance, { type: "RESET" });
    expect(next).toEqual({ mode: "EXPLORE" });
  });
});

describe("appModeReducer — TOGGLE_OVERVIEW restores follow on exit", () => {
  it("toggling overview on breaks follow, toggling off restores it", () => {
    const guidance = toGuidance(initialAppModeState);
    expect(guidance.isFollowingCamera).toBe(true);
    expect(guidance.isOverview).toBe(false);

    const overviewOn = appModeReducer(guidance, { type: "TOGGLE_OVERVIEW" });
    expect(overviewOn).toMatchObject({ isOverview: true });

    const overviewOff = appModeReducer(overviewOn, { type: "TOGGLE_OVERVIEW" });
    expect(overviewOff).toMatchObject({ isOverview: false, isFollowingCamera: true });
  });
});
