/**
 * ThemeContext Tests
 *
 * Tests for dark mode support functionality
 */

import React from 'react';
import { renderHook, act, waitFor } from '@testing-library/react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { ThemeProvider, useTheme, useThemeOptional, type ThemeMode } from '../ThemeContext';

// Mock AsyncStorage
jest.mock('@react-native-async-storage/async-storage', () => ({
  getItem: jest.fn(() => Promise.resolve(null)),
  setItem: jest.fn(() => Promise.resolve()),
}));

// Mock useColorScheme hook via the hook path
let mockSystemColorScheme: 'light' | 'dark' | null = 'light';
jest.mock('react-native/Libraries/Utilities/useColorScheme', () => ({
  default: () => mockSystemColorScheme,
}));

describe('ThemeContext', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    mockSystemColorScheme = 'light';
    (AsyncStorage.getItem as jest.Mock).mockResolvedValue(null);
  });

  const wrapper = ({ children }: { children: React.ReactNode }) => (
    <ThemeProvider>{children}</ThemeProvider>
  );

  describe('ThemeProvider', () => {
    it('should provide default theme values', async () => {
      const { result } = renderHook(() => useTheme(), { wrapper });

      await waitFor(() => {
        expect(result.current.themeMode).toBe('system');
        expect(result.current.resolvedTheme).toBe('light');
        expect(result.current.isDark).toBe(false);
      });
    });

    it('should resolve to dark theme when system is dark', async () => {
      mockSystemColorScheme = 'dark';

      const { result } = renderHook(() => useTheme(), { wrapper });

      await waitFor(() => {
        expect(result.current.resolvedTheme).toBe('dark');
        expect(result.current.isDark).toBe(true);
      });
    });

    it('should load saved theme preference from storage', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue('dark');

      const { result } = renderHook(() => useTheme(), { wrapper });

      await waitFor(() => {
        expect(result.current.themeMode).toBe('dark');
        expect(result.current.resolvedTheme).toBe('dark');
        expect(result.current.isDark).toBe(true);
      });
    });

    it('should load light theme preference from storage', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue('light');

      const { result } = renderHook(() => useTheme(), { wrapper });

      await waitFor(() => {
        expect(result.current.themeMode).toBe('light');
        expect(result.current.resolvedTheme).toBe('light');
        expect(result.current.isDark).toBe(false);
      });
    });
  });

  describe('setThemeMode', () => {
    it('should change theme mode to dark', async () => {
      const { result } = renderHook(() => useTheme(), { wrapper });

      await waitFor(() => {
        expect(result.current.themeMode).toBe('system');
      });

      await act(async () => {
        await result.current.setThemeMode('dark');
      });

      expect(result.current.themeMode).toBe('dark');
      expect(result.current.resolvedTheme).toBe('dark');
      expect(result.current.isDark).toBe(true);
      expect(AsyncStorage.setItem).toHaveBeenCalledWith('@wellnuo:theme_preference', 'dark');
    });

    it('should change theme mode to light', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue('dark');

      const { result } = renderHook(() => useTheme(), { wrapper });

      await waitFor(() => {
        expect(result.current.themeMode).toBe('dark');
      });

      await act(async () => {
        await result.current.setThemeMode('light');
      });

      expect(result.current.themeMode).toBe('light');
      expect(result.current.resolvedTheme).toBe('light');
      expect(result.current.isDark).toBe(false);
      expect(AsyncStorage.setItem).toHaveBeenCalledWith('@wellnuo:theme_preference', 'light');
    });

    it('should change theme mode to system', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue('dark');

      const { result } = renderHook(() => useTheme(), { wrapper });

      await waitFor(() => {
        expect(result.current.themeMode).toBe('dark');
      });

      await act(async () => {
        await result.current.setThemeMode('system');
      });

      expect(result.current.themeMode).toBe('system');
      expect(result.current.resolvedTheme).toBe('light'); // system is light
      expect(AsyncStorage.setItem).toHaveBeenCalledWith('@wellnuo:theme_preference', 'system');
    });
  });

  describe('toggleTheme', () => {
    it('should toggle from light to dark', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue('light');

      const { result } = renderHook(() => useTheme(), { wrapper });

      await waitFor(() => {
        expect(result.current.resolvedTheme).toBe('light');
      });

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

      expect(result.current.themeMode).toBe('dark');
      expect(result.current.resolvedTheme).toBe('dark');
    });

    it('should toggle from dark to light', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue('dark');

      const { result } = renderHook(() => useTheme(), { wrapper });

      await waitFor(() => {
        expect(result.current.resolvedTheme).toBe('dark');
      });

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

      expect(result.current.themeMode).toBe('light');
      expect(result.current.resolvedTheme).toBe('light');
    });

    it('should toggle from system (light) to dark', async () => {
      mockSystemColorScheme = 'light';

      const { result } = renderHook(() => useTheme(), { wrapper });

      await waitFor(() => {
        expect(result.current.themeMode).toBe('system');
        expect(result.current.resolvedTheme).toBe('light');
      });

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

      expect(result.current.themeMode).toBe('dark');
      expect(result.current.resolvedTheme).toBe('dark');
    });

    it('should toggle from system (dark) to light', async () => {
      mockSystemColorScheme = 'dark';

      const { result } = renderHook(() => useTheme(), { wrapper });

      await waitFor(() => {
        expect(result.current.themeMode).toBe('system');
        expect(result.current.resolvedTheme).toBe('dark');
      });

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

      expect(result.current.themeMode).toBe('light');
      expect(result.current.resolvedTheme).toBe('light');
    });
  });

  describe('useThemeOptional', () => {
    it('should return null when used outside provider', () => {
      const { result } = renderHook(() => useThemeOptional());
      expect(result.current).toBeNull();
    });

    it('should return theme context when used inside provider', async () => {
      const { result } = renderHook(() => useThemeOptional(), { wrapper });

      await waitFor(() => {
        expect(result.current).not.toBeNull();
        expect(result.current?.themeMode).toBe('system');
      });
    });
  });

  describe('useTheme error handling', () => {
    it('should throw error when used outside provider', () => {
      // Suppress console.error for this test
      const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

      expect(() => {
        renderHook(() => useTheme());
      }).toThrow('useTheme must be used within a ThemeProvider');

      consoleSpy.mockRestore();
    });
  });

  describe('error handling', () => {
    it('should handle AsyncStorage read error gracefully', async () => {
      const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
      (AsyncStorage.getItem as jest.Mock).mockRejectedValue(new Error('Storage error'));

      const { result } = renderHook(() => useTheme(), { wrapper });

      await waitFor(() => {
        // Should fallback to default 'system' mode
        expect(result.current.themeMode).toBe('system');
      });

      consoleSpy.mockRestore();
    });

    it('should handle AsyncStorage write error gracefully', async () => {
      const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
      (AsyncStorage.setItem as jest.Mock).mockRejectedValue(new Error('Storage error'));

      const { result } = renderHook(() => useTheme(), { wrapper });

      await waitFor(() => {
        expect(result.current.themeMode).toBe('system');
      });

      // Should still update state even if storage fails
      await act(async () => {
        await result.current.setThemeMode('dark');
      });

      expect(result.current.themeMode).toBe('dark');
      consoleSpy.mockRestore();
    });

    it('should ignore invalid stored theme values', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue('invalid_theme');

      const { result } = renderHook(() => useTheme(), { wrapper });

      await waitFor(() => {
        // Should fallback to default 'system' mode
        expect(result.current.themeMode).toBe('system');
      });
    });
  });
});
