import { test } from '@japa/runner'
import { DigestAuthHandler } from '../../../../../src/runtime/http-engine/connections/DigestAuthHandler.js'
import { createLogger, type Logger, type ILogObj } from '../../../../../src/lib/logging/index.js'

test.group('DigestAuthHandler', (group) => {
  let handler: DigestAuthHandler
  let logger: Logger<ILogObj>

  group.each.setup(() => {
    logger = createLogger()
    handler = new DigestAuthHandler('testuser', 'testpass', logger)
  })

  test('should parse a basic Digest challenge', ({ assert }) => {
    const challenge = handler.parseChallenge(
      'Digest realm="testrealm", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", algorithm=MD5'
    )

    assert.equal(challenge.realm, 'testrealm')
    assert.equal(challenge.nonce, 'dcd98b7102dd2f0e8b11d0f600bfb0c093')
    assert.equal(challenge.algorithm, 'MD5')
  })

  test('should parse a challenge with qop', ({ assert }) => {
    const challenge = handler.parseChallenge(
      'Digest realm="testrealm", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", qop="auth"'
    )

    assert.equal(challenge.realm, 'testrealm')
    assert.equal(challenge.nonce, 'dcd98b7102dd2f0e8b11d0f600bfb0c093')
    assert.equal(challenge.qop, 'auth')
  })

  test('should throw error for non-Digest challenge', ({ assert }) => {
    assert.throws(() => {
      handler.parseChallenge('Basic realm="testrealm"')
    }, 'Not a Digest authentication challenge')
  })

  test('should throw error for invalid challenge', ({ assert }) => {
    assert.throws(() => {
      handler.parseChallenge('Digest realm="testrealm"')
    }, 'Invalid Digest challenge: missing realm or nonce')
  })

  test('should generate response without qop', ({ assert }) => {
    const challenge = handler.parseChallenge(
      'Digest realm="testrealm", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", algorithm=MD5'
    )

    const response = handler.generateResponse(challenge, 'CONNECT', '/')

    assert.include(response, 'Digest')
    assert.include(response, 'username="testuser"')
    assert.include(response, 'realm="testrealm"')
    assert.include(response, 'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093"')
    assert.include(response, 'uri="/"')
    assert.include(response, 'response=')
    assert.include(response, 'algorithm="MD5"')
  })

  test('should generate response with qop', ({ assert }) => {
    const challenge = handler.parseChallenge(
      'Digest realm="testrealm", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", qop="auth", algorithm=MD5'
    )

    const response = handler.generateResponse(challenge, 'CONNECT', '/')

    assert.include(response, 'Digest')
    assert.include(response, 'username="testuser"')
    assert.include(response, 'realm="testrealm"')
    assert.include(response, 'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093"')
    assert.include(response, 'uri="/"')
    assert.include(response, 'response=')
    assert.include(response, 'qop="auth"')
    assert.include(response, 'nc=')
    assert.include(response, 'cnonce=')
  })

  test('should handle opaque parameter', ({ assert }) => {
    const challenge = handler.parseChallenge(
      'Digest realm="testrealm", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", ' +
        'opaque="5ccc069c403ebaf9f0171e9517f40e41"'
    )

    const response = handler.generateResponse(challenge, 'CONNECT', '/')

    assert.include(response, 'opaque="5ccc069c403ebaf9f0171e9517f40e41"')
  })

  test('should throw error for unsupported qop', ({ assert }) => {
    const challenge = handler.parseChallenge(
      'Digest realm="testrealm", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", qop="auth-int"'
    )

    assert.throws(() => {
      handler.generateResponse(challenge, 'CONNECT', '/')
    }, 'Unsupported qop value: auth-int')
  })

  test('should increment nonce count for each request', ({ assert }) => {
    const challenge = handler.parseChallenge(
      'Digest realm="testrealm", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", qop="auth"'
    )

    const response1 = handler.generateResponse(challenge, 'CONNECT', '/')
    const response2 = handler.generateResponse(challenge, 'CONNECT', '/')

    // Extract nc values
    const nc1Match = response1.match(/nc="([^"]+)"/)
    const nc2Match = response2.match(/nc="([^"]+)"/)

    assert.isNotNull(nc1Match)
    assert.isNotNull(nc2Match)
    assert.equal(nc1Match![1], '00000001')
    assert.equal(nc2Match![1], '00000002')
  })

  test('should reset nonce count and generate new cnonce', ({ assert }) => {
    const challenge = handler.parseChallenge(
      'Digest realm="testrealm", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", qop="auth"'
    )

    const response1 = handler.generateResponse(challenge, 'CONNECT', '/')
    handler.reset()
    const response2 = handler.generateResponse(challenge, 'CONNECT', '/')

    // Extract nc and cnonce values
    const nc1Match = response1.match(/nc="([^"]+)"/)
    const nc2Match = response2.match(/nc="([^"]+)"/)
    const cnonce1Match = response1.match(/cnonce="([^"]+)"/)
    const cnonce2Match = response2.match(/cnonce="([^"]+)"/)

    assert.equal(nc1Match![1], '00000001')
    assert.equal(nc2Match![1], '00000001') // Reset to 1
    assert.notEqual(cnonce1Match![1], cnonce2Match![1]) // Different cnonce
  })

  test('should detect stale challenges', ({ assert }) => {
    const staleChallenge = handler.parseChallenge(
      'Digest realm="testrealm", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", stale=true'
    )

    const freshChallenge = handler.parseChallenge(
      'Digest realm="testrealm", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", stale=false'
    )

    assert.isTrue(handler.isStale(staleChallenge))
    assert.isFalse(handler.isStale(freshChallenge))
  })

  test('should calculate correct HA1', ({ assert }) => {
    // This test verifies the MD5 calculation is correct
    // HA1 = MD5(username:realm:password)
    const challenge = handler.parseChallenge('Digest realm="testrealm", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093"')

    const response = handler.generateResponse(challenge, 'CONNECT', '/')
    const responseMatch = response.match(/response="([^"]+)"/)

    assert.isNotNull(responseMatch)
    // The response should be a valid MD5 hash
    assert.match(responseMatch![1], /^[a-f0-9]{32}$/)
  })
})
