import { describe, it, expect, beforeEach, vi } from 'vitest';
import { renderHook, act, waitFor } from '@testing-library/react';
import { useAuthStore } from './useAuth';
import { useLogin } from './useLogin';
import { useRegister } from './useRegister';
import { useLogout } from './useLogout';
import { usePasswordReset } from './usePasswordReset';
import * as authService from '../services/authService';

// Mock react-router-dom
const mockNavigate = vi.fn();
vi.mock('react-router-dom', async () => {
  const actual = await vi.importActual('react-router-dom');
  return {
    ...actual,
    useNavigate: () => mockNavigate,
  };
});

// Mock authService
vi.mock('../services/authService', () => ({
  login: vi.fn(),
  register: vi.fn(),
  logout: vi.fn(),
  requestPasswordReset: vi.fn(),
  resetPassword: vi.fn(),
}));

describe('useAuthStore', () => {
  beforeEach(() => {
    // Reset store state
    useAuthStore.setState({
      user: null,
      accessToken: null,
      refreshToken: null,
      isAuthenticated: false,
    });
  });

  it('should have initial state', () => {
    const state = useAuthStore.getState();
    expect(state.user).toBeNull();
    expect(state.accessToken).toBeNull();
    expect(state.refreshToken).toBeNull();
    expect(state.isAuthenticated).toBe(false);
  });

  it('should set auth state', () => {
    const user = { id: 1, email: 'test@example.com', username: 'testuser' };
    const accessToken = 'access-token-123';
    const refreshToken = 'refresh-token-123';

    act(() => {
      useAuthStore.getState().setAuth(user, accessToken, refreshToken);
    });

    const state = useAuthStore.getState();
    expect(state.user).toEqual(user);
    expect(state.accessToken).toBe(accessToken);
    expect(state.refreshToken).toBe(refreshToken);
    expect(state.isAuthenticated).toBe(true);
  });

  it('should clear auth state', () => {
    const user = { id: 1, email: 'test@example.com', username: 'testuser' };
    act(() => {
      useAuthStore.getState().setAuth(user, 'token', 'refresh');
    });

    act(() => {
      useAuthStore.getState().clearAuth();
    });

    const state = useAuthStore.getState();
    expect(state.user).toBeNull();
    expect(state.accessToken).toBeNull();
    expect(state.refreshToken).toBeNull();
    expect(state.isAuthenticated).toBe(false);
  });

  it('should update tokens', () => {
    const user = { id: 1, email: 'test@example.com', username: 'testuser' };
    act(() => {
      useAuthStore.getState().setAuth(user, 'old-token', 'old-refresh');
    });

    act(() => {
      useAuthStore.getState().updateTokens('new-token', 'new-refresh');
    });

    const state = useAuthStore.getState();
    expect(state.accessToken).toBe('new-token');
    expect(state.refreshToken).toBe('new-refresh');
    expect(state.user).toEqual(user); // User should remain unchanged
  });
});

describe('useLogin', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    useAuthStore.setState({
      user: null,
      accessToken: null,
      refreshToken: null,
      isAuthenticated: false,
    });
  });

  it('should login successfully', async () => {
    const mockResponse = {
      user: { id: 1, email: 'test@example.com', username: 'testuser' },
      accessToken: 'access-token-123',
      refreshToken: 'refresh-token-123',
    };

    vi.mocked(authService.login).mockResolvedValue(mockResponse);

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

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

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

    expect(authService.login).toHaveBeenCalledWith({
      email: 'test@example.com',
      password: 'password123',
    });
    expect(useAuthStore.getState().user).toEqual(mockResponse.user);
    expect(useAuthStore.getState().accessToken).toBe(mockResponse.accessToken);
    expect(mockNavigate).toHaveBeenCalledWith('/dashboard');
    expect(result.current.error).toBeNull();
  });

  it('should handle login error', async () => {
    const mockError = new Error('Invalid credentials');
    vi.mocked(authService.login).mockRejectedValue(mockError);

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

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

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

    expect(result.current.error).toEqual(mockError);
    expect(useAuthStore.getState().isAuthenticated).toBe(false);
  });

  it('should set loading state during login', async () => {
    vi.mocked(authService.login).mockImplementation(
      () =>
        new Promise((resolve) => {
          setTimeout(() => {
            resolve({
              user: { id: 1, email: 'test@example.com', username: 'testuser' },
              accessToken: 'token',
              refreshToken: 'refresh',
            });
          }, 100);
        }),
    );

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

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

    expect(result.current.loading).toBe(true);

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

describe('useRegister', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    useAuthStore.setState({
      user: null,
      accessToken: null,
      refreshToken: null,
      isAuthenticated: false,
    });
  });

  it('should register successfully', async () => {
    const mockResponse = {
      user: { id: 1, email: 'new@example.com', username: 'newuser' },
      accessToken: 'access-token-123',
      refreshToken: 'refresh-token-123',
    };

    vi.mocked(authService.register).mockResolvedValue(mockResponse);

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

    await act(async () => {
      await result.current.handleRegister({
        email: 'new@example.com',
        password: 'password123',
        confirmPassword: 'password123',
        username: 'newuser',
      });
    });

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

    expect(authService.register).toHaveBeenCalledWith({
      email: 'new@example.com',
      password: 'password123',
      confirmPassword: 'password123',
      username: 'newuser',
    });
    expect(useAuthStore.getState().user).toEqual(mockResponse.user);
    expect(mockNavigate).toHaveBeenCalledWith('/dashboard');
    expect(result.current.error).toBeNull();
  });

  it('should handle registration error', async () => {
    const mockError = new Error('Email already exists');
    vi.mocked(authService.register).mockRejectedValue(mockError);

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

    await act(async () => {
      await result.current.handleRegister({
        email: 'existing@example.com',
        password: 'password123',
        confirmPassword: 'password123',
        username: 'existinguser',
      });
    });

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

    expect(result.current.error).toEqual(mockError);
  });
});

describe('useLogout', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    const user = { id: 1, email: 'test@example.com', username: 'testuser' };
    useAuthStore.setState({
      user,
      accessToken: 'token',
      refreshToken: 'refresh',
      isAuthenticated: true,
    });
  });

  it('should logout successfully', async () => {
    vi.mocked(authService.logout).mockResolvedValue(undefined);

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

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

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

    expect(authService.logout).toHaveBeenCalled();
    expect(useAuthStore.getState().isAuthenticated).toBe(false);
    expect(useAuthStore.getState().user).toBeNull();
    expect(mockNavigate).toHaveBeenCalledWith('/login');
    expect(result.current.error).toBeNull();
  });

  it('should clear auth even on logout error', async () => {
    const mockError = new Error('Logout failed');
    vi.mocked(authService.logout).mockRejectedValue(mockError);

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

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

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

    expect(useAuthStore.getState().isAuthenticated).toBe(false);
    expect(mockNavigate).toHaveBeenCalledWith('/login');
  });
});

describe('usePasswordReset', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('should request password reset successfully', async () => {
    vi.mocked(authService.requestPasswordReset).mockResolvedValue(undefined);

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

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

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

    expect(authService.requestPasswordReset).toHaveBeenCalledWith({
      email: 'test@example.com',
    });
    expect(result.current.success).toBe(true);
    expect(result.current.error).toBeNull();
  });

  it('should handle password reset request error', async () => {
    const mockError = new Error('User not found');
    vi.mocked(authService.requestPasswordReset).mockRejectedValue(mockError);

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

    await act(async () => {
      await result.current.handleRequestReset({
        email: 'nonexistent@example.com',
      });
    });

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

    expect(result.current.error).toEqual(mockError);
    expect(result.current.success).toBe(false);
  });

  it('should reset password successfully', async () => {
    vi.mocked(authService.resetPassword).mockResolvedValue(undefined);

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

    await act(async () => {
      await result.current.handleReset({
        token: 'reset-token-123',
        password: 'newpassword123',
        confirmPassword: 'newpassword123',
      });
    });

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

    expect(authService.resetPassword).toHaveBeenCalledWith({
      token: 'reset-token-123',
      password: 'newpassword123',
      confirmPassword: 'newpassword123',
    });
    expect(result.current.success).toBe(true);
    expect(result.current.error).toBeNull();
  });

  it('should handle password reset error', async () => {
    const mockError = new Error('Invalid token');
    vi.mocked(authService.resetPassword).mockRejectedValue(mockError);

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

    await act(async () => {
      await result.current.handleReset({
        token: 'invalid-token',
        password: 'newpassword123',
        confirmPassword: 'newpassword123',
      });
    });

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

    expect(result.current.error).toEqual(mockError);
    expect(result.current.success).toBe(false);
  });
});
