import { test } from '@japa/runner'
import zlib from 'zlib'
import { Headers } from '../../../../../src/lib/headers/Headers.js'
import {
  decompress,
  inflate,
  gunzip,
  brotli,
  CompressionOptions,
} from '../../../../../src/runtime/http-engine/compression/CompressionManager.js'

// Helper functions to create compressed test data
function createDeflateBuffer(data: string | Buffer): Buffer {
  const input = typeof data === 'string' ? Buffer.from(data) : data
  return zlib.deflateSync(input)
}

function createGzipBuffer(data: string | Buffer): Buffer {
  const input = typeof data === 'string' ? Buffer.from(data) : data
  return zlib.gzipSync(input)
}

function createBrotliBuffer(data: string | Buffer): Buffer {
  const input = typeof data === 'string' ? Buffer.from(data) : data
  return zlib.brotliCompressSync(input)
}

test.group('CompressionManager - decompress()', () => {
  test('returns undefined when body is not provided', async ({ assert }) => {
    const result = await decompress(undefined, {})
    assert.isUndefined(result)
  })

  test('returns undefined when body is null', async ({ assert }) => {
    const result = await decompress(null as unknown as Buffer, {})
    assert.isUndefined(result)
  })

  test('returns undefined when aborted is true', async ({ assert }) => {
    const body = Buffer.from('test-data')
    const options: CompressionOptions = {
      aborted: true,
      headers: new Headers('content-encoding: gzip'),
    }
    const result = await decompress(body, options)
    assert.isUndefined(result)
  })

  test('returns undefined when headers are not provided', async ({ assert }) => {
    const body = Buffer.from('test-data')
    const options: CompressionOptions = { aborted: false }
    const result = await decompress(body, options)
    assert.isUndefined(result)
  })

  test('returns undefined when headers are null', async ({ assert }) => {
    const body = Buffer.from('test-data')
    const options: CompressionOptions = {
      aborted: false,
      headers: null as unknown as Headers,
    }
    const result = await decompress(body, options)
    assert.isUndefined(result)
  })

  test('returns original body when no content-encoding header', async ({ assert }) => {
    const body = Buffer.from('test-data')
    const headers = new Headers()
    const options: CompressionOptions = { headers }

    const result = await decompress(body, options)
    assert.deepEqual(result, body)
  })

  test('returns original body when content-encoding header is empty', async ({ assert }) => {
    const body = Buffer.from('test-data')
    const headers = new Headers('content-encoding: ')
    const options: CompressionOptions = { headers }

    const result = await decompress(body, options)
    assert.deepEqual(result, body)
  })

  test('decompresses deflate content', async ({ assert }) => {
    const originalData = 'deflate-test-content'
    const compressedBody = createDeflateBuffer(originalData)
    const headers = new Headers('content-encoding: deflate')
    const options: CompressionOptions = { headers }

    const result = await decompress(compressedBody, options)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result!.toString(), originalData)
  })

  test('decompresses gzip content', async ({ assert }) => {
    const originalData = 'gzip-test-content'
    const compressedBody = createGzipBuffer(originalData)
    const headers = new Headers('content-encoding: gzip')
    const options: CompressionOptions = { headers }

    const result = await decompress(compressedBody, options)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result!.toString(), originalData)
  })

  test('decompresses brotli content', async ({ assert }) => {
    const originalData = 'brotli-test-content'
    const compressedBody = createBrotliBuffer(originalData)
    const headers = new Headers('content-encoding: br')
    const options: CompressionOptions = { headers }

    const result = await decompress(compressedBody, options)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result!.toString(), originalData)
  })

  test('handles multiple content-encoding - decodes in reverse order (gzip then deflate)', async ({ assert }) => {
    const originalData = 'reverse-order-test-data'

    // Apply deflate first, then gzip (simulating: original -> deflate -> gzip)
    const deflatedData = createDeflateBuffer(originalData)
    const doubleCompressed = createGzipBuffer(deflatedData)

    const headers = new Headers('content-encoding: deflate, gzip')
    const options: CompressionOptions = { headers }

    // Should decompress gzip first (last applied), then deflate
    const result = await decompress(doubleCompressed, options)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result!.toString(), originalData)
  })

  test('handles multiple content-encoding - decodes in reverse order (brotli then gzip)', async ({ assert }) => {
    const originalData = 'brotli-gzip-test-data'

    // Apply gzip first, then brotli (simulating: original -> gzip -> brotli)
    const gzippedData = createGzipBuffer(originalData)
    const doubleCompressed = createBrotliBuffer(gzippedData)

    const headers = new Headers('content-encoding: gzip, br')
    const options: CompressionOptions = { headers }

    // Should decompress brotli first (last applied), then gzip
    const result = await decompress(doubleCompressed, options)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result!.toString(), originalData)
  })

  test('handles triple encoding - decodes in reverse order', async ({ assert }) => {
    const originalData = 'triple-encoding-test'

    // Apply deflate, then gzip, then brotli (original -> deflate -> gzip -> brotli)
    const deflatedData = createDeflateBuffer(originalData)
    const gzippedData = createGzipBuffer(deflatedData)
    const tripleCompressed = createBrotliBuffer(gzippedData)

    const headers = new Headers('content-encoding: deflate, gzip, br')
    const options: CompressionOptions = { headers }

    // Should decompress brotli, then gzip, then deflate
    const result = await decompress(tripleCompressed, options)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result!.toString(), originalData)
  })

  test('returns original body for unknown content-encoding', async ({ assert }) => {
    const body = Buffer.from('test-data')
    const headers = new Headers('content-encoding: unknown-encoding')
    const options: CompressionOptions = { headers }

    const result = await decompress(body, options)
    assert.deepEqual(result, body)
  })

  test('handles case-insensitive content-encoding header', async ({ assert }) => {
    const originalData = 'case-test-content'
    const compressedBody = createGzipBuffer(originalData)
    const headers = new Headers('Content-Encoding: GZIP')
    const options: CompressionOptions = { headers }

    const result = await decompress(compressedBody, options)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result!.toString(), originalData)
  })

  test('handles content-encoding with spaces and mixed case', async ({ assert }) => {
    const originalData = 'spaces-and-case-test'

    // Apply gzip first, then brotli
    const gzippedData = createGzipBuffer(originalData)
    const doubleCompressed = createBrotliBuffer(gzippedData)

    const headers = new Headers('content-encoding:  GZIP , br  ')
    const options: CompressionOptions = { headers }

    const result = await decompress(doubleCompressed, options)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result!.toString(), originalData)
  })

  test('handles brotli encoding specified as "brotli"', async ({ assert }) => {
    const originalData = 'brotli-name-test'
    const compressedBody = createBrotliBuffer(originalData)
    const headers = new Headers('content-encoding: brotli')
    const options: CompressionOptions = { headers }

    const result = await decompress(compressedBody, options)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result!.toString(), originalData)
  })

  test('ignores unknown encodings in multiple encoding chain', async ({ assert }) => {
    const originalData = 'unknown-encoding-test'

    // Apply gzip only (unknown encoding will be ignored)
    const compressedBody = createGzipBuffer(originalData)

    const headers = new Headers('content-encoding: unknown, gzip, another-unknown')
    const options: CompressionOptions = { headers }

    // Should only decompress gzip, ignoring the unknown encodings
    const result = await decompress(compressedBody, options)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result!.toString(), originalData)
  })

  test('handles empty encoding values in header', async ({ assert }) => {
    const originalData = 'empty-encoding-test'
    const compressedBody = createGzipBuffer(originalData)
    const headers = new Headers('content-encoding: , gzip, , ')
    const options: CompressionOptions = { headers }

    const result = await decompress(compressedBody, options)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result!.toString(), originalData)
  })

  test('uses default options when not provided', async ({ assert }) => {
    const body = Buffer.from('test-data')
    const result = await decompress(body)
    assert.isUndefined(result)
  })
})

test.group('CompressionManager - inflate()', () => {
  test('decompresses deflate data correctly', async ({ assert }) => {
    const originalData = 'inflate-test-data'
    const compressedBuffer = createDeflateBuffer(originalData)

    const result = await inflate(compressedBuffer)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result.toString(), originalData)
  })

  test('handles empty string', async ({ assert }) => {
    const compressedBuffer = createDeflateBuffer('')

    const result = await inflate(compressedBuffer)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result.toString(), '')
  })

  test('handles unicode characters', async ({ assert }) => {
    const originalData = 'Test with unicode: 你好世界 🚀 émojis'
    const compressedBuffer = createDeflateBuffer(originalData)

    const result = await inflate(compressedBuffer)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result.toString(), originalData)
  })

  test('creates new buffer from input', async ({ assert }) => {
    const originalData = 'buffer-test-data'
    const compressedBuffer = createDeflateBuffer(originalData)
    const originalLength = compressedBuffer.length

    const result = await inflate(compressedBuffer)
    // Original buffer should remain unchanged
    assert.equal(compressedBuffer.length, originalLength)
    assert.notEqual(result.toString(), compressedBuffer.toString())
  })

  test('rejects with error for invalid deflate data', async ({ assert }) => {
    const invalidBuffer = Buffer.from('this is not valid deflate data')

    await assert.rejects(async () => await inflate(invalidBuffer), 'incorrect header check')
  })

  test('rejects with error for corrupted deflate data', async ({ assert }) => {
    const compressedBuffer = createDeflateBuffer('test-data')
    // Corrupt the compressed data
    compressedBuffer[5] = 255

    await assert.rejects(async () => await inflate(compressedBuffer))
  })
})

test.group('CompressionManager - gunzip()', () => {
  test('decompresses gzip data correctly', async ({ assert }) => {
    const originalData = 'gunzip-test-data'
    const compressedBuffer = createGzipBuffer(originalData)

    const result = await gunzip(compressedBuffer)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result.toString(), originalData)
  })

  test('handles empty string', async ({ assert }) => {
    const compressedBuffer = createGzipBuffer('')

    const result = await gunzip(compressedBuffer)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result.toString(), '')
  })

  test('handles large data', async ({ assert }) => {
    const originalData = 'a'.repeat(10000) // Large string
    const compressedBuffer = createGzipBuffer(originalData)

    const result = await gunzip(compressedBuffer)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result.toString(), originalData)
    assert.equal(result.length, 10000)
  })

  test('handles unicode characters', async ({ assert }) => {
    const originalData = 'Gzip test with unicode: 日本語 🎯 français'
    const compressedBuffer = createGzipBuffer(originalData)

    const result = await gunzip(compressedBuffer)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result.toString(), originalData)
  })

  test('creates new buffer from input', async ({ assert }) => {
    const originalData = 'gzip-buffer-test'
    const compressedBuffer = createGzipBuffer(originalData)
    const originalLength = compressedBuffer.length

    const result = await gunzip(compressedBuffer)
    // Original buffer should remain unchanged
    assert.equal(compressedBuffer.length, originalLength)
    assert.notEqual(result.toString(), compressedBuffer.toString())
  })

  test('rejects with error for invalid gzip data', async ({ assert }) => {
    const invalidBuffer = Buffer.from('this is not valid gzip data')

    await assert.rejects(async () => await gunzip(invalidBuffer), 'incorrect header check')
  })

  test('rejects with error for corrupted gzip data', async ({ assert }) => {
    const compressedBuffer = createGzipBuffer('test-data')
    // Corrupt the gzip header
    compressedBuffer[0] = 255

    await assert.rejects(async () => await gunzip(compressedBuffer))
  })
})

test.group('CompressionManager - brotli()', () => {
  test('decompresses brotli data correctly', async ({ assert }) => {
    const originalData = 'brotli-test-data'
    const compressedBuffer = createBrotliBuffer(originalData)

    const result = await brotli(compressedBuffer)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result.toString(), originalData)
  })

  test('handles empty string', async ({ assert }) => {
    const compressedBuffer = createBrotliBuffer('')

    const result = await brotli(compressedBuffer)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result.toString(), '')
  })

  test('handles large data', async ({ assert }) => {
    const originalData = 'b'.repeat(5000) // Large string for brotli compression
    const compressedBuffer = createBrotliBuffer(originalData)

    const result = await brotli(compressedBuffer)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result.toString(), originalData)
    assert.equal(result.length, 5000)
  })

  test('handles unicode characters', async ({ assert }) => {
    const originalData = 'Brotli test: 中文 🎉 español ñoño'
    const compressedBuffer = createBrotliBuffer(originalData)

    const result = await brotli(compressedBuffer)
    assert.isTrue(Buffer.isBuffer(result))
    assert.equal(result.toString(), originalData)
  })

  test('creates new buffer from input', async ({ assert }) => {
    const originalData = 'brotli-buffer-test'
    const compressedBuffer = createBrotliBuffer(originalData)
    const originalLength = compressedBuffer.length

    const result = await brotli(compressedBuffer)
    // Original buffer should remain unchanged
    assert.equal(compressedBuffer.length, originalLength)
    assert.notEqual(result.toString(), compressedBuffer.toString())
  })

  test('rejects with error for invalid brotli data', async ({ assert }) => {
    const invalidBuffer = Buffer.from('this is not valid brotli data')

    await assert.rejects(async () => await brotli(invalidBuffer))
  })

  test('rejects with error for corrupted brotli data', async ({ assert }) => {
    const compressedBuffer = createBrotliBuffer('test-data')
    // Corrupt the brotli data
    compressedBuffer[2] = 255
    compressedBuffer[3] = 255

    await assert.rejects(async () => await brotli(compressedBuffer))
  })

  test('preserves error from brotliDecompress', async ({ assert }) => {
    const invalidBuffer = Buffer.from([0x21]) // Invalid brotli stream

    try {
      await brotli(invalidBuffer)
      assert.fail('Should have thrown an error')
    } catch (error) {
      assert.isTrue(error instanceof Error)
      // The brotli function should preserve the original error object
      assert.isTrue((error as Error).message.length > 0)
    }
  })
})

test.group('CompressionManager - Edge Cases', () => {
  test('decompress handles Buffer.from() conversion in all functions', async ({ assert }) => {
    // Test that all functions properly handle the Buffer.from() conversion
    const originalData = 'conversion-test'

    // Test with a Uint8Array input to ensure Buffer.from() works
    const deflateData = new Uint8Array(createDeflateBuffer(originalData))
    const gzipData = new Uint8Array(createGzipBuffer(originalData))
    const brotliData = new Uint8Array(createBrotliBuffer(originalData))

    const deflateResult = await inflate(deflateData)
    const gzipResult = await gunzip(gzipData)
    const brotliResult = await brotli(brotliData)

    assert.equal(deflateResult.toString(), originalData)
    assert.equal(gzipResult.toString(), originalData)
    assert.equal(brotliResult.toString(), originalData)
  })

  test('decompress prioritizes order correctly in multiple encodings', async ({ assert }) => {
    const originalData = 'priority-test'

    // Apply deflate first, then gzip (original -> deflate -> gzip)
    const deflateData = createDeflateBuffer(originalData)
    const doubleCompressed = createGzipBuffer(deflateData)

    const headers = new Headers('content-encoding: deflate, gzip')
    const options: CompressionOptions = { headers }

    // Should decompress gzip first, then deflate (reverse order)
    const result = await decompress(doubleCompressed, options)
    assert.equal(result!.toString(), originalData)
  })

  test('decompress handles single encoding correctly', async ({ assert }) => {
    const originalData = 'single-encoding-test'
    const gzipBuffer = createGzipBuffer(originalData)
    const headers = new Headers('content-encoding: gzip')
    const options: CompressionOptions = { headers }

    // Should decompress the single gzip encoding
    const result = await decompress(gzipBuffer, options)
    assert.equal(result!.toString(), originalData)
  })

  test('all decompression functions handle very small buffers', async ({ assert }) => {
    const singleChar = 'x'

    const deflateBuffer = createDeflateBuffer(singleChar)
    const gzipBuffer = createGzipBuffer(singleChar)
    const brotliBuffer = createBrotliBuffer(singleChar)

    const deflateResult = await inflate(deflateBuffer)
    const gzipResult = await gunzip(gzipBuffer)
    const brotliResult = await brotli(brotliBuffer)

    assert.equal(deflateResult.toString(), singleChar)
    assert.equal(gzipResult.toString(), singleChar)
    assert.equal(brotliResult.toString(), singleChar)
  })
})
