/**
 * Auth Service Tests
 */

import { describe, it, expect, beforeEach, vi } from 'vitest'
import { login } from '../../services/auth'
import { getDb } from '../../db'
import { AppErrors } from '../../lib/errors'
import bcrypt from 'bcryptjs'
import jwt from 'jsonwebtoken'

// Mock dependencies
vi.mock('../../db', () => ({
  getDb: vi.fn(),
}))

vi.mock('bcryptjs', () => ({
  default: {
    compare: vi.fn(),
  },
}))

vi.mock('jsonwebtoken', () => ({
  default: {
    sign: vi.fn(),
  },
}))

vi.mock('../../lib/secret-validation', () => ({
  requireJWTSecret: vi.fn(() => 'test-secret'),
}))

describe('Auth Service', () => {
  let mockDb: { query: ReturnType<typeof vi.fn> }

  beforeEach(() => {
    vi.clearAllMocks()
    mockDb = {
      query: vi.fn(),
    }
    vi.mocked(getDb).mockReturnValue(mockDb as never)
  })

  describe('login', () => {
    it('should successfully login with valid credentials', async () => {
      const mockUser = {
        id: 'user-1',
        email: 'test@example.com',
        name: 'Test User',
        password_hash: 'hashed-password',
        role: 'USER',
        created_at: new Date(),
        updated_at: new Date(),
      }

      mockDb.query.mockResolvedValueOnce({
        rows: [mockUser],
      })

      vi.mocked(bcrypt.compare).mockResolvedValueOnce(true)
      vi.mocked(jwt.sign).mockReturnValueOnce('mock-token' as never)

      const result = await login('test@example.com', 'password123')

      expect(result.token).toBe('mock-token')
      expect(result.user.email).toBe('test@example.com')
      expect(result.user.name).toBe('Test User')
      expect(bcrypt.compare).toHaveBeenCalledWith('password123', 'hashed-password')
      expect(jwt.sign).toHaveBeenCalledWith(
        {
          id: 'user-1',
          email: 'test@example.com',
          name: 'Test User',
          role: 'USER',
        },
        'test-secret',
        { expiresIn: expect.any(String) }
      )
    })

    it('should throw unauthenticated error for invalid email', async () => {
      mockDb.query.mockResolvedValueOnce({
        rows: [],
      })

      await expect(login('invalid@example.com', 'password123')).rejects.toThrow()
      await expect(login('invalid@example.com', 'password123')).rejects.toMatchObject({
        code: 'UNAUTHENTICATED',
      })
    })

    it('should throw unauthenticated error for invalid password', async () => {
      const mockUser = {
        id: 'user-1',
        email: 'test@example.com',
        name: 'Test User',
        password_hash: 'hashed-password',
        role: 'USER',
        created_at: new Date(),
        updated_at: new Date(),
      }

      mockDb.query.mockResolvedValueOnce({
        rows: [mockUser],
      })

      vi.mocked(bcrypt.compare).mockResolvedValueOnce(false)

      await expect(login('test@example.com', 'wrong-password')).rejects.toThrow()
      await expect(login('test@example.com', 'wrong-password')).rejects.toMatchObject({
        code: 'UNAUTHENTICATED',
      })
    })
  })
})

