import { renderHook, waitFor, act } from '@testing-library/react';
import { AuthProvider, useAuth } from '@/lib/auth/AuthContext';
import apiClient from '@/lib/api/client';
import { useRouter } from 'next/navigation';

// Mock dependencies
jest.mock('@/lib/api/client');
jest.mock('next/navigation', () => ({
  useRouter: jest.fn(),
}));

const mockApiClient = apiClient as jest.Mocked<typeof apiClient>;
const mockUseRouter = useRouter as jest.MockedFunction<typeof useRouter>;

describe('AuthContext', () => {
  const mockRouter = {
    push: jest.fn(),
    replace: jest.fn(),
    refresh: jest.fn(),
    back: jest.fn(),
    forward: jest.fn(),
    prefetch: jest.fn(),
  };

  beforeEach(() => {
    jest.clearAllMocks();
    mockUseRouter.mockReturnValue(mockRouter as any);
    localStorage.clear();
  });

  describe('login', () => {
    it('should login successfully and store tokens', async () => {
      const mockUser = {
        id: 'usr_123',
        email: 'test@example.com',
        name: 'Test User',
        role: 'parent',
      };

      const mockTokens = {
        accessToken: 'access_token_123',
        refreshToken: 'refresh_token_123',
      };

      mockApiClient.post.mockResolvedValueOnce({
        data: {
          success: true,
          data: {
            user: mockUser,
            tokens: mockTokens,
          },
        },
      });

      const { result } = renderHook(() => useAuth(), {
        wrapper: AuthProvider,
      });

      await act(async () => {
        await result.current.login({
          email: 'test@example.com',
          password: 'password123',
        });
      });

      await waitFor(() => {
        expect(result.current.user).toEqual(mockUser);
        expect(result.current.isAuthenticated).toBe(true);
        expect(localStorage.setItem).toHaveBeenCalledWith('accessToken', mockTokens.accessToken);
        expect(localStorage.setItem).toHaveBeenCalledWith('refreshToken', mockTokens.refreshToken);
        expect(mockRouter.push).toHaveBeenCalledWith('/');
      });
    });

    it('should handle login failure', async () => {
      mockApiClient.post.mockRejectedValueOnce({
        response: {
          data: {
            message: 'Invalid credentials',
          },
        },
      });

      const { result } = renderHook(() => useAuth(), {
        wrapper: AuthProvider,
      });

      await expect(
        result.current.login({
          email: 'test@example.com',
          password: 'wrong_password',
        })
      ).rejects.toThrow('Invalid credentials');

      expect(result.current.user).toBeNull();
      expect(result.current.isAuthenticated).toBe(false);
    });
  });

  describe('register', () => {
    it('should register successfully and redirect to onboarding', async () => {
      const mockUser = {
        id: 'usr_123',
        email: 'newuser@example.com',
        name: 'New User',
        role: 'parent',
      };

      const mockTokens = {
        accessToken: 'access_token_123',
        refreshToken: 'refresh_token_123',
      };

      mockApiClient.post.mockResolvedValueOnce({
        data: {
          success: true,
          data: {
            user: mockUser,
            tokens: mockTokens,
            family: { id: 'fam_123' },
          },
        },
      });

      const { result } = renderHook(() => useAuth(), {
        wrapper: AuthProvider,
      });

      await act(async () => {
        await result.current.register({
          email: 'newuser@example.com',
          password: 'password123',
          name: 'New User',
        });
      });

      await waitFor(() => {
        expect(result.current.user).toEqual(mockUser);
        expect(localStorage.setItem).toHaveBeenCalledWith('accessToken', mockTokens.accessToken);
        expect(mockRouter.push).toHaveBeenCalledWith('/onboarding');
      });
    });

    it('should handle registration failure with invalid tokens', async () => {
      mockApiClient.post.mockResolvedValueOnce({
        data: {
          success: true,
          data: {
            user: {},
            tokens: {}, // Invalid tokens
          },
        },
      });

      const { result } = renderHook(() => useAuth(), {
        wrapper: AuthProvider,
      });

      await expect(
        result.current.register({
          email: 'newuser@example.com',
          password: 'password123',
          name: 'New User',
        })
      ).rejects.toThrow('Invalid response from server');
    });
  });

  describe('logout', () => {
    it('should logout successfully and clear tokens', async () => {
      mockApiClient.post.mockResolvedValueOnce({
        data: { success: true },
      });

      localStorage.setItem('accessToken', 'token_123');
      localStorage.setItem('refreshToken', 'refresh_123');

      const { result } = renderHook(() => useAuth(), {
        wrapper: AuthProvider,
      });

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

      await waitFor(() => {
        expect(localStorage.removeItem).toHaveBeenCalledWith('accessToken');
        expect(localStorage.removeItem).toHaveBeenCalledWith('refreshToken');
        expect(result.current.user).toBeNull();
        expect(mockRouter.push).toHaveBeenCalledWith('/login');
      });
    });

    it('should clear tokens even if API call fails', async () => {
      mockApiClient.post.mockRejectedValueOnce(new Error('Network error'));

      localStorage.setItem('accessToken', 'token_123');
      localStorage.setItem('refreshToken', 'refresh_123');

      const { result } = renderHook(() => useAuth(), {
        wrapper: AuthProvider,
      });

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

      await waitFor(() => {
        expect(localStorage.removeItem).toHaveBeenCalledWith('accessToken');
        expect(localStorage.removeItem).toHaveBeenCalledWith('refreshToken');
        expect(mockRouter.push).toHaveBeenCalledWith('/login');
      });
    });
  });

  describe('refreshUser', () => {
    it('should refresh user data successfully', async () => {
      const mockUser = {
        id: 'usr_123',
        email: 'test@example.com',
        name: 'Updated Name',
        role: 'parent',
      };

      mockApiClient.get.mockResolvedValueOnce({
        data: {
          data: mockUser,
        },
      });

      const { result } = renderHook(() => useAuth(), {
        wrapper: AuthProvider,
      });

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

      await waitFor(() => {
        expect(result.current.user).toEqual(mockUser);
      });
    });

    it('should handle refresh failure gracefully', async () => {
      mockApiClient.get.mockRejectedValueOnce(new Error('Unauthorized'));

      const { result } = renderHook(() => useAuth(), {
        wrapper: AuthProvider,
      });

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

      // User should remain null, no error thrown
      expect(result.current.user).toBeNull();
    });
  });

  describe('authentication check', () => {
    it('should check auth on mount with valid token', async () => {
      const mockUser = {
        id: 'usr_123',
        email: 'test@example.com',
        name: 'Test User',
        role: 'parent',
      };

      localStorage.setItem('accessToken', 'valid_token');

      mockApiClient.get.mockResolvedValueOnce({
        data: {
          data: mockUser,
        },
      });

      const { result } = renderHook(() => useAuth(), {
        wrapper: AuthProvider,
      });

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false);
        expect(result.current.user).toEqual(mockUser);
        expect(result.current.isAuthenticated).toBe(true);
      });
    });

    it('should handle auth check failure', async () => {
      localStorage.setItem('accessToken', 'invalid_token');

      mockApiClient.get.mockRejectedValueOnce(new Error('Unauthorized'));

      const { result } = renderHook(() => useAuth(), {
        wrapper: AuthProvider,
      });

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false);
        expect(result.current.user).toBeNull();
        expect(localStorage.removeItem).toHaveBeenCalledWith('accessToken');
        expect(localStorage.removeItem).toHaveBeenCalledWith('refreshToken');
      });
    });

    it('should not check auth if no token exists', async () => {
      const { result } = renderHook(() => useAuth(), {
        wrapper: AuthProvider,
      });

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false);
        expect(result.current.user).toBeNull();
        expect(mockApiClient.get).not.toHaveBeenCalled();
      });
    });
  });
});
