import { test } from '@japa/runner'
import { createLogger } from '../../../../src/index.js'
import {
  HttpResponseParser,
  RequestState,
  type ParserCallbacks,
} from '../../../../src/runtime/http-engine/parsers/HttpResponseParser.js'

const logger = createLogger()

test.group('HttpResponseParser', (group) => {
  let parser: HttpResponseParser
  let callbacks: ParserCallbacks
  let statusParsed = false
  let headersParsed = false
  let bodyComplete = false
  let errorOccurred = false
  let aborted = false
  let emittedEvents: { event: string; detail?: unknown }[] = []

  group.each.setup(() => {
    statusParsed = false
    headersParsed = false
    bodyComplete = false
    errorOccurred = false
    aborted = false
    emittedEvents = []

    callbacks = {
      onStatusParsed: () => {
        statusParsed = true
      },
      onHeadersParsed: () => {
        headersParsed = true
      },
      onBodyComplete: () => {
        bodyComplete = true
      },
      onError: () => {
        errorOccurred = true
      },
      onAbort: () => {
        aborted = true
      },
      emit: (event: string, detail?: unknown) => {
        emittedEvents.push({ event, detail })
      },
    }

    parser = new HttpResponseParser(logger, callbacks)
  })

  test('parses status line correctly', ({ assert }) => {
    const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
    parser.processData(statusData)

    assert.isTrue(statusParsed)
    assert.equal(parser.getState(), RequestState.Headers)
  }).tags(['@http-engine', '@response', '@parser'])

  test('handles incomplete status line', ({ assert }) => {
    const statusData = Buffer.from('HTTP/1.1 200')
    parser.processData(statusData)

    assert.isFalse(statusParsed)
    assert.equal(parser.getState(), RequestState.Status)
  }).tags(['@http-engine', '@response', '@parser'])

  test('handles malformed status line', ({ assert }) => {
    const statusData = Buffer.from('Invalid Status Line\r\n')
    parser.processData(statusData)

    // StatusParser doesn't throw errors, it just returns invalid status codes
    assert.isTrue(statusParsed)
    assert.equal(parser.getState(), RequestState.Headers)
  }).tags(['@http-engine', '@response', '@parser'])

  test('parses headers correctly', ({ assert }) => {
    const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
    const headersData = Buffer.from('Content-Type: application/json\r\nContent-Length: 114\r\n\r\n')

    parser.processData(statusData)
    parser.processData(headersData)

    assert.isTrue(statusParsed)
    assert.isTrue(headersParsed)
    assert.equal(parser.getState(), RequestState.Body)
  }).tags(['@http-engine', '@response', '@parser'])

  test('handles partial headers', ({ assert }) => {
    const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
    const partialHeaders = Buffer.from('Content-Type: application/json\r\n')

    parser.processData(statusData)
    parser.processData(partialHeaders)

    assert.isTrue(statusParsed)
    assert.isFalse(headersParsed)
    assert.equal(parser.getState(), RequestState.Headers)
  }).tags(['@http-engine', '@response', '@parser'])

  test('extracts content length from headers', ({ assert }) => {
    const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
    const headersData = Buffer.from('Content-Length: 114\r\n\r\n')

    parser.processData(statusData)
    parser.processData(headersData)

    const responseInfo = parser.getResponseInfo()
    assert.equal(responseInfo.contentLength, 114)
  }).tags(['@http-engine', '@response', '@parser'])

  test('detects chunked transfer encoding', ({ assert }) => {
    const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
    const headersData = Buffer.from('Transfer-Encoding: chunked\r\n\r\n')

    parser.processData(statusData)
    parser.processData(headersData)

    const responseInfo = parser.getResponseInfo()
    assert.isTrue(responseInfo.chunked)
  }).tags(['@http-engine', '@response', '@parser'])

  test('emits headersreceived event', ({ assert }) => {
    const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
    const headersData = Buffer.from('Content-Type: application/json\r\n\r\n')

    parser.processData(statusData)
    parser.processData(headersData)

    const headersEvent = emittedEvents.find((e) => e.event === 'headersreceived')
    assert.isDefined(headersEvent)
    assert.isDefined(headersEvent?.detail)
  }).tags(['@http-engine', '@response', '@parser'])

  test('handles content-length body', ({ assert }) => {
    const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
    const headersData = Buffer.from('Content-Length: 4\r\n\r\n')
    const bodyData = Buffer.from('test')

    parser.processData(statusData)
    parser.processData(headersData)
    parser.processData(bodyData)

    assert.isTrue(bodyComplete)
  }).tags(['@http-engine', '@response', '@parser'])

  test('handles chunked body', ({ assert }) => {
    const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
    const headersData = Buffer.from('Transfer-Encoding: chunked\r\n\r\n')
    const chunkedBody = Buffer.from('4\r\ntest\r\n0\r\n\r\n')

    parser.processData(statusData)
    parser.processData(headersData)
    parser.processData(chunkedBody)

    assert.isTrue(bodyComplete)
  }).tags(['@http-engine', '@response', '@parser'])

  test('handles multiple chunks', ({ assert }) => {
    const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
    const headersData = Buffer.from('Transfer-Encoding: chunked\r\n\r\n')
    const chunkedBody = Buffer.from('6\r\ntest\r\n\r\n8\r\ntest1234\r\n0\r\n\r\n')

    parser.processData(statusData)
    parser.processData(headersData)
    parser.processData(chunkedBody)

    assert.isTrue(bodyComplete)
  }).tags(['@http-engine', '@response', '@parser'])

  test('handles partial chunks', ({ assert }) => {
    const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
    const headersData = Buffer.from('Transfer-Encoding: chunked\r\n\r\n')

    parser.processData(statusData)
    parser.processData(headersData)

    // Send partial chunk
    parser.processData(Buffer.from('6\r\nte'))
    parser.processData(Buffer.from('st\r\n\r\n'))
    parser.processData(Buffer.from('8\r\ntest'))
    parser.processData(Buffer.from('1234\r\n0\r\n\r\n'))

    assert.isTrue(bodyComplete)
  }).tags(['@http-engine', '@response', '@parser'])

  test('handles 204 No Content without body', ({ assert }) => {
    const statusData = Buffer.from('HTTP/1.1 204 No Content\r\n')
    const headersData = Buffer.from('\r\n')

    parser.processData(statusData)
    parser.processData(headersData)

    assert.isTrue(bodyComplete)
    assert.equal(parser.getState(), RequestState.Done)
  }).tags(['@http-engine', '@response', '@parser'])

  test('handles 304 Not Modified without body', ({ assert }) => {
    const statusData = Buffer.from('HTTP/1.1 304 Not Modified\r\n')
    const headersData = Buffer.from('\r\n')

    parser.processData(statusData)
    parser.processData(headersData)

    assert.isTrue(bodyComplete)
    assert.equal(parser.getState(), RequestState.Done)
  }).tags(['@http-engine', '@response', '@parser'])

  test('handles informational responses without body', ({ assert }) => {
    const statusData = Buffer.from('HTTP/1.1 100 Continue\r\n')
    const headersData = Buffer.from('\r\n')

    parser.processData(statusData)
    parser.processData(headersData)

    assert.isTrue(bodyComplete)
    assert.equal(parser.getState(), RequestState.Done)
  }).tags(['@http-engine', '@response', '@parser'])

  test('aborts parser correctly', ({ assert }) => {
    parser.abort()

    assert.isTrue(aborted)
    assert.equal(parser.getState(), RequestState.Done)
  }).tags(['@http-engine', '@response', '@parser'])

  test('ignores data after abort', ({ assert }) => {
    parser.abort()

    const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
    parser.processData(statusData)

    assert.isFalse(statusParsed)
  }).tags(['@http-engine', '@response', '@parser'])

  test('resets parser state correctly', ({ assert }) => {
    const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
    parser.processData(statusData)

    parser.reset()

    assert.equal(parser.getState(), RequestState.Status)
    assert.isFalse(parser.getResponseInfo().chunked)
  }).tags(['@http-engine', '@response', '@parser'])

  test('can process new response after reset', ({ assert }) => {
    const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
    parser.processData(statusData)

    parser.reset()

    parser.processData(statusData)
    assert.isTrue(statusParsed)
  }).tags(['@http-engine', '@response', '@parser'])

  const parts = [
    Buffer.from([
      72, 84, 84, 80, 47, 49, 46, 48, 32, 50, 48, 48, 32, 79, 75, 13, 10, 67, 111, 110, 116, 101, 110, 116, 45, 84, 121,
      112, 101, 58, 32, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 106, 115, 111, 110, 13, 10, 67, 111,
      110, 116, 101, 110, 116, 45, 76, 101, 110, 103, 116, 104, 58, 32, 49, 49, 52, 13, 10,
    ]),
    Buffer.from([
      83, 101, 114, 118, 101, 114, 58, 32, 87, 101, 114, 107, 122, 101, 117, 103, 47, 48, 46, 49, 52, 46, 49, 32, 80,
      121, 116, 104, 111, 110, 47, 50, 46, 55, 46, 49, 52, 13, 10, 68, 97, 116, 101, 58, 32, 84, 104, 117, 44, 32, 50,
      49, 32, 74, 117, 110, 32, 50, 48, 49, 56, 32, 49, 56, 58, 51, 48, 58, 53, 49, 32, 71, 77, 84, 13, 10, 13, 10, 123,
      34, 105, 110, 115, 116, 114, 117, 109, 101, 110, 116, 95, 105, 100, 34, 58, 32, 50, 52, 49, 56, 54, 44, 32, 34,
      117, 115, 101, 114, 95, 105, 100, 34, 58, 32, 53, 57, 44, 32, 34, 112, 114, 111, 100, 117, 99, 116, 95, 105, 100,
      34, 58, 32, 51, 50, 54, 57, 44, 32, 34, 112, 114, 105, 99, 101, 34, 58, 32, 50, 46, 48, 44, 32, 34, 115, 105, 100,
      101, 34, 58, 32, 34, 83, 101, 108, 108, 34, 44, 32, 34, 105, 100, 34, 58, 32, 50, 44, 32, 34, 113, 117, 97, 110,
      116, 105, 116, 121, 34, 58, 32, 48, 125, 10,
    ]),
  ]

  test('processes complete response in multiple parts', ({ assert }) => {
    parser.processData(parts[0])
    parser.processData(parts[1])

    assert.isTrue(statusParsed)
    assert.isTrue(headersParsed)
    assert.isTrue(bodyComplete)
    assert.equal(parser.getState(), RequestState.Done)
  }).tags(['@http-engine', '@response', '@parser'])

  test('extracts correct response information', ({ assert }) => {
    parser.processData(parts[0])
    parser.processData(parts[1])

    const responseInfo = parser.getResponseInfo()
    assert.equal(responseInfo.status, 200)
    assert.equal(responseInfo.contentLength, 114)
    assert.isFalse(responseInfo.chunked)
  }).tags(['@http-engine', '@response', '@parser'])

  test('handles parsing errors gracefully', ({ assert }) => {
    const invalidData = Buffer.from('Invalid HTTP Response Data')
    parser.processData(invalidData)

    // StatusParser doesn't throw errors for invalid data
    assert.isFalse(errorOccurred)
  }).tags(['@http-engine', '@response', '@parser'])

  test('continues processing after error', ({ assert }) => {
    const invalidData = Buffer.from('Invalid Data')
    parser.processData(invalidData)

    const validData = Buffer.from('HTTP/1.1 200 OK\r\n\r\n')
    parser.processData(validData)

    // StatusParser doesn't throw errors for invalid data
    assert.isFalse(errorOccurred)
    assert.isTrue(statusParsed)
  }).tags(['@http-engine', '@response', '@parser'])

  test('tracks state correctly through parsing stages', ({ assert }) => {
    assert.equal(parser.getState(), RequestState.Status)

    const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
    parser.processData(statusData)
    assert.equal(parser.getState(), RequestState.Headers)

    const headersData = Buffer.from('Content-Length: 4\r\n\r\n')
    parser.processData(headersData)
    assert.equal(parser.getState(), RequestState.Body)

    const bodyData = Buffer.from('test')
    parser.processData(bodyData)
    assert.equal(parser.getState(), RequestState.Done)
  }).tags(['@http-engine', '@response', '@parser'])

  test('returns correct response info', ({ assert }) => {
    const responseInfo = parser.getResponseInfo()
    assert.isFalse(responseInfo.chunked)
    assert.isUndefined(responseInfo.status)
    assert.isUndefined(responseInfo.contentLength)
  }).tags(['@http-engine', '@response', '@parser'])
})
