import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, waitFor, act } from '@testing-library/react';
import { useUsernameAvailability } from './useUsernameAvailability';
import { checkUsernameAvailability } from '../services/authService';

vi.mock('../services/authService', () => ({
  checkUsernameAvailability: vi.fn(),
}));

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

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

  it('should return null when username is empty', () => {
    const { result } = renderHook(() => useUsernameAvailability(''));

    expect(result.current.available).toBeNull();
    expect(result.current.checking).toBe(false);
  });

  it('should return null when username is too short', () => {
    const { result } = renderHook(() => useUsernameAvailability('ab'));

    expect(result.current.available).toBeNull();
    expect(result.current.checking).toBe(false);
  });

  it('should check availability when username is long enough', async () => {
    vi.mocked(checkUsernameAvailability).mockResolvedValue(true);

    const { result } = renderHook(() => useUsernameAvailability('testuser'));

    // Wait for debounce (500ms) and API call
    await waitFor(
      () => {
        expect(checkUsernameAvailability).toHaveBeenCalledWith('testuser');
      },
      { timeout: 2000 },
    );

    await waitFor(
      () => {
        expect(result.current.available).toBe(true);
        expect(result.current.checking).toBe(false);
      },
      { timeout: 2000 },
    );
  });

  it('should return false when username is not available', async () => {
    vi.mocked(checkUsernameAvailability).mockResolvedValue(false);

    const { result } = renderHook(() => useUsernameAvailability('takenuser'));

    // Wait for debounce (500ms) and API call
    await waitFor(
      () => {
        expect(checkUsernameAvailability).toHaveBeenCalledWith('takenuser');
      },
      { timeout: 2000 },
    );

    await waitFor(
      () => {
        expect(result.current.available).toBe(false);
        expect(result.current.checking).toBe(false);
      },
      { timeout: 2000 },
    );
  });

  it('should debounce the API call', async () => {
    vi.mocked(checkUsernameAvailability).mockResolvedValue(true);

    const { result, rerender } = renderHook(
      ({ username }) => useUsernameAvailability(username),
      { initialProps: { username: 'test' } },
    );

    // Change username multiple times quickly
    await act(async () => {
      rerender({ username: 'test1' });
      await new Promise((resolve) => setTimeout(resolve, 100));
      rerender({ username: 'test12' });
      await new Promise((resolve) => setTimeout(resolve, 100));
      rerender({ username: 'test123' });
    });

    // Wait for debounce to complete and API call
    await waitFor(
      () => {
        expect(checkUsernameAvailability).toHaveBeenCalled();
      },
      { timeout: 2000 },
    );

    await waitFor(
      () => {
        expect(result.current.available).toBe(true);
      },
      { timeout: 2000 },
    );

    // Should only be called once for the last value
    expect(checkUsernameAvailability).toHaveBeenCalledTimes(1);
    expect(checkUsernameAvailability).toHaveBeenCalledWith('test123');
  });

  it('should handle errors gracefully', async () => {
    vi.mocked(checkUsernameAvailability).mockRejectedValue(
      new Error('API Error'),
    );

    const { result } = renderHook(() => useUsernameAvailability('testuser'));

    // Wait for debounce (500ms) and API call
    await waitFor(
      () => {
        expect(checkUsernameAvailability).toHaveBeenCalledWith('testuser');
      },
      { timeout: 2000 },
    );

    // Wait for promise to reject and state to update
    await waitFor(
      () => {
        expect(result.current.available).toBeNull();
        expect(result.current.checking).toBe(false);
      },
      { timeout: 2000 },
    );
  });

  it('should reset when username changes to empty', async () => {
    vi.mocked(checkUsernameAvailability).mockResolvedValue(true);

    const { result, rerender } = renderHook(
      ({ username }) => useUsernameAvailability(username),
      { initialProps: { username: 'testuser' } },
    );

    // Wait for debounce and API call
    await waitFor(
      () => {
        expect(result.current.available).toBe(true);
      },
      { timeout: 2000 },
    );

    // Change to empty
    await act(async () => {
      rerender({ username: '' });
    });

    expect(result.current.available).toBeNull();
    expect(result.current.checking).toBe(false);
  });
});
