import {describe, expect, test} from 'bun:test'

import {ApiError, cleanError, isApiStatus, unwrap} from '@/lib/errors'

const okResponse = <T>(json: T) => ({
  ok: true,
  status: 200,
  text: () => Promise.resolve(JSON.stringify(json)),
  json: () => Promise.resolve(json),
})

const errResponse = (status: number, body: string) => ({
  ok: false as const,
  status,
  text: () => Promise.resolve(body),
  json: () => Promise.resolve(JSON.parse(body)),
})

describe('unwrap', () => {
  test('resolves the ok branch to its json', async () => {
    expect(await unwrap(okResponse({a: 1}))).toEqual({a: 1})
  })

  test('reads the error code from a json body', async () => {
    expect(
      unwrap(errResponse(403, '{"error":"not_friends"}')),
    ).rejects.toMatchObject({status: 403, code: 'not_friends'})
  })

  test('falls back to the raw body as the code', async () => {
    expect(unwrap(errResponse(500, 'boom'))).rejects.toMatchObject({
      status: 500,
      code: 'boom',
    })
  })

  test('tolerates an empty error body', async () => {
    expect(unwrap(errResponse(502, ''))).rejects.toMatchObject({
      status: 502,
      code: undefined,
    })
  })
})

describe('isApiStatus', () => {
  test('matches an ApiError with any of the given statuses', () => {
    expect(isApiStatus(new ApiError(404), 404)).toBe(true)
    expect(isApiStatus(new ApiError(409), 404, 409)).toBe(true)
  })

  test('rejects other statuses and non-api errors', () => {
    expect(isApiStatus(new ApiError(500), 404)).toBe(false)
    expect(isApiStatus(new TypeError('network'), 404)).toBe(false)
    expect(isApiStatus(undefined, 404)).toBe(false)
  })
})

describe('cleanError', () => {
  test('maps known codes to friendly copy', () => {
    expect(cleanError(new ApiError(403, 'not_friends'))).toBe(
      'Only friends can see this.',
    )
  })

  test('detects network failures', () => {
    expect(
      cleanError(new TypeError('Network request failed')),
    ).toMatch(/offline/)
  })

  test('falls back to generic copy for unknown errors', () => {
    expect(cleanError(new ApiError(500, 'mystery_code'))).toBe(
      'Something went wrong. Try again.',
    )
    expect(cleanError(new Error('x'), 'Custom fallback')).toBe(
      'Custom fallback',
    )
  })
})
