import { describe, it, expect, vi, beforeEach } from 'vitest'
import { get } from 'svelte/store'
import { auth } from './auth.js'

const mockGetSession = vi.fn()
const mockOnAuthStateChange = vi.fn()
const mockSignInWithPassword = vi.fn()
const mockSignUp = vi.fn()
const mockSignOut = vi.fn()

vi.mock('../supabase.js', () => ({
  supabase: {
    auth: {
      getSession: (...args) => mockGetSession(...args),
      onAuthStateChange: (...args) => mockOnAuthStateChange(...args),
      signInWithPassword: (...args) => mockSignInWithPassword(...args),
      signUp: (...args) => mockSignUp(...args),
      signOut: (...args) => mockSignOut(...args),
    },
  },
}))

const mockGetEmailForUsername = vi.fn()
const mockUpdateProfile = vi.fn()
vi.mock('../api/profile.js', () => ({
  getEmailForUsername: (...args) => mockGetEmailForUsername(...args),
  updateProfile: (...args) => mockUpdateProfile(...args),
}))

describe('auth store', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockGetSession.mockResolvedValue({ data: { session: null } })
    mockSignOut.mockResolvedValue(undefined)
  })

  it('has initial state with loading true', () => {
    expect(get(auth)).toMatchObject({ user: null, loading: true, error: null })
  })

  it('clearError sets error to null', async () => {
    auth.clearError()
    expect(get(auth).error).toBe(null)
  })

  it('init sets session from getSession and registers onAuthStateChange', async () => {
    const user = { id: 'u1', email: 'a@b.com' }
    mockGetSession.mockResolvedValue({ data: { session: { user } } })
    await auth.init()
    expect(get(auth)).toMatchObject({ user, loading: false, error: null })
    expect(mockOnAuthStateChange).toHaveBeenCalled()
  })

  it('init sets error when getSession throws', async () => {
    mockGetSession.mockRejectedValue(new Error('Network error'))
    await auth.init()
    expect(get(auth)).toMatchObject({ user: null, loading: false, error: 'Network error' })
  })

  it('login with empty email sets error and returns false', async () => {
    const result = await auth.login('', 'pass')
    expect(result).toBe(false)
    expect(get(auth).error).toBe('Enter your email or username.')
  })

  it('login with username resolves email and signs in', async () => {
    mockGetEmailForUsername.mockResolvedValue('user@example.com')
    mockSignInWithPassword.mockResolvedValue({ error: null })
    const result = await auth.login('username', 'pass')
    expect(mockGetEmailForUsername).toHaveBeenCalledWith('username')
    expect(mockSignInWithPassword).toHaveBeenCalledWith({
      email: 'user@example.com',
      password: 'pass',
    })
    expect(result).toBe(true)
    expect(get(auth).error).toBe(null)
  })

  it('login with unknown username sets error and returns false', async () => {
    mockGetEmailForUsername.mockResolvedValue(null)
    const result = await auth.login('unknown', 'pass')
    expect(result).toBe(false)
    expect(get(auth).error).toBe('No account with that username.')
  })

  it('login with email signs in directly', async () => {
    mockSignInWithPassword.mockResolvedValue({ error: null })
    const result = await auth.login('a@b.com', 'pass')
    expect(mockGetEmailForUsername).not.toHaveBeenCalled()
    expect(mockSignInWithPassword).toHaveBeenCalledWith({ email: 'a@b.com', password: 'pass' })
    expect(result).toBe(true)
  })

  it('login with signIn error sets error and returns false', async () => {
    mockSignInWithPassword.mockResolvedValue({ error: { message: 'Invalid login' } })
    const result = await auth.login('a@b.com', 'wrong')
    expect(result).toBe(false)
    expect(get(auth).error).toBe('Invalid login')
  })

  it('register success returns success and data', async () => {
    const user = { id: 'u1' }
    const session = {}
    mockSignUp.mockResolvedValue({ data: { user, session }, error: null })
    const result = await auth.register('a@b.com', 'pass', 'Alice')
    expect(result).toEqual({ success: true, needsConfirmation: false, data: { user, session } })
    expect(get(auth).error).toBe(null)
  })

  it('register with displayName calls updateProfile', async () => {
    const user = { id: 'u1' }
    mockSignUp.mockResolvedValue({ data: { user, session: {} }, error: null })
    mockUpdateProfile.mockResolvedValue(undefined)
    await auth.register('a@b.com', 'pass', 'Alice')
    expect(mockUpdateProfile).toHaveBeenCalledWith('u1', { display_name: 'Alice' })
  })

  it('register with signUp error returns success false', async () => {
    mockSignUp.mockResolvedValue({ data: null, error: { message: 'Email taken' } })
    const result = await auth.register('a@b.com', 'pass', '')
    expect(result).toEqual({ success: false })
    expect(get(auth).error).toBe('Email taken')
  })

  it('logout calls signOut and clears user', async () => {
    await auth.logout()
    expect(mockSignOut).toHaveBeenCalled()
    expect(get(auth)).toMatchObject({ user: null, loading: false, error: null })
  })
})
