import { renderHook, act, waitFor } from '@testing-library/react';
import { useFilterOptions } from '@/hooks/useFilterOptions';
import { getFilterOptions } from '@/lib/api/csv-rates';
import type { FilterOptions } from '@/types/rate-filters';

jest.mock('@/lib/api/csv-rates', () => ({
  getFilterOptions: jest.fn(),
}));

const mockGetFilterOptions = jest.mocked(getFilterOptions);

const MOCK_OPTIONS: FilterOptions = {
  companies: ['Maersk', 'MSC', 'CMA CGM'],
  containerTypes: ['20ft', '40ft', '40ft HC'],
  currencies: ['USD', 'EUR'],
};

beforeEach(() => {
  jest.clearAllMocks();
});

describe('useFilterOptions', () => {
  describe('initial state', () => {
    it('starts with loading=true', () => {
      mockGetFilterOptions.mockResolvedValue(MOCK_OPTIONS);
      const { result } = renderHook(() => useFilterOptions());
      expect(result.current.loading).toBe(true);
    });

    it('starts with empty companies array', () => {
      mockGetFilterOptions.mockResolvedValue(MOCK_OPTIONS);
      const { result } = renderHook(() => useFilterOptions());
      expect(result.current.companies).toEqual([]);
    });

    it('starts with empty containerTypes array', () => {
      mockGetFilterOptions.mockResolvedValue(MOCK_OPTIONS);
      const { result } = renderHook(() => useFilterOptions());
      expect(result.current.containerTypes).toEqual([]);
    });

    it('starts with empty currencies array', () => {
      mockGetFilterOptions.mockResolvedValue(MOCK_OPTIONS);
      const { result } = renderHook(() => useFilterOptions());
      expect(result.current.currencies).toEqual([]);
    });

    it('starts with error=null', () => {
      mockGetFilterOptions.mockResolvedValue(MOCK_OPTIONS);
      const { result } = renderHook(() => useFilterOptions());
      expect(result.current.error).toBeNull();
    });
  });

  describe('on mount — success', () => {
    it('fetches options automatically on mount', async () => {
      mockGetFilterOptions.mockResolvedValue(MOCK_OPTIONS);

      renderHook(() => useFilterOptions());

      await waitFor(() => {
        expect(mockGetFilterOptions).toHaveBeenCalledTimes(1);
      });
    });

    it('populates all option arrays after a successful fetch', async () => {
      mockGetFilterOptions.mockResolvedValue(MOCK_OPTIONS);

      const { result } = renderHook(() => useFilterOptions());

      await waitFor(() => expect(result.current.loading).toBe(false));

      expect(result.current.companies).toEqual(MOCK_OPTIONS.companies);
      expect(result.current.containerTypes).toEqual(MOCK_OPTIONS.containerTypes);
      expect(result.current.currencies).toEqual(MOCK_OPTIONS.currencies);
    });

    it('sets loading=false after a successful fetch', async () => {
      mockGetFilterOptions.mockResolvedValue(MOCK_OPTIONS);

      const { result } = renderHook(() => useFilterOptions());

      await waitFor(() => expect(result.current.loading).toBe(false));

      expect(result.current.error).toBeNull();
    });

    it('handles an API response with empty arrays', async () => {
      mockGetFilterOptions.mockResolvedValue({
        companies: [],
        containerTypes: [],
        currencies: [],
      });

      const { result } = renderHook(() => useFilterOptions());

      await waitFor(() => expect(result.current.loading).toBe(false));

      expect(result.current.companies).toEqual([]);
      expect(result.current.containerTypes).toEqual([]);
      expect(result.current.currencies).toEqual([]);
    });
  });

  describe('on mount — error', () => {
    it('sets error when the API call fails', async () => {
      mockGetFilterOptions.mockRejectedValue(new Error('Gateway timeout'));

      const { result } = renderHook(() => useFilterOptions());

      await waitFor(() => expect(result.current.loading).toBe(false));

      expect(result.current.error).toBe('Gateway timeout');
    });

    it('uses a fallback message when the error has no message', async () => {
      mockGetFilterOptions.mockRejectedValue({});

      const { result } = renderHook(() => useFilterOptions());

      await waitFor(() => expect(result.current.loading).toBe(false));

      expect(result.current.error).toBe('Failed to fetch filter options');
    });

    it('preserves the empty option arrays on error', async () => {
      mockGetFilterOptions.mockRejectedValue(new Error('error'));

      const { result } = renderHook(() => useFilterOptions());

      await waitFor(() => expect(result.current.loading).toBe(false));

      expect(result.current.companies).toEqual([]);
      expect(result.current.containerTypes).toEqual([]);
      expect(result.current.currencies).toEqual([]);
    });
  });

  describe('refetch', () => {
    it('exposes a refetch function', async () => {
      mockGetFilterOptions.mockResolvedValue(MOCK_OPTIONS);

      const { result } = renderHook(() => useFilterOptions());

      await waitFor(() => expect(result.current.loading).toBe(false));

      expect(typeof result.current.refetch).toBe('function');
    });

    it('re-triggers the fetch when refetch is invoked', async () => {
      mockGetFilterOptions.mockResolvedValue(MOCK_OPTIONS);

      const { result } = renderHook(() => useFilterOptions());

      await waitFor(() => expect(result.current.loading).toBe(false));

      await act(async () => {
        await result.current.refetch();
      });

      expect(mockGetFilterOptions).toHaveBeenCalledTimes(2);
    });

    it('updates options with fresh data on refetch', async () => {
      const updatedOptions: FilterOptions = {
        companies: ['Maersk', 'MSC', 'ONE'],
        containerTypes: ['20ft', '40ft'],
        currencies: ['USD'],
      };

      mockGetFilterOptions
        .mockResolvedValueOnce(MOCK_OPTIONS)
        .mockResolvedValueOnce(updatedOptions);

      const { result } = renderHook(() => useFilterOptions());

      await waitFor(() => expect(result.current.companies).toEqual(MOCK_OPTIONS.companies));

      await act(async () => {
        await result.current.refetch();
      });

      expect(result.current.companies).toEqual(updatedOptions.companies);
    });
  });
});
