import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
import { useAuth } from './useAuth';
import { TokenStorage } from '@/services/tokenStorage';
import { apiClient } from '@/services/api/client';

// Mock dependencies
vi.mock('@/services/tokenStorage');
vi.mock('@/services/api/client');

const mockTokenStorage = vi.mocked(TokenStorage);
const mockApiClient = vi.mocked(apiClient);

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

  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('should initialize with loading state and then complete', async () => {
    mockTokenStorage.hasTokens.mockReturnValue(false);
    mockApiClient.get = vi.fn();

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

    // Initially should be loading (may be very brief, so we check immediately after render)
    // The loading state might already be false if the check completes synchronously
    // So we just verify the final state
    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    expect(result.current.isAuthenticated).toBe(false);
    expect(mockTokenStorage.hasTokens).toHaveBeenCalled();
  });

  it('should set authenticated to false when no tokens exist', async () => {
    mockTokenStorage.hasTokens.mockReturnValue(false);
    mockApiClient.get = vi.fn();

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

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

    expect(result.current.isAuthenticated).toBe(false);
    expect(mockTokenStorage.hasTokens).toHaveBeenCalled();
    expect(mockApiClient.get).not.toHaveBeenCalled();
  });

  it('should validate token and set authenticated to true when valid', async () => {
    mockTokenStorage.hasTokens.mockReturnValue(true);
    mockApiClient.get = vi
      .fn()
      .mockResolvedValue({ data: { id: 1, email: 'test@example.com' } });

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

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

    expect(result.current.isAuthenticated).toBe(true);
    expect(mockTokenStorage.hasTokens).toHaveBeenCalled();
    expect(mockApiClient.get).toHaveBeenCalledWith('/auth/me');
  });

  it('should clear tokens and set authenticated to false when token is invalid', async () => {
    mockTokenStorage.hasTokens.mockReturnValue(true);
    mockApiClient.get = vi.fn().mockRejectedValue(new Error('Unauthorized'));

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

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

    expect(result.current.isAuthenticated).toBe(false);
    expect(mockTokenStorage.hasTokens).toHaveBeenCalled();
    expect(mockApiClient.get).toHaveBeenCalledWith('/auth/me');
    expect(mockTokenStorage.clearTokens).toHaveBeenCalledTimes(1);
  });

  it('should clear tokens and set authenticated to false on 401 error', async () => {
    mockTokenStorage.hasTokens.mockReturnValue(true);
    const axiosError = {
      response: {
        status: 401,
        data: { error: 'Unauthorized' },
      },
      isAxiosError: true,
    };
    mockApiClient.get = vi.fn().mockRejectedValue(axiosError);

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

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

    expect(result.current.isAuthenticated).toBe(false);
    expect(mockTokenStorage.clearTokens).toHaveBeenCalledTimes(1);
  });

  it('should clear tokens and set authenticated to false on network error', async () => {
    mockTokenStorage.hasTokens.mockReturnValue(true);
    mockApiClient.get = vi.fn().mockRejectedValue(new Error('Network Error'));

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

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

    expect(result.current.isAuthenticated).toBe(false);
    expect(mockTokenStorage.clearTokens).toHaveBeenCalledTimes(1);
  });

  it('should only check auth once on mount', async () => {
    mockTokenStorage.hasTokens.mockReturnValue(true);
    mockApiClient.get = vi
      .fn()
      .mockResolvedValue({ data: { id: 1, email: 'test@example.com' } });

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

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

    // Wait a bit to ensure no additional calls
    await new Promise((resolve) => setTimeout(resolve, 100));

    expect(mockApiClient.get).toHaveBeenCalledTimes(1);
  });
});
