import http from 'node:http'
import type { AddressInfo } from 'node:net'
import {
  providerRequest,
  sanitizeProviderErrorBody,
} from '../providers/http'

describe('providerRequest', () => {
  test('retorna resposta de sucesso via mock server', async () => {
    const server = routeServer((_req, res) => {
      res.statusCode = 200
      res.end(JSON.stringify({ ok: true }))
    })

    try {
      const port = await listen(server)
      const response = await providerRequest<{ ok: boolean }>({
        operation: 'mock.success',
        method: 'GET',
        url: `http://127.0.0.1:${port}/ok`,
      })

      expect(response.data).toEqual({ ok: true })
    } finally {
      await close(server)
    }
  })

  test('classifica 401, 404 e redige tokens do body', async () => {
    const server = routeServer((req, res) => {
      if (req.url === '/auth') {
        res.statusCode = 401
        res.end('token ghp_secret000000000000000000000')
        return
      }
      res.statusCode = 404
      res.end('missing')
    })

    try {
      const port = await listen(server)
      await expect(providerRequest({
        operation: 'mock.auth',
        method: 'GET',
        url: `http://127.0.0.1:${port}/auth`,
        maxAttempts: 1,
      })).rejects.toThrow(/HTTP 401 \(auth_forbidden\).*ghp_\[REDACTED\]/)

      await expect(providerRequest({
        operation: 'mock.not_found',
        method: 'GET',
        url: `http://127.0.0.1:${port}/missing`,
        maxAttempts: 1,
      })).rejects.toThrow(/HTTP 404 \(not_found\)/)
    } finally {
      await close(server)
    }
  })

  test('retry limitado cobre 429 e 5xx somente em método seguro', async () => {
    const counts = new Map<string, number>()
    const server = routeServer((req, res) => {
      const key = req.url ?? '/'
      const count = (counts.get(key) ?? 0) + 1
      counts.set(key, count)

      if (key === '/rate' && count === 1) {
        res.statusCode = 429
        res.setHeader('Retry-After', '0')
        res.end('rate limited')
        return
      }
      if (key === '/server' && count === 1) {
        res.statusCode = 502
        res.end('bad gateway')
        return
      }
      if (key === '/post') {
        res.statusCode = 502
        res.end('unsafe')
        return
      }

      res.statusCode = 200
      res.end(JSON.stringify({ count }))
    })

    try {
      const port = await listen(server)
      await expect(providerRequest({
        operation: 'mock.rate',
        method: 'GET',
        url: `http://127.0.0.1:${port}/rate`,
        retryDelayMs: 0,
      })).resolves.toHaveProperty('data.count', 2)

      await expect(providerRequest({
        operation: 'mock.server',
        method: 'GET',
        url: `http://127.0.0.1:${port}/server`,
        retryDelayMs: 0,
      })).resolves.toHaveProperty('data.count', 2)

      await expect(providerRequest({
        operation: 'mock.post',
        method: 'POST',
        url: `http://127.0.0.1:${port}/post`,
        data: { unsafe: true },
        retryDelayMs: 0,
      })).rejects.toThrow(/HTTP 502 \(server_error\)/)
      expect(counts.get('/post')).toBe(1)
    } finally {
      await close(server)
    }
  })

  test('timeout é classificado e pode ser redigido', async () => {
    const server = routeServer((_req, _res) => {
      // Mantém a conexão aberta até o timeout do cliente.
    })

    try {
      const port = await listen(server)
      await expect(providerRequest({
        operation: 'mock.timeout',
        method: 'GET',
        url: `http://127.0.0.1:${port}/slow`,
        timeoutMs: 20,
        maxAttempts: 1,
      })).rejects.toThrow(/timeout_or_connect/)
    } finally {
      await close(server)
    }

    expect(sanitizeProviderErrorBody('Bearer abc123 e Basic deadbeef')).toBe(
      'Bearer [REDACTED] e Basic [REDACTED]',
    )
  })
})

function routeServer(
  handler: (req: http.IncomingMessage, res: http.ServerResponse) => void,
): http.Server {
  return http.createServer(handler)
}

function listen(server: http.Server): Promise<number> {
  return new Promise((resolve) => {
    server.listen(0, () => resolve((server.address() as AddressInfo).port))
  })
}

function close(server: http.Server): Promise<void> {
  return new Promise((resolve, reject) => {
    server.close((err) => {
      if (err) reject(err)
      else resolve()
    })
  })
}
