import { createNext } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { fetchViaHTTP } from 'next-test-utils'
const usuallySkip = process.env.RUN_SKIPPED_TESTS ? it : it.skip

describe('Middleware fetches with body', () => {
  let next: NextInstance

  beforeAll(async () => {
    next = await createNext({
      files: {
        'pages/api/default.js': `
          export default (req, res) => res.json({ body: req.body })
        `,
        'pages/api/size_limit_5kb.js': `
          export const config = { api: { bodyParser: { sizeLimit: '5kb' } } }
          export default (req, res) => res.json({ body: req.body })
        `,
        'pages/api/size_limit_5mb.js': `
          export const config = { api: { bodyParser: { sizeLimit: '5mb' } } }
          export default (req, res) => res.json({ body: req.body })
        `,
        'pages/api/body_parser_false.js': `
          export const config = { api: { bodyParser: false } }

          async function buffer(readable) {
            const chunks = []
            for await (const chunk of readable) {
              chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk)
            }
            return Buffer.concat(chunks)
          }

          export default async (req, res) => {
            const buf = await buffer(req)
            const rawBody = buf.toString('utf8');

            res.json({ rawBody, body: req.body })
          }
        `,
        'middleware.js': `
          import { NextResponse } from 'next/server';

          export default async (req) => NextResponse.next();
        `,
      },
      dependencies: {},
    })
  })
  afterAll(() => next.destroy())

  describe('with default bodyParser sizeLimit (1mb)', () => {
    it('should return 413 for body greater than 1mb', async () => {
      const bodySize = 1024 * 1024 + 1
      const body = 'r'.repeat(bodySize)

      const res = await fetchViaHTTP(
        next.url,
        '/api/default',
        {},
        {
          body,
          method: 'POST',
        },
      )

      expect(res.status).toBe(413)

      if (!(global as any).isNextDeploy) {
        expect(res.statusText).toBe('Body exceeded 1mb limit')
      }
    })

    it('should be able to send and return body size equal to 1mb', async () => {
      const bodySize = 1024 * 1024
      const body = 'B1C2D3E4F5G6H7I8J9K0LaMbNcOdPeQf'.repeat(bodySize / 32)

      const res = await fetchViaHTTP(
        next.url,
        '/api/default',
        {},
        {
          body,
          method: 'POST',
        },
      )
      const data = await res.json()

      expect(res.status).toBe(200)
      expect(data.body.length).toBe(bodySize)
      expect(data.body.split('B1C2D3E4F5G6H7I8J9K0LaMbNcOdPeQf').length).toBe(bodySize / 32 + 1)
    })

    it('should be able to send and return body greater than default highWaterMark (16KiB)', async () => {
      const bodySize = 16 * 1024 + 1
      const body = 'CD1E2F3G4H5I6J7K8L9M0NaObPcQdReS'.repeat(bodySize / 32) + 'C'

      const res = await fetchViaHTTP(
        next.url,
        '/api/default',
        {},
        {
          body,
          method: 'POST',
        },
      )
      const data = await res.json()

      expect(res.status).toBe(200)
      expect(data.body.length).toBe(bodySize)
      expect(data.body.split('CD1E2F3G4H5I6J7K8L9M0NaObPcQdReS').length).toBe(512 + 1)
    })
  })

  describe('with custom bodyParser sizeLimit (5kb)', () => {
    it('should return 413 for body greater than 5kb', async () => {
      const bodySize = 5 * 1024 + 1
      const body = 's'.repeat(bodySize)

      const res = await fetchViaHTTP(
        next.url,
        '/api/size_limit_5kb',
        {},
        {
          body,
          method: 'POST',
        },
      )

      expect(res.status).toBe(413)

      if (!(global as any).isNextDeploy) {
        expect(res.statusText).toBe('Body exceeded 5kb limit')
      }
    })

    it('should be able to send and return body size equal to 5kb', async () => {
      const bodySize = 5120
      const body = 'DEF1G2H3I4J5K6L7M8N9O0PaQbRcSdTe'.repeat(bodySize / 32)

      const res = await fetchViaHTTP(
        next.url,
        '/api/size_limit_5kb',
        {},
        {
          body,
          method: 'POST',
        },
      )
      const data = await res.json()

      expect(res.status).toBe(200)
      expect(data.body.length).toBe(bodySize)
      expect(data.body.split('DEF1G2H3I4J5K6L7M8N9O0PaQbRcSdTe').length).toBe(bodySize / 32 + 1)
    })
  })

  describe('with custom bodyParser sizeLimit (5mb)', () => {
    // NTL: disabled because of EF bug
    usuallySkip('should return 413 for body equal to 10mb', async () => {
      const bodySize = 10 * 1024 * 1024
      const body = 't'.repeat(bodySize)

      const res = await fetchViaHTTP(
        next.url,
        '/api/size_limit_5mb',
        {},
        {
          body,
          method: 'POST',
        },
      )

      expect(res.status).toBe(413)

      if (!(global as any).isNextDeploy) {
        expect(res.statusText).toBe('Body exceeded 5mb limit')
      }
    })

    it('should return 413 for body greater than 5mb', async () => {
      const bodySize = 5 * 1024 * 1024 + 1
      const body = 'u'.repeat(bodySize)

      const res = await fetchViaHTTP(
        next.url,
        '/api/size_limit_5mb',
        {},
        {
          body,
          method: 'POST',
        },
      )

      expect(res.status).toBe(413)

      if (!(global as any).isNextDeploy) {
        expect(res.statusText).toBe('Body exceeded 5mb limit')
      }
    })

    if (!(global as any).isNextDeploy) {
      it('should be able to send and return body size equal to 5mb', async () => {
        const bodySize = 5 * 1024 * 1024
        const body = 'FGHI1J2K3L4M5N6O7P8Q9R0SaTbUcVdW'.repeat(bodySize / 32)

        const res = await fetchViaHTTP(
          next.url,
          '/api/size_limit_5mb',
          {},
          {
            body,
            method: 'POST',
          },
        )
        const data = await res.json()

        expect(res.status).toBe(200)
        expect(data.body.length).toBe(bodySize)
        expect(data.body.split('FGHI1J2K3L4M5N6O7P8Q9R0SaTbUcVdW').length).toBe(bodySize / 32 + 1)
      })
    }
  })

  describe('with bodyParser = false', () => {
    it('should be able to send and return with body size equal to 16KiB', async () => {
      const bodySize = 16 * 1024
      const body = 'HIJK1L2M3N4O5P6Q7R8S9T0UaVbWcXdY'.repeat(bodySize / 32)

      const res = await fetchViaHTTP(
        next.url,
        '/api/body_parser_false',
        {},
        {
          body,
          method: 'POST',
        },
      )
      const data = await res.json()

      expect(res.status).toBe(200)
      expect(data.body).toBeUndefined()
      expect(data.rawBody.length).toBe(bodySize)
      expect(data.rawBody.split('HIJK1L2M3N4O5P6Q7R8S9T0UaVbWcXdY').length).toBe(bodySize / 32 + 1)
    })

    it('should be able to send and return with body greater than 16KiB', async () => {
      const bodySize = 1024 * 1024
      const body = 'JKLM1N2O3P4Q5R6S7T8U9V0WaXbYcZdA'.repeat(bodySize / 32)

      const res = await fetchViaHTTP(
        next.url,
        '/api/body_parser_false',
        {},
        {
          body,
          method: 'POST',
        },
      )
      const data = await res.json()

      expect(res.status).toBe(200)
      expect(data.body).toBeUndefined()
      expect(data.rawBody.length).toBe(bodySize)
      expect(data.rawBody.split('JKLM1N2O3P4Q5R6S7T8U9V0WaXbYcZdA').length).toBe(bodySize / 32 + 1)
    })
  })
})
