import { renderHook, act } from '@testing-library/react';
import { vi, describe, it, expect, beforeEach } from 'vitest';

const {
  mockUnsubscribe,
  mockGetSession,
  mockOnAuthStateChange,
  mockSignInWithPassword,
  mockSignOut,
} = vi.hoisted(() => ({
  mockUnsubscribe: vi.fn(),
  mockGetSession: vi.fn(),
  mockOnAuthStateChange: vi.fn(() => ({
    data: { subscription: { unsubscribe: vi.fn() } },
  })),
  mockSignInWithPassword: vi.fn(),
  mockSignOut: vi.fn(),
}));

vi.mock('../../../lib/supabase', () => ({
  supabase: {
    auth: {
      getSession: mockGetSession,
      onAuthStateChange: mockOnAuthStateChange,
      signInWithPassword: mockSignInWithPassword,
      signOut: mockSignOut,
    },
  },
}));

import { useAuth } from '../useAuth';

describe('useAuth', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockOnAuthStateChange.mockReturnValue({
      data: { subscription: { unsubscribe: mockUnsubscribe } },
    });
  });

  it('starts with loading=true and session=null', () => {
    mockGetSession.mockReturnValue(new Promise(() => {}));
    const { result } = renderHook(() => useAuth());
    expect(result.current.loading).toBe(true);
    expect(result.current.session).toBe(null);
  });

  it('sets loading=false and session after getSession resolves', async () => {
    const fakeSession = { user: { email: 'admin@test.com' } };
    mockGetSession.mockResolvedValue({ data: { session: fakeSession } });
    const { result } = renderHook(() => useAuth());
    await act(async () => {});
    expect(result.current.loading).toBe(false);
    expect(result.current.session).toEqual(fakeSession);
  });

  it('login calls signInWithPassword with email and password', async () => {
    mockGetSession.mockResolvedValue({ data: { session: null } });
    mockSignInWithPassword.mockResolvedValue({ error: null });
    const { result } = renderHook(() => useAuth());
    await act(async () => {
      await result.current.login('admin@test.com', 'secret');
    });
    expect(mockSignInWithPassword).toHaveBeenCalledWith({
      email: 'admin@test.com',
      password: 'secret',
    });
  });

  it('login returns error when credentials are wrong', async () => {
    mockGetSession.mockResolvedValue({ data: { session: null } });
    const fakeError = new Error('Invalid credentials');
    mockSignInWithPassword.mockResolvedValue({ error: fakeError });
    const { result } = renderHook(() => useAuth());
    let loginResult;
    await act(async () => {
      loginResult = await result.current.login('bad@test.com', 'wrong');
    });
    expect(loginResult.error).toBe(fakeError);
  });

  it('logout calls signOut', async () => {
    mockGetSession.mockResolvedValue({ data: { session: null } });
    mockSignOut.mockResolvedValue({});
    const { result } = renderHook(() => useAuth());
    await act(async () => {
      await result.current.logout();
    });
    expect(mockSignOut).toHaveBeenCalled();
  });

  it('unsubscribes on unmount', async () => {
    mockGetSession.mockResolvedValue({ data: { session: null } });
    const { unmount } = renderHook(() => useAuth());
    await act(async () => {});
    unmount();
    expect(mockUnsubscribe).toHaveBeenCalled();
  });

  it('updates session when onAuthStateChange fires', async () => {
    let authCallback;
    mockOnAuthStateChange.mockImplementation((cb) => {
      authCallback = cb;
      return { data: { subscription: { unsubscribe: mockUnsubscribe } } };
    });
    const newSession = { user: { email: 'other@test.com' } };
    mockGetSession.mockResolvedValue({ data: { session: null } });
    const { result } = renderHook(() => useAuth());
    await act(async () => {});
    act(() => {
      authCallback('SIGNED_IN', newSession);
    });
    expect(result.current.session).toEqual(newSession);
  });
});
