import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, waitFor, act } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import type { ReactNode } from 'react';
import { useAuthSession, useAuthModalWorkflow } from './hooks';
import * as authApi from '@/shared/api/auth';
import type { CurrentUser } from '@/shared/types/auth';
import { AuthExpiredError } from '@/shared/api/httpClient';

vi.mock('@/shared/api/auth', () => ({
  getCurrentUser: vi.fn(),
  signIn: vi.fn(),
  signOut: vi.fn(),
}));

vi.mock('@/business/campuses/hooks', () => ({
  useCampusCatalog: () => ({
    campuses: [],
    isLoading: false,
    error: null,
  }),
}));

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

function wrapper({ children }: { children: ReactNode }) {
  return <MemoryRouter>{children}</MemoryRouter>;
}

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

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

  describe('initialization', () => {
    it('starts with loading=true', async () => {
      vi.mocked(authApi.getCurrentUser).mockImplementation(
        () => new Promise(() => {}), // Never resolves
      );

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

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

    it('fetches current user on mount', async () => {
      vi.mocked(authApi.getCurrentUser).mockResolvedValue({
        id: 'user-1',
        email: 'test@example.com',
      });

      renderHook(() => useAuthSession(), { wrapper });

      await waitFor(() => {
        expect(authApi.getCurrentUser).toHaveBeenCalledTimes(1);
      });
    });

    it('sets user when fetch succeeds', async () => {
      const mockUser = {
        id: 'user-1',
        email: 'test@example.com',
        firstName: 'Test',
        lastName: 'User',
      };
      vi.mocked(authApi.getCurrentUser).mockResolvedValue(mockUser);

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

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

    it('sets loading=false after fetch completes', async () => {
      vi.mocked(authApi.getCurrentUser).mockResolvedValue({
        id: 'user-1',
        email: 'test@example.com',
      });

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

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

    it('handles fetch error gracefully and clears user', async () => {
      vi.mocked(authApi.getCurrentUser).mockRejectedValue(new Error('Network error'));

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

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

    it('redirects to login on AuthExpiredError', async () => {
      vi.mocked(authApi.getCurrentUser).mockRejectedValue(new AuthExpiredError());

      renderHook(() => useAuthSession(), { wrapper });

      await waitFor(() => {
        expect(mockNavigate).toHaveBeenCalledWith('/login', { replace: true });
      });
    });
  });

  describe('signIn', () => {
    it('calls signIn API with credentials', async () => {
      const mockUser = { id: 'user-1', email: 'test@example.com' } as CurrentUser;
      vi.mocked(authApi.getCurrentUser).mockRejectedValue(new Error('Not authenticated'));
      vi.mocked(authApi.signIn).mockResolvedValue(mockUser);

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

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

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

      expect(authApi.signIn).toHaveBeenCalledWith({
        email: 'test@example.com',
        password: 'password123',
      });
    });

    it('sets user on successful sign in', async () => {
      const mockUser = { id: 'user-1', email: 'test@example.com' } as CurrentUser;
      vi.mocked(authApi.getCurrentUser).mockRejectedValue(new Error('Not authenticated'));
      vi.mocked(authApi.signIn).mockResolvedValue(mockUser);

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

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

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

      expect(result.current.user).toEqual(mockUser);
    });

    it('returns error on sign in failure', async () => {
      vi.mocked(authApi.getCurrentUser).mockRejectedValue(new Error('Not authenticated'));
      vi.mocked(authApi.signIn).mockRejectedValue(new Error('Invalid credentials'));

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

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

      let signInResult: { error: string | null } | undefined;
      await act(async () => {
        signInResult = await result.current.signIn('test@example.com', 'wrong');
      });

      expect(signInResult?.error).toBeTruthy();
    });

    it('sets loading during sign in request', async () => {
      vi.mocked(authApi.getCurrentUser).mockRejectedValue(new Error('Not authenticated'));
      vi.mocked(authApi.signIn).mockImplementation(
        () => new Promise((resolve) => setTimeout(() => resolve({ id: '1', email: 'test@example.com' } as CurrentUser), 100)),
      );

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

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

      let signInPromise: Promise<{ error: string | null }>;
      act(() => {
        signInPromise = result.current.signIn('test@example.com', 'password');
      });

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

      await act(async () => {
        await signInPromise;
      });

      expect(result.current.loading).toBe(false);
    });
  });

  describe('signOut', () => {
    it('calls signOut API', async () => {
      const mockUser = { id: 'user-1', email: 'test@example.com' };
      vi.mocked(authApi.getCurrentUser).mockResolvedValue(mockUser);
      vi.mocked(authApi.signOut).mockResolvedValue(undefined);

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

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

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

      expect(authApi.signOut).toHaveBeenCalledTimes(1);
    });

    it('clears user on successful sign out', async () => {
      const mockUser = { id: 'user-1', email: 'test@example.com' };
      vi.mocked(authApi.getCurrentUser).mockResolvedValue(mockUser);
      vi.mocked(authApi.signOut).mockResolvedValue(undefined);

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

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

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

      expect(result.current.user).toBeNull();
    });

    it('handles AuthExpiredError gracefully', async () => {
      const mockUser = { id: 'user-1', email: 'test@example.com' };
      vi.mocked(authApi.getCurrentUser).mockResolvedValue(mockUser);
      vi.mocked(authApi.signOut).mockRejectedValue(new AuthExpiredError());

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

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

      mockNavigate.mockClear();

      let signOutResult: { error: string | null } | undefined;
      await act(async () => {
        signOutResult = await result.current.signOut();
      });

      expect(signOutResult?.error).toBeNull();
      expect(mockNavigate).toHaveBeenCalledWith('/login', { replace: true });
    });
  });

  describe('isAuthenticated', () => {
    it('returns true when user is set', async () => {
      vi.mocked(authApi.getCurrentUser).mockResolvedValue({
        id: 'user-1',
        email: 'test@example.com',
      });

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

      await waitFor(() => {
        expect(result.current.isAuthenticated).toBe(true);
      });
    });

    it('returns false when user is null', async () => {
      vi.mocked(authApi.getCurrentUser).mockRejectedValue(new Error('Not authenticated'));

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

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

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

describe('useAuthModalWorkflow', () => {
  const mockSignIn = vi.fn().mockResolvedValue({ error: null });
  const mockSignUp = vi.fn().mockResolvedValue({ error: null });
  const mockOnClose = vi.fn();

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

  it('initializes with signin mode', () => {
    const { result } = renderHook(
      () => useAuthModalWorkflow({
        signIn: mockSignIn,
        signUp: mockSignUp,
        onClose: mockOnClose,
      }),
      { wrapper },
    );

    expect(result.current.state.mode).toBe('signin');
  });

  it('initializes with empty draft', () => {
    const { result } = renderHook(
      () => useAuthModalWorkflow({
        signIn: mockSignIn,
        signUp: mockSignUp,
        onClose: mockOnClose,
      }),
      { wrapper },
    );

    expect(result.current.state.draft.email).toBe('');
    expect(result.current.state.draft.password).toBe('');
    expect(result.current.state.draft.fullName).toBe('');
  });

  it('updates draft when updateDraft is called', () => {
    const { result } = renderHook(
      () => useAuthModalWorkflow({
        signIn: mockSignIn,
        signUp: mockSignUp,
        onClose: mockOnClose,
      }),
      { wrapper },
    );

    act(() => {
      result.current.actions.updateDraft({ email: 'new@example.com' });
    });

    expect(result.current.state.draft.email).toBe('new@example.com');
  });

  it('switches mode when switchMode is called', () => {
    const { result } = renderHook(
      () => useAuthModalWorkflow({
        signIn: mockSignIn,
        signUp: mockSignUp,
        onClose: mockOnClose,
      }),
      { wrapper },
    );

    act(() => {
      result.current.actions.switchMode('signup');
    });

    expect(result.current.state.mode).toBe('signup');
  });

  it('calls signIn with email and password on handleSignIn', async () => {
    const { result } = renderHook(
      () => useAuthModalWorkflow({
        signIn: mockSignIn,
        signUp: mockSignUp,
        onClose: mockOnClose,
      }),
      { wrapper },
    );

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

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

    expect(mockSignIn).toHaveBeenCalledWith('test@example.com', 'secret');
  });

  it('sets error when signIn fails', async () => {
    const failingSignIn = vi.fn().mockResolvedValue({ error: 'Invalid credentials' });

    const { result } = renderHook(
      () => useAuthModalWorkflow({
        signIn: failingSignIn,
        signUp: mockSignUp,
        onClose: mockOnClose,
      }),
      { wrapper },
    );

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

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

    expect(result.current.state.error).toBe('Invalid credentials');
  });

  it('sets loading during signIn', async () => {
    const slowSignIn = vi.fn().mockImplementation(
      () => new Promise((resolve) => setTimeout(() => resolve({ error: null }), 100)),
    );

    const { result } = renderHook(
      () => useAuthModalWorkflow({
        signIn: slowSignIn,
        signUp: mockSignUp,
        onClose: mockOnClose,
      }),
      { wrapper },
    );

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

    let signInPromise: Promise<void>;
    act(() => {
      signInPromise = result.current.actions.handleSignIn();
    });

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

    await act(async () => {
      await signInPromise;
    });

    expect(result.current.state.loading).toBe(false);
  });

  it('resets form on handleClose', () => {
    const { result } = renderHook(
      () => useAuthModalWorkflow({
        signIn: mockSignIn,
        signUp: mockSignUp,
        onClose: mockOnClose,
      }),
      { wrapper },
    );

    act(() => {
      result.current.actions.updateDraft({ email: 'test@example.com' });
      result.current.actions.switchMode('signup');
    });

    expect(result.current.state.draft.email).toBe('test@example.com');
    expect(result.current.state.mode).toBe('signup');

    act(() => {
      result.current.actions.handleClose();
    });

    expect(result.current.state.draft.email).toBe('');
    expect(result.current.state.mode).toBe('signin');
    expect(mockOnClose).toHaveBeenCalled();
  });
});
