/**
 * @vitest-environment jsdom
 */

import { describe, it, expect, beforeEach, vi } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { useSearchHistory } from "./useSearchHistory";
import type { SearchHistoryItem } from "./useSearchHistory";

// ---------------------------------------------------------------------------
// localStorage mock
// ---------------------------------------------------------------------------

function createMockLocalStorage() {
  const store = new Map<string, string>();
  return {
    getItem: vi.fn((key: string) => store.get(key) ?? null),
    setItem: vi.fn((key: string, value: string) => {
      store.set(key, value);
    }),
    removeItem: vi.fn((key: string) => {
      store.delete(key);
    }),
    get length() {
      return store.size;
    },
    key: vi.fn((index: number) => [...store.keys()][index] ?? null),
    clear: vi.fn(() => store.clear()),
    store,
  } satisfies Storage & { store: Map<string, string> };
}

// ---------------------------------------------------------------------------
// Test data
// ---------------------------------------------------------------------------

const flightItem: SearchHistoryItem = {
  type: "flight-number",
  url: "/ru/onlineboard/flight/SU0001-20250528",
  label: "SU 0001",
};

const routeItem: SearchHistoryItem = {
  type: "board-route",
  url: "/ru/onlineboard/route/SVO-JFK-20250528",
  label: "SVO - JFK",
};

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe("useSearchHistory", () => {
  let mockStorage: ReturnType<typeof createMockLocalStorage>;

  beforeEach(() => {
    mockStorage = createMockLocalStorage();
    vi.stubGlobal("localStorage", mockStorage);
  });

  it("starts with empty history when nothing in storage", () => {
    const { result } = renderHook(() => useSearchHistory("ru"));
    expect(result.current.items).toEqual([]);
  });

  it("loads existing history from storage on mount", () => {
    mockStorage.store.set(
      "afl_history_ru",
      JSON.stringify([flightItem]),
    );

    const { result } = renderHook(() => useSearchHistory("ru"));
    expect(result.current.items).toEqual([flightItem]);
  });

  it("adds an item and persists to storage", () => {
    const { result } = renderHook(() => useSearchHistory("ru"));

    act(() => {
      result.current.add(flightItem);
    });

    expect(result.current.items).toEqual([flightItem]);
    expect(mockStorage.setItem).toHaveBeenCalledWith(
      "afl_history_ru",
      JSON.stringify([flightItem]),
    );
  });

  it("prepends new items (most recent first)", () => {
    const { result } = renderHook(() => useSearchHistory("ru"));

    act(() => {
      result.current.add(flightItem);
    });
    act(() => {
      result.current.add(routeItem);
    });

    expect(result.current.items).toEqual([routeItem, flightItem]);
  });

  it("deduplicates by URL — skips if most recent matches", () => {
    const { result } = renderHook(() => useSearchHistory("ru"));

    act(() => {
      result.current.add(flightItem);
    });
    act(() => {
      result.current.add(flightItem);
    });

    expect(result.current.items).toEqual([flightItem]);
  });

  it("moves duplicate to front when URL exists in older position", () => {
    const { result } = renderHook(() => useSearchHistory("ru"));

    act(() => {
      result.current.add(flightItem);
    });
    act(() => {
      result.current.add(routeItem);
    });
    act(() => {
      result.current.add(flightItem);
    });

    expect(result.current.items).toEqual([flightItem, routeItem]);
  });

  it("limits history to 10 items", () => {
    const { result } = renderHook(() => useSearchHistory("ru"));

    for (let i = 0; i < 12; i++) {
      act(() => {
        result.current.add({
          type: "flight-number",
          url: `/flight-${i}`,
          label: `Flight ${i}`,
        });
      });
    }

    expect(result.current.items).toHaveLength(10);
    // Most recent should be first
    expect(result.current.items[0]?.url).toBe("/flight-11");
  });

  it("namespaces by language — different langs have separate history", () => {
    const { result: ruResult } = renderHook(() => useSearchHistory("ru"));
    const { result: enResult } = renderHook(() => useSearchHistory("en"));

    act(() => {
      ruResult.current.add(flightItem);
    });

    expect(ruResult.current.items).toEqual([flightItem]);
    expect(enResult.current.items).toEqual([]);
  });

  it("clear removes all items and deletes from storage", () => {
    const { result } = renderHook(() => useSearchHistory("ru"));

    act(() => {
      result.current.add(flightItem);
      result.current.add(routeItem);
    });
    act(() => {
      result.current.clear();
    });

    expect(result.current.items).toEqual([]);
    expect(mockStorage.removeItem).toHaveBeenCalledWith("afl_history_ru");
  });

  it("gracefully handles corrupted storage data", () => {
    mockStorage.store.set("afl_history_ru", "not-valid-json{{{");

    const { result } = renderHook(() => useSearchHistory("ru"));
    expect(result.current.items).toEqual([]);
  });

  it("gracefully handles invalid schema in storage", () => {
    mockStorage.store.set(
      "afl_history_ru",
      JSON.stringify([{ invalid: true }]),
    );

    const { result } = renderHook(() => useSearchHistory("ru"));
    expect(result.current.items).toEqual([]);
  });
});
