import { renderHook, act, waitFor } from '@testing-library/react'
import { vi, beforeEach, describe, it, expect } from 'vitest'
import { useAuth } from '../src/hooks/useAuth'
import * as sb from '../src/lib/supabase'

function makeAuth(opts: { sessionUser?: any } = {}) {
  let cb: any = null
  const unsubscribe = vi.fn()
  const auth = {
    getSession: vi.fn(() =>
      Promise.resolve({ data: { session: opts.sessionUser ? { user: opts.sessionUser } : null } })
    ),
    onAuthStateChange: vi.fn((fn: any) => {
      cb = fn
      return { data: { subscription: { unsubscribe } } }
    }),
    signUp: vi.fn(() => Promise.resolve({ data: { user: { id: 'u1', email: 'a@b.c' } }, error: null })),
    signInWithPassword: vi.fn(() => Promise.resolve({ data: { user: { id: 'u1', email: 'a@b.c' } }, error: null })),
    signOut: vi.fn(() => Promise.resolve({ error: null })),
    resetPasswordForEmail: vi.fn(() => Promise.resolve({ data: {}, error: null })),
  }
  return { auth, emit: (event: string, session: any) => cb && cb(event, session), unsubscribe }
}

beforeEach(() => vi.restoreAllMocks())

describe('useAuth', () => {
  it('reports not-configured: signIn returns error, not authenticated', async () => {
    vi.spyOn(sb, 'getSupabase').mockReturnValue(null as any)
    const { result } = renderHook(() => useAuth())
    await waitFor(() => expect(result.current.loading).toBe(false))
    expect(result.current.isAuthenticated).toBe(false)
    let ok = true
    await act(async () => { ok = await result.current.signIn('a@b.c', 'pw') })
    expect(ok).toBe(false)
    expect(result.current.error).toBeTruthy()
  })

  it('signIn calls signInWithPassword with email/password', async () => {
    const { auth } = makeAuth()
    vi.spyOn(sb, 'getSupabase').mockReturnValue({ auth } as any)
    const { result } = renderHook(() => useAuth())
    await waitFor(() => expect(result.current.loading).toBe(false))
    await act(async () => { await result.current.signIn('a@b.c', 'pw') })
    expect(auth.signInWithPassword).toHaveBeenCalledWith({ email: 'a@b.c', password: 'pw' })
  })

  it('signUp calls signUp with email/password', async () => {
    const { auth } = makeAuth()
    vi.spyOn(sb, 'getSupabase').mockReturnValue({ auth } as any)
    const { result } = renderHook(() => useAuth())
    await waitFor(() => expect(result.current.loading).toBe(false))
    await act(async () => { await result.current.signUp('a@b.c', 'pw') })
    expect(auth.signUp).toHaveBeenCalledWith({ email: 'a@b.c', password: 'pw' })
  })

  it('resetPassword calls resetPasswordForEmail', async () => {
    const { auth } = makeAuth()
    vi.spyOn(sb, 'getSupabase').mockReturnValue({ auth } as any)
    const { result } = renderHook(() => useAuth())
    await waitFor(() => expect(result.current.loading).toBe(false))
    await act(async () => { await result.current.resetPassword('a@b.c') })
    expect(auth.resetPasswordForEmail).toHaveBeenCalledWith('a@b.c')
  })

  it('onAuthStateChange session maps Supabase user to {id,email} and authenticates', async () => {
    const { auth, emit } = makeAuth()
    vi.spyOn(sb, 'getSupabase').mockReturnValue({ auth } as any)
    const { result } = renderHook(() => useAuth())
    await waitFor(() => expect(result.current.loading).toBe(false))
    act(() => { emit('SIGNED_IN', { user: { id: 'u2', email: 'x@y.z' } }) })
    await waitFor(() => expect(result.current.isAuthenticated).toBe(true))
    expect(result.current.user).toEqual({ id: 'u2', email: 'x@y.z' })
  })

  it('loads an existing session on mount', async () => {
    const { auth } = makeAuth({ sessionUser: { id: 'u3', email: 'm@n.o' } })
    vi.spyOn(sb, 'getSupabase').mockReturnValue({ auth } as any)
    const { result } = renderHook(() => useAuth())
    await waitFor(() => expect(result.current.isAuthenticated).toBe(true))
    expect(result.current.user).toEqual({ id: 'u3', email: 'm@n.o' })
  })

  it('unsubscribes on unmount', async () => {
    const { auth, unsubscribe } = makeAuth()
    vi.spyOn(sb, 'getSupabase').mockReturnValue({ auth } as any)
    const { result, unmount } = renderHook(() => useAuth())
    await waitFor(() => expect(result.current.loading).toBe(false))
    unmount()
    expect(unsubscribe).toHaveBeenCalled()
  })

  it('clearError resets the error', async () => {
    vi.spyOn(sb, 'getSupabase').mockReturnValue(null as any)
    const { result } = renderHook(() => useAuth())
    await waitFor(() => expect(result.current.loading).toBe(false))
    await act(async () => { await result.current.signIn('a@b.c', 'pw') })
    expect(result.current.error).toBeTruthy()
    act(() => { result.current.clearError() })
    expect(result.current.error).toBeNull()
  })

  it('signOut calls supabase signOut', async () => {
    const { auth } = makeAuth({ sessionUser: { id: 'u3', email: 'm@n.o' } })
    vi.spyOn(sb, 'getSupabase').mockReturnValue({ auth } as any)
    const { result } = renderHook(() => useAuth())
    await waitFor(() => expect(result.current.isAuthenticated).toBe(true))
    let ok = false
    await act(async () => { ok = await result.current.signOut() })
    expect(auth.signOut).toHaveBeenCalled()
    expect(ok).toBe(true)
  })
})
