/**
 * Unit tests for authentication service
 * 
 * This file demonstrates testing patterns for service functions in the API.
 * See docs/TEST_EXAMPLES.md for more examples.
 */

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

// Mock dependencies
vi.mock('../db')
vi.mock('../lib/errors')
vi.mock('bcryptjs')
vi.mock('jsonwebtoken')
vi.mock('../lib/secret-validation', () => ({
  requireJWTSecret: () => 'test-secret'
}))

describe('auth service', () => {
  let mockDb: any

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

  describe('login', () => {
    it('should authenticate valid user and return token', async () => {
      // Arrange
      const mockUser = {
        id: '1',
        email: 'user@example.com',
        name: 'Test User',
        password_hash: '$2a$10$hashed',
        role: 'USER',
        created_at: new Date('2024-01-01'),
        updated_at: new Date('2024-01-01'),
      }

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

      vi.mocked(bcrypt.compare).mockResolvedValue(true as never)
      vi.mocked(jwt.sign).mockReturnValue('mock-jwt-token' as any)

      // Act
      const result = await login('user@example.com', 'password123')

      // Assert
      expect(result).toHaveProperty('token')
      expect(result.token).toBe('mock-jwt-token')
      expect(result.user.email).toBe('user@example.com')
      expect(result.user.name).toBe('Test User')
      expect(result.user.role).toBe('USER')
      
      expect(mockDb.query).toHaveBeenCalledWith(
        expect.stringContaining('SELECT'),
        ['user@example.com']
      )
      expect(bcrypt.compare).toHaveBeenCalledWith('password123', mockUser.password_hash)
      expect(jwt.sign).toHaveBeenCalled()
    })

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

      // Act & Assert
      await expect(login('invalid@example.com', 'password123')).rejects.toThrow()
      
      expect(bcrypt.compare).not.toHaveBeenCalled()
      expect(jwt.sign).not.toHaveBeenCalled()
    })

    it('should throw error for invalid password', async () => {
      // Arrange
      const mockUser = {
        id: '1',
        email: 'user@example.com',
        name: 'Test User',
        password_hash: '$2a$10$hashed',
        role: 'USER',
        created_at: new Date('2024-01-01'),
        updated_at: new Date('2024-01-01'),
      }

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

      vi.mocked(bcrypt.compare).mockResolvedValue(false as never)

      // Act & Assert
      await expect(login('user@example.com', 'wrongpassword')).rejects.toThrow()
      
      expect(bcrypt.compare).toHaveBeenCalledWith('wrongpassword', mockUser.password_hash)
      expect(jwt.sign).not.toHaveBeenCalled()
    })

    it('should include user role in JWT token', async () => {
      // Arrange
      const mockUser = {
        id: '1',
        email: 'admin@example.com',
        name: 'Admin User',
        password_hash: '$2a$10$hashed',
        role: 'ADMIN',
        created_at: new Date('2024-01-01'),
        updated_at: new Date('2024-01-01'),
      }

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

      vi.mocked(bcrypt.compare).mockResolvedValue(true as never)
      vi.mocked(jwt.sign).mockReturnValue('mock-jwt-token' as any)

      // Act
      await login('admin@example.com', 'password123')

      // Assert
      expect(jwt.sign).toHaveBeenCalledWith(
        expect.objectContaining({
          id: '1',
          email: 'admin@example.com',
          name: 'Admin User',
          role: 'ADMIN',
        }),
        'test-secret',
        expect.objectContaining({
          expiresIn: expect.any(String)
        })
      )
    })
  })
})

