/**
 * Unit tests for lib/api/error.ts helpers.
 *
 * Focus: routeError must not leak raw error detail to the HTTP response,
 * and must log internally via the logger.
 */

import { describe, it, expect, vi, beforeEach } from 'vitest'

// Mock lib/logger before importing the module under test so the import-time
// singleton picks up the mock.
vi.mock('@/lib/logger', () => ({
  logger: {
    error: vi.fn(),
    warn:  vi.fn(),
    info:  vi.fn(),
    debug: vi.fn(),
    child: vi.fn(),
  },
}))

// NextResponse.json is available in Node via next/server — provide a minimal
// stub so tests run without a full Next.js runtime.
vi.mock('next/server', () => {
  return {
    NextResponse: {
      json: (body: unknown, init?: { status?: number }) => ({
        status: init?.status ?? 200,
        body,
      }),
    },
  }
})

import { sanitizeApiError, errorResponse, routeError, apiError } from '@/lib/api/error'
import { logger } from '@/lib/logger'

const mockLogger = logger as unknown as { error: ReturnType<typeof vi.fn>; warn: ReturnType<typeof vi.fn> }

beforeEach(() => {
  vi.clearAllMocks()
})

describe('sanitizeApiError', () => {
  it('returns generic message for known code', () => {
    const result = sanitizeApiError('internal_error')
    expect(result.message).toBe('A server error occurred. The incident has been logged.')
    expect(result.error).toBe('internal_error')
  })

  it('does not expose the raw detail in the returned object', () => {
    const raw = new Error('SELECT * FROM passwords WHERE 1=1')
    const result = sanitizeApiError('internal_error', raw)
    expect(JSON.stringify(result)).not.toContain('SELECT')
    expect(JSON.stringify(result)).not.toContain('passwords')
  })

  it('logs the detail via logger.error when detail is provided', () => {
    const raw = new Error('pg: duplicate key constraint "users_pkey"')
    sanitizeApiError('conflict', raw)
    expect(mockLogger.error).toHaveBeenCalledOnce()
  })

  it('does not call logger when no detail is provided', () => {
    sanitizeApiError('not_found')
    expect(mockLogger.error).not.toHaveBeenCalled()
  })
})

describe('routeError', () => {
  it('returns the caller-supplied publicMessage, not raw error detail', () => {
    const raw = new Error('relation "user_profiles" does not exist')
    const response = routeError('Benutzer konnte nicht angelegt werden.', raw)
    const body = (response as { body: unknown }).body as { message: string; error: string }
    expect(body.message).toBe('Benutzer konnte nicht angelegt werden.')
    expect(body.error).toBe('internal_error')
    expect(JSON.stringify(body)).not.toContain('relation')
    expect(JSON.stringify(body)).not.toContain('user_profiles')
  })

  it('logs the raw detail internally', () => {
    const raw = new Error('DB constraint violation details')
    routeError('Ein Fehler ist aufgetreten.', raw)
    expect(mockLogger.error).toHaveBeenCalledOnce()
    const [, loggedDetail] = (mockLogger.error as ReturnType<typeof vi.fn>).mock.calls[0]
    expect(loggedDetail).toBe(raw)
  })

  it('defaults to HTTP 500', () => {
    const response = routeError('Fehler.', new Error('detail'))
    expect((response as { status: number }).status).toBe(500)
  })

  it('accepts a custom status code', () => {
    const response = routeError('Nicht gefunden.', new Error('detail'), 503)
    expect((response as { status: number }).status).toBe(503)
  })

  it('works without a detail argument and does not call logger', () => {
    const response = routeError('Allgemeiner Fehler.')
    const body = (response as { body: unknown }).body as { message: string }
    expect(body.message).toBe('Allgemeiner Fehler.')
    expect(mockLogger.error).not.toHaveBeenCalled()
  })
})

describe('errorResponse', () => {
  it('uses the generic message for the code, not raw detail', () => {
    const raw = new Error('internal details')
    const response = errorResponse('internal_error', raw)
    const body = (response as { body: unknown }).body as { message: string }
    expect(body.message).toBe('A server error occurred. The incident has been logged.')
    expect(JSON.stringify(body)).not.toContain('internal details')
  })
})

describe('apiError shortcuts', () => {
  it('apiError.internalError produces 500 with generic message', () => {
    const response = apiError.internalError(new Error('db detail'))
    expect((response as { status: number }).status).toBe(500)
    const body = (response as { body: unknown }).body as { message: string }
    expect(JSON.stringify(body)).not.toContain('db detail')
  })
})
