/**
 * @vitest-environment jsdom
 */

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { renderHook } from "@testing-library/react";
import { StrictMode, type ReactNode } from "react";
import { useGeolocationDefault } from "./useGeolocationDefault.js";
import { transformDictionaries } from "@/shared/dictionaries/index.js";
import type { IDictionaries, IRawDictionaries } from "@/shared/dictionaries/index.js";
import type { IFlightsMapFilterState } from "../types.js";

const raw: IRawDictionaries = {
  regions: [],
  countries: [{ code: "RU", title: { ru: "Россия" }, world_region_id: 500374 }],
  cities: [
    { code: "MOW", title: { ru: "Москва" }, country_code: "RU", has_afl_flights: true, location: { lat: 55, lon: 37 } },
    { code: "LED", title: { ru: "Санкт-Петербург" }, country_code: "RU", has_afl_flights: true, location: { lat: 60, lon: 30 } },
  ],
  airports: [
    { code: "SVO", city_code: "MOW", title: { ru: "Шереметьево" }, has_afl_flights: true, location: { lat: 55, lon: 37 } },
    { code: "LED", city_code: "LED", title: { ru: "Пулково" }, has_afl_flights: true, location: { lat: 60, lon: 30 } },
  ],
};
const dictionaries: IDictionaries = transformDictionaries(raw, "ru");

function makeFilter(
  overrides: Partial<IFlightsMapFilterState> = {},
): IFlightsMapFilterState {
  return {
    connections: false,
    domestic: false,
    international: false,
    ...overrides,
  };
}

interface GeolocationMock {
  getCurrentPosition: ReturnType<typeof vi.fn>;
}

function installGeolocation(mock: GeolocationMock | undefined): void {
  Object.defineProperty(navigator, "geolocation", {
    value: mock,
    writable: true,
    configurable: true,
  });
}

function successMock(lat: number, lon: number): GeolocationMock {
  return {
    getCurrentPosition: vi.fn((success: PositionCallback) => {
      success({
        coords: {
          latitude: lat,
          longitude: lon,
          accuracy: 0,
          altitude: null,
          altitudeAccuracy: null,
          heading: null,
          speed: null,
          toJSON: () => ({}),
        },
        timestamp: 0,
        toJSON: () => ({}),
      } as GeolocationPosition);
    }),
  };
}

function errorMock(): GeolocationMock {
  return {
    getCurrentPosition: vi.fn((_success, error?: PositionErrorCallback) => {
      error?.({
        code: 1,
        message: "permission denied",
        PERMISSION_DENIED: 1,
        POSITION_UNAVAILABLE: 2,
        TIMEOUT: 3,
      } as GeolocationPositionError);
    }),
  };
}

describe("useGeolocationDefault", () => {
  let lastState: IFlightsMapFilterState = makeFilter();
  let setFilterState: (updater: (prev: IFlightsMapFilterState) => IFlightsMapFilterState) => void;

  beforeEach(() => {
    lastState = makeFilter();
    setFilterState = (updater) => {
      lastState = updater(lastState);
    };
  });

  afterEach(() => {
    installGeolocation(undefined);
  });

  it("sets departure to nearest city when filter is empty and geolocation succeeds", () => {
    installGeolocation(successMock(55.1, 37.1));
    renderHook(() =>
      useGeolocationDefault(dictionaries, lastState, setFilterState),
    );
    expect(lastState.departure).toBe("MOW");
  });

  it("does not update when filter.departure is already set", () => {
    lastState = makeFilter({ departure: "LED" });
    installGeolocation(successMock(55.1, 37.1));
    renderHook(() =>
      useGeolocationDefault(dictionaries, lastState, setFilterState),
    );
    expect(lastState.departure).toBe("LED");
  });

  it("does not update when filter.arrival is already set", () => {
    lastState = makeFilter({ arrival: "MOW" });
    installGeolocation(successMock(55.1, 37.1));
    renderHook(() =>
      useGeolocationDefault(dictionaries, lastState, setFilterState),
    );
    expect(lastState.departure).toBeUndefined();
  });

  it("does nothing when geolocation permission is denied", () => {
    installGeolocation(errorMock());
    renderHook(() =>
      useGeolocationDefault(dictionaries, lastState, setFilterState),
    );
    expect(lastState.departure).toBeUndefined();
  });

  it("does nothing when navigator.geolocation is missing", () => {
    installGeolocation(undefined);
    renderHook(() =>
      useGeolocationDefault(dictionaries, lastState, setFilterState),
    );
    expect(lastState.departure).toBeUndefined();
  });

  it("does nothing when dictionaries is null at callback time", () => {
    installGeolocation(successMock(55.1, 37.1));
    renderHook(() =>
      useGeolocationDefault(null, lastState, setFilterState),
    );
    expect(lastState.departure).toBeUndefined();
  });

  it("invokes getCurrentPosition exactly once under StrictMode", () => {
    const mock = successMock(55.1, 37.1);
    installGeolocation(mock);
    const wrapper = ({ children }: { children: ReactNode }) => (
      <StrictMode>{children}</StrictMode>
    );
    renderHook(
      () => useGeolocationDefault(dictionaries, lastState, setFilterState),
      { wrapper },
    );
    expect(mock.getCurrentPosition).toHaveBeenCalledTimes(1);
  });
});
