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

import { AppError, ErrorType, isAppError } from './AppError'
import { getErrorMessage, isFormValidationError } from './index'

describe('AppError', () => {
  it('uses the default status and type when options are omitted', () => {
    const error = new AppError(1001, 'business failed')

    expect(error).toBeInstanceOf(AppError)
    expect(error).toBeInstanceOf(Error)
    expect(error.name).toBe('AppError')
    expect(error.code).toBe(1001)
    expect(error.status).toBe(500)
    expect(error.type).toBe(ErrorType.BUSINESS)
    expect(error.cause).toBeUndefined()
  })

  it('keeps explicit options including cause and exposes type guards', () => {
    const cause = new Error('root cause')
    const authByStatus = new AppError(2001, 'status-auth', {
      status: 401,
      type: ErrorType.BUSINESS,
      cause,
    })
    const forbiddenByStatus = new AppError(2002, 'status-forbidden', {
      status: 403,
      type: ErrorType.BUSINESS,
    })
    const networkError = AppError.network('network failed', cause)

    expect(authByStatus.cause).toBe(cause)
    expect(authByStatus.isAuthError()).toBe(true)
    expect(forbiddenByStatus.isForbidden()).toBe(true)
    expect(networkError.isNetworkError()).toBe(true)
  })

  it('maps backend responses to the expected error type for each status family', () => {
    const unauthorized = AppError.fromResponse({ code: 40101, message: 'unauthorized' }, 401)
    const forbidden = AppError.fromResponse({ code: 40301, message: 'forbidden' }, 403)
    const notFound = AppError.fromResponse({ code: 40401, message: 'missing' }, 404)
    const network = AppError.fromResponse({ code: 50001, message: 'server error' }, 500)
    const business = AppError.fromResponse({ code: 40001, message: 'business error' }, 400)

    expect(unauthorized.type).toBe(ErrorType.AUTH)
    expect(forbidden.type).toBe(ErrorType.FORBIDDEN)
    expect(notFound.type).toBe(ErrorType.NOT_FOUND)
    expect(network.type).toBe(ErrorType.NETWORK)
    expect(business.type).toBe(ErrorType.BUSINESS)
  })

  it('creates auth, forbidden, and validation errors with the expected defaults', () => {
    const auth = AppError.auth()
    const forbidden = AppError.forbidden()
    const validation = AppError.validation('validation failed')

    expect(auth.code).toBe(401)
    expect(auth.status).toBe(401)
    expect(auth.type).toBe(ErrorType.AUTH)
    expect(auth.message.length).toBeGreaterThan(0)

    expect(forbidden.code).toBe(403)
    expect(forbidden.status).toBe(403)
    expect(forbidden.type).toBe(ErrorType.FORBIDDEN)
    expect(forbidden.message.length).toBeGreaterThan(0)

    expect(validation.code).toBe(400)
    expect(validation.status).toBe(400)
    expect(validation.type).toBe(ErrorType.VALIDATION)
    expect(validation.message).toBe('validation failed')
  })

  it('returns user-facing messages for each supported error type', () => {
    const networkMessage = AppError.network('network failed').getUserMessage()
    const authMessage = AppError.auth('custom auth').getUserMessage()
    const forbiddenMessage = AppError.forbidden('custom forbidden').getUserMessage()
    const notFoundMessage = new AppError(40401, 'missing', {
      status: 404,
      type: ErrorType.NOT_FOUND,
    }).getUserMessage()
    const validationMessage = AppError.validation('validation failed').getUserMessage()
    const customUnknownMessage = new AppError(9001, 'custom unknown', {
      type: ErrorType.UNKNOWN,
    }).getUserMessage()
    const fallbackUnknownMessage = new AppError(9002, '', {
      type: ErrorType.UNKNOWN,
    }).getUserMessage()

    expect(networkMessage.length).toBeGreaterThan(0)
    expect(networkMessage).not.toBe('network failed')
    expect(authMessage.length).toBeGreaterThan(0)
    expect(authMessage).not.toBe('custom auth')
    expect(forbiddenMessage.length).toBeGreaterThan(0)
    expect(forbiddenMessage).not.toBe('custom forbidden')
    expect(notFoundMessage.length).toBeGreaterThan(0)
    expect(notFoundMessage).not.toBe('missing')
    expect(validationMessage).toBe('validation failed')
    expect(customUnknownMessage).toBe('custom unknown')
    expect(fallbackUnknownMessage.length).toBeGreaterThan(0)
  })

  it('identifies AppError instances correctly', () => {
    expect(isAppError(new AppError(1, 'boom'))).toBe(true)
    expect(isAppError(new Error('boom'))).toBe(false)
    expect(isAppError('boom')).toBe(false)
  })
})

describe('error helpers', () => {
  it('uses the AppError user message when available', () => {
    const error = AppError.validation('invalid form')

    expect(getErrorMessage(error, 'fallback')).toBe('invalid form')
  })

  it('falls back to generic Error messages and finally to the provided fallback', () => {
    expect(getErrorMessage(new Error('plain error'), 'fallback')).toBe('plain error')
    expect(getErrorMessage({ foo: 'bar' }, 'fallback')).toBe('fallback')
  })

  it('detects form validation errors only for objects with an errorFields array', () => {
    expect(isFormValidationError({ errorFields: [] })).toBe(true)
    expect(isFormValidationError({ errorFields: 'nope' })).toBe(false)
    expect(isFormValidationError(null)).toBe(false)
  })
})
