import { test } from '@japa/runner'
import tls from 'tls'
import sinon from 'sinon'
import {
  addClientCertificate,
  checkServerIdentity,
} from '../../../../../src/runtime/http-engine/certificates/CertificateManager.js'
import { HttpCertificate, IPemCertificate, IP12Certificate } from '../../../../../src/models/ClientCertificate.js'

test.group('addClientCertificate()', (group) => {
  let options: tls.ConnectionOptions

  group.each.setup(() => {
    options = {}
  })

  test('returns early when certificate is null', ({ assert }) => {
    const originalOptions = { ...options }
    addClientCertificate(null as unknown as HttpCertificate, options)
    assert.deepEqual(options, originalOptions, 'options should not be modified')
  })

  test('returns early when certificate is undefined', ({ assert }) => {
    const originalOptions = { ...options }
    addClientCertificate(undefined as unknown as HttpCertificate, options)
    assert.deepEqual(options, originalOptions, 'options should not be modified')
  })

  test('adds p12 certificate to empty pfx array', ({ assert }) => {
    const cert: IP12Certificate = {
      kind: 'Core#Certificate',
      key: 'test-key',
      name: 'Test P12',
      type: 'p12',
      cert: {
        data: Buffer.from('test-cert-data'),
      },
    }

    addClientCertificate(cert, options)

    assert.isArray(options.pfx, 'pfx should be an array')
    assert.lengthOf(options.pfx as tls.PxfObject[], 1, 'pfx should contain one certificate')

    const pfxEntry = (options.pfx as tls.PxfObject[])[0]
    assert.deepEqual(pfxEntry.buf, Buffer.from('test-cert-data'), 'certificate data should match')
    assert.isUndefined(pfxEntry.passphrase, 'passphrase should not be set when not provided')
  })

  test('adds p12 certificate with passphrase', ({ assert }) => {
    const cert: IP12Certificate = {
      kind: 'Core#Certificate',
      key: 'test-key',
      name: 'Test P12 with passphrase',
      type: 'p12',
      cert: {
        data: Buffer.from('test-cert-data'),
        passphrase: 'secret-passphrase',
      },
    }

    addClientCertificate(cert, options)

    const pfxEntry = (options.pfx as tls.PxfObject[])[0]
    assert.equal(pfxEntry.passphrase, 'secret-passphrase', 'passphrase should be set')
  })

  test('converts existing pfx non-array to array and adds new certificate', ({ assert }) => {
    const existingPfx: tls.PxfObject = {
      buf: Buffer.from('existing-cert'),
    }
    options.pfx = existingPfx as unknown as (string | Buffer | tls.PxfObject)[]

    const cert: IP12Certificate = {
      kind: 'Core#Certificate',
      key: 'test-key',
      name: 'Test P12',
      type: 'p12',
      cert: {
        data: Buffer.from('new-cert-data'),
      },
    }

    addClientCertificate(cert, options)

    assert.isArray(options.pfx, 'pfx should be converted to array')
    assert.lengthOf(options.pfx as tls.PxfObject[], 2, 'pfx should contain both certificates')
    assert.deepEqual((options.pfx as tls.PxfObject[])[0], existingPfx, 'existing certificate should be preserved')
    assert.deepEqual(
      (options.pfx as tls.PxfObject[])[1].buf,
      Buffer.from('new-cert-data'),
      'new certificate should be added'
    )
  })

  test('converts existing pfx non-array to array and adds new certificate', ({ assert }) => {
    const existingPfx: tls.PxfObject = {
      buf: Buffer.from('existing-cert'),
    }
    options.pfx = existingPfx as unknown as (string | Buffer | tls.PxfObject)[]

    const cert: IP12Certificate = {
      kind: 'Core#Certificate',
      key: 'test-key',
      name: 'Test P12',
      type: 'p12',
      cert: {
        data: Buffer.from('new-cert-data'),
      },
    }

    addClientCertificate(cert, options)

    assert.isArray(options.pfx, 'pfx should be converted to array')
    assert.lengthOf(options.pfx as tls.PxfObject[], 2, 'pfx should contain both certificates')
    assert.deepEqual((options.pfx as tls.PxfObject[])[0], existingPfx, 'existing certificate should be preserved')
    assert.deepEqual(
      (options.pfx as tls.PxfObject[])[1].buf,
      Buffer.from('new-cert-data'),
      'new certificate should be added'
    )
  })

  test('adds certificate to existing pfx array', ({ assert }) => {
    const existingPfx: tls.PxfObject = {
      buf: Buffer.from('existing-cert'),
    }
    options.pfx = [existingPfx]

    const cert: IP12Certificate = {
      kind: 'Core#Certificate',
      key: 'test-key',
      name: 'Test P12',
      type: 'p12',
      cert: {
        data: Buffer.from('new-cert-data'),
      },
    }

    addClientCertificate(cert, options)

    assert.lengthOf(options.pfx as tls.PxfObject[], 2, 'pfx should contain both certificates')
    assert.deepEqual((options.pfx as tls.PxfObject[])[0], existingPfx, 'existing certificate should be preserved')
    assert.deepEqual(
      (options.pfx as tls.PxfObject[])[1].buf,
      Buffer.from('new-cert-data'),
      'new certificate should be added'
    )
  })

  test('handles Buffer data correctly for p12', ({ assert }) => {
    const certData = Buffer.from('test-buffer-data')
    const cert: IP12Certificate = {
      kind: 'Core#Certificate',
      key: 'test-key',
      name: 'Test P12 Buffer',
      type: 'p12',
      cert: {
        data: certData,
      },
    }

    addClientCertificate(cert, options)

    const pfxEntry = (options.pfx as tls.PxfObject[])[0]
    assert.deepEqual(pfxEntry.buf, certData, 'buffer data should be preserved')
  })

  test('adds pem certificate without key to empty cert array', ({ assert }) => {
    const cert: IPemCertificate = {
      kind: 'Core#Certificate',
      key: 'test-key',
      name: 'Test PEM',
      type: 'pem',
      cert: {
        data: Buffer.from('test-cert-data'),
      },
      certKey: {
        data: Buffer.from('test-key-data'),
      },
    }

    addClientCertificate(cert, options)

    assert.isArray(options.cert, 'cert should be an array')
    assert.lengthOf(options.cert as Buffer[], 1, 'cert should contain one certificate')
    assert.deepEqual((options.cert as Buffer[])[0], Buffer.from('test-cert-data'), 'certificate data should match')
  })

  test('adds pem certificate with key', ({ assert }) => {
    const cert: IPemCertificate = {
      kind: 'Core#Certificate',
      key: 'test-key',
      name: 'Test PEM with key',
      type: 'pem',
      cert: {
        data: Buffer.from('test-cert-data'),
      },
      certKey: {
        data: Buffer.from('test-key-data'),
      },
    }

    addClientCertificate(cert, options)

    assert.isArray(options.cert, 'cert should be an array')
    assert.isArray(options.key, 'key should be an array')
    assert.lengthOf(options.cert as Buffer[], 1, 'cert should contain one certificate')
    assert.lengthOf(options.key as tls.KeyObject[], 1, 'key should contain one key')

    assert.deepEqual((options.cert as Buffer[])[0], Buffer.from('test-cert-data'), 'certificate data should match')

    const keyEntry = (options.key as tls.KeyObject[])[0]
    assert.deepEqual(keyEntry.pem, Buffer.from('test-key-data'), 'key data should match')
    assert.isUndefined(keyEntry.passphrase, 'passphrase should not be set when not provided')
  })

  test('adds pem certificate with key and passphrase', ({ assert }) => {
    const cert: IPemCertificate = {
      kind: 'Core#Certificate',
      key: 'test-key',
      name: 'Test PEM with key and passphrase',
      type: 'pem',
      cert: {
        data: Buffer.from('test-cert-data'),
      },
      certKey: {
        data: Buffer.from('test-key-data'),
        passphrase: 'key-passphrase',
      },
    }

    addClientCertificate(cert, options)

    const keyEntry = (options.key as tls.KeyObject[])[0]
    assert.equal(keyEntry.passphrase, 'key-passphrase', 'key passphrase should be set')
  })

  test('converts existing cert non-array to array and adds new certificate', ({ assert }) => {
    const existingCert = Buffer.from('existing-cert')
    options.cert = existingCert as string | Buffer | (string | Buffer)[]

    const cert: IPemCertificate = {
      kind: 'Core#Certificate',
      key: 'test-key',
      name: 'Test PEM',
      type: 'pem',
      cert: {
        data: Buffer.from('new-cert-data'),
      },
      certKey: {
        data: Buffer.from('new-key-data'),
      },
    }

    addClientCertificate(cert, options)

    assert.isArray(options.cert, 'cert should be converted to array')
    assert.lengthOf(options.cert as Buffer[], 2, 'cert should contain both certificates')
    assert.deepEqual((options.cert as Buffer[])[0], existingCert, 'existing certificate should be preserved')
    assert.deepEqual((options.cert as Buffer[])[1], Buffer.from('new-cert-data'), 'new certificate should be added')
  })

  test('converts existing key non-array to array and adds new key', ({ assert }) => {
    const existingKey: tls.KeyObject = {
      pem: Buffer.from('existing-key'),
    }
    options.key = existingKey as unknown as (string | Buffer | tls.KeyObject)[]

    const cert: IPemCertificate = {
      kind: 'Core#Certificate',
      key: 'test-key',
      name: 'Test PEM',
      type: 'pem',
      cert: {
        data: Buffer.from('new-cert-data'),
      },
      certKey: {
        data: Buffer.from('new-key-data'),
      },
    }

    addClientCertificate(cert, options)

    assert.isArray(options.key, 'key should be converted to array')
    assert.lengthOf(options.key as tls.KeyObject[], 2, 'key should contain both keys')
    assert.deepEqual((options.key as tls.KeyObject[])[0], existingKey, 'existing key should be preserved')
    assert.deepEqual((options.key as tls.KeyObject[])[1].pem, Buffer.from('new-key-data'), 'new key should be added')
  })

  test('adds certificate to existing cert array', ({ assert }) => {
    const existingCert = Buffer.from('existing-cert')
    options.cert = [existingCert]

    const cert: IPemCertificate = {
      kind: 'Core#Certificate',
      key: 'test-key',
      name: 'Test PEM',
      type: 'pem',
      cert: {
        data: Buffer.from('new-cert-data'),
      },
      certKey: {
        data: Buffer.from('new-key-data'),
      },
    }

    addClientCertificate(cert, options)

    assert.lengthOf(options.cert as Buffer[], 2, 'cert should contain both certificates')
    assert.deepEqual((options.cert as Buffer[])[0], existingCert, 'existing certificate should be preserved')
    assert.deepEqual((options.cert as Buffer[])[1], Buffer.from('new-cert-data'), 'new certificate should be added')
  })

  test('handles Uint8Array data correctly', ({ assert }) => {
    const certData = new Uint8Array([1, 2, 3, 4, 5])
    const keyData = new Uint8Array([6, 7, 8, 9, 10])

    const cert: IPemCertificate = {
      kind: 'Core#Certificate',
      key: 'test-key',
      name: 'Test PEM Uint8Array',
      type: 'pem',
      cert: {
        data: certData,
      },
      certKey: {
        data: keyData,
      },
    }

    addClientCertificate(cert, options)

    assert.deepEqual(
      (options.cert as Buffer[])[0],
      Buffer.from(certData),
      'Uint8Array cert data should be converted to Buffer'
    )
    assert.deepEqual(
      (options.key as tls.KeyObject[])[0].pem,
      Buffer.from(keyData),
      'Uint8Array key data should be converted to Buffer'
    )
  })

  test('adds pem certificate without certKey', ({ assert }) => {
    const cert: HttpCertificate = {
      kind: 'Core#Certificate',
      key: 'test-key',
      name: 'Test PEM without key',
      type: 'pem',
      cert: {
        data: Buffer.from('test-cert-data'),
      },
    }

    addClientCertificate(cert, options)

    assert.isArray(options.cert, 'cert should be an array')
    assert.lengthOf(options.cert as Buffer[], 1, 'cert should contain one certificate')
    assert.isUndefined(options.key, 'key should not be set when certKey is not provided')
  })

  test('does not mutate original certificate object', ({ assert }) => {
    const originalCert: IP12Certificate = {
      kind: 'Core#Certificate',
      key: 'test-key',
      name: 'Test P12',
      type: 'p12',
      cert: {
        data: Buffer.from('test-cert-data'),
        passphrase: 'original-passphrase',
      },
    }

    const certCopy = structuredClone(originalCert)
    addClientCertificate(originalCert, options)

    assert.deepEqual(originalCert, certCopy, 'original certificate should not be mutated')
  })
})

test.group('checkServerIdentity()', (group) => {
  let mockCheckServerIdentity: sinon.SinonStub

  group.setup(() => {
    mockCheckServerIdentity = sinon.stub(tls, 'checkServerIdentity')
  })

  group.teardown(() => {
    mockCheckServerIdentity.restore()
  })

  group.each.teardown(() => {
    mockCheckServerIdentity.reset()
  })

  test('returns undefined when tls.checkServerIdentity returns undefined', ({ assert }) => {
    const host = 'example.com'
    const cert = { subject: { CN: 'example.com' } } as tls.PeerCertificate

    mockCheckServerIdentity.returns(undefined)

    const result = checkServerIdentity(host, cert)

    assert.isUndefined(result, 'should return undefined when no error')
    assert.isTrue(
      mockCheckServerIdentity.calledOnceWith(host, cert),
      'should call tls.checkServerIdentity with correct arguments'
    )
  })

  test('returns error when tls.checkServerIdentity returns an error', ({ assert }) => {
    const host = 'example.com'
    const cert = { subject: { CN: 'different.com' } } as tls.PeerCertificate
    const expectedError = new Error("Hostname/IP does not match certificate's altnames")

    mockCheckServerIdentity.returns(expectedError)

    const result = checkServerIdentity(host, cert)

    assert.equal(result, expectedError, 'should return the error from tls.checkServerIdentity')
    assert.isTrue(
      mockCheckServerIdentity.calledOnceWith(host, cert),
      'should call tls.checkServerIdentity with correct arguments'
    )
  })

  test('passes through different error types', ({ assert }) => {
    const host = 'example.com'
    const cert = { subject: { CN: 'example.com' } } as tls.PeerCertificate
    const expectedError = new TypeError('Invalid certificate format')

    mockCheckServerIdentity.returns(expectedError)

    const result = checkServerIdentity(host, cert)

    assert.equal(result, expectedError, 'should return any error type from tls.checkServerIdentity')
  })

  test('handles empty host string', ({ assert }) => {
    const host = ''
    const cert = { subject: { CN: 'example.com' } } as tls.PeerCertificate
    const expectedError = new Error('Invalid host')

    mockCheckServerIdentity.returns(expectedError)

    const result = checkServerIdentity(host, cert)

    assert.equal(result, expectedError, 'should handle empty host string')
    assert.isTrue(
      mockCheckServerIdentity.calledOnceWith(host, cert),
      'should call tls.checkServerIdentity with empty host'
    )
  })

  test('handles null certificate', ({ assert }) => {
    const host = 'example.com'

    const result = checkServerIdentity(host, null)
    assert.equal(result?.message, 'Certificate is required', 'should handle null certificate')
  })

  test('verifies function signature and call pattern', ({ assert }) => {
    const host = 'test.example.com'
    const cert = {
      subject: { CN: 'test.example.com' },
      issuer: { CN: 'Test CA' },
      valid_from: '2024-01-01',
      valid_to: '2025-01-01',
    } as tls.PeerCertificate

    mockCheckServerIdentity.returns(undefined)

    checkServerIdentity(host, cert)

    assert.isTrue(mockCheckServerIdentity.calledOnce, 'should call tls.checkServerIdentity exactly once')
    const [calledHost, calledCert] = mockCheckServerIdentity.firstCall.args
    assert.equal(calledHost, host, 'should pass correct host parameter')
    assert.equal(calledCert, cert, 'should pass correct certificate parameter')
  })
})
