// src/hooks/useAppInitialization.test.tsx
import { renderHook, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { MemoryRouter, useNavigate } from 'react-router-dom';
import { useAppInitialization } from './useAppInitialization';
import { useAuth } from './useAuth';
import { useModal } from './useModal';
import { createMockUserProfile } from '../tests/utils/mockFactories';

// Mock dependencies
vi.mock('./useAuth');
vi.mock('./useModal');
vi.mock('react-router-dom', async (importOriginal) => {
  const actual = await importOriginal<typeof import('react-router-dom')>();
  return {
    ...actual,
    useNavigate: vi.fn(),
  };
});
vi.mock('../services/logger.client');
vi.mock('../config', () => ({
  default: {
    app: { version: '1.0.1' },
  },
}));

const mockedUseAuth = vi.mocked(useAuth);
const mockedUseModal = vi.mocked(useModal);
const mockedUseNavigate = vi.mocked(useNavigate);

const mockLogin = vi.fn().mockResolvedValue(undefined);
const mockNavigate = vi.fn();
const mockOpenModal = vi.fn();

// Wrapper with MemoryRouter is needed because the hook uses useLocation and useNavigate
const wrapper = ({
  children,
  initialEntries = ['/'],
}: {
  children: React.ReactNode;
  initialEntries?: string[];
}) => <MemoryRouter initialEntries={initialEntries}>{children}</MemoryRouter>;

describe('useAppInitialization Hook', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockedUseNavigate.mockReturnValue(mockNavigate);
    mockedUseAuth.mockReturnValue({
      userProfile: null,
      login: mockLogin,
      authStatus: 'SIGNED_OUT',
      isLoading: false,
      logout: vi.fn(),
      updateProfile: vi.fn(),
    });
    mockedUseModal.mockReturnValue({
      openModal: mockOpenModal,
      closeModal: vi.fn(),
      isModalOpen: vi.fn(),
    });
    // Mock localStorage
    Object.defineProperty(window, 'localStorage', {
      value: {
        getItem: vi.fn().mockReturnValue(null),
        setItem: vi.fn(),
        removeItem: vi.fn(),
        clear: vi.fn(),
      },
      writable: true,
    });
    // Mock matchMedia
    Object.defineProperty(window, 'matchMedia', {
      value: vi.fn().mockImplementation((_query) => ({
        matches: false, // default to light mode
      })),
      writable: true,
      configurable: true,
    });
  });

  it('should call login when googleAuthToken is in URL', async () => {
    renderHook(() => useAppInitialization(), {
      wrapper: (props) => wrapper({ ...props, initialEntries: ['/?googleAuthToken=test-token'] }),
    });
    await waitFor(() => {
      expect(mockLogin).toHaveBeenCalledWith('test-token');
    });
  });

  it('should call login when githubAuthToken is in URL', async () => {
    renderHook(() => useAppInitialization(), {
      wrapper: (props) => wrapper({ ...props, initialEntries: ['/?githubAuthToken=test-token'] }),
    });
    await waitFor(() => {
      expect(mockLogin).toHaveBeenCalledWith('test-token');
    });
  });

  it('should call navigate to clean the URL after processing a token', async () => {
    renderHook(() => useAppInitialization(), {
      wrapper: (props) =>
        wrapper({ ...props, initialEntries: ['/some/path?googleAuthToken=test-token'] }),
    });
    await waitFor(() => {
      expect(mockLogin).toHaveBeenCalledWith('test-token');
    });
    expect(mockNavigate).toHaveBeenCalledWith('/some/path', { replace: true });
  });

  it('should open "What\'s New" modal if version is new', () => {
    vi.spyOn(window.localStorage, 'getItem').mockReturnValue('1.0.0');
    renderHook(() => useAppInitialization(), { wrapper });
    expect(mockOpenModal).toHaveBeenCalledWith('whatsNew');
    expect(window.localStorage.setItem).toHaveBeenCalledWith('lastSeenVersion', '1.0.1');
  });

  it('should not open "What\'s New" modal if version is the same', () => {
    vi.spyOn(window.localStorage, 'getItem').mockReturnValue('1.0.1');
    renderHook(() => useAppInitialization(), { wrapper });
    expect(mockOpenModal).not.toHaveBeenCalled();
  });

  it('should set dark mode from user profile', async () => {
    mockedUseAuth.mockReturnValue({
      ...mockedUseAuth(),
      userProfile: createMockUserProfile({ preferences: { darkMode: true } }),
    });
    const { result } = renderHook(() => useAppInitialization(), { wrapper });
    await waitFor(() => {
      expect(result.current.isDarkMode).toBe(true);
      expect(document.documentElement.classList.contains('dark')).toBe(true);
    });
  });

  it('should set dark mode from localStorage', async () => {
    vi.spyOn(window.localStorage, 'getItem').mockImplementation((key) =>
      key === 'darkMode' ? 'true' : null,
    );
    const { result } = renderHook(() => useAppInitialization(), { wrapper });
    await waitFor(() => {
      expect(result.current.isDarkMode).toBe(true);
      expect(document.documentElement.classList.contains('dark')).toBe(true);
    });
  });

  it('should set dark mode from system preference', async () => {
    vi.spyOn(window, 'matchMedia').mockReturnValue({ matches: true } as any);
    const { result } = renderHook(() => useAppInitialization(), { wrapper });
    await waitFor(() => {
      expect(result.current.isDarkMode).toBe(true);
      expect(document.documentElement.classList.contains('dark')).toBe(true);
    });
  });

  it('should set unit system from user profile', async () => {
    mockedUseAuth.mockReturnValue({
      ...mockedUseAuth(),
      userProfile: createMockUserProfile({ preferences: { unitSystem: 'metric' } }),
    });
    const { result } = renderHook(() => useAppInitialization(), { wrapper });
    await waitFor(() => {
      expect(result.current.unitSystem).toBe('metric');
    });
  });

  it('should set unit system from localStorage', async () => {
    vi.spyOn(window.localStorage, 'getItem').mockImplementation((key) =>
      key === 'unitSystem' ? 'metric' : null,
    );
    const { result } = renderHook(() => useAppInitialization(), { wrapper });
    await waitFor(() => {
      expect(result.current.unitSystem).toBe('metric');
    });
  });
});
