import { Duplex, Readable, Writable } from 'node:stream'
import { expectAssignable, expectType } from 'tsd'
import { Client, Dispatcher } from '../..'
import { URL } from 'node:url'

expectAssignable<Client>(new Client(''))
expectAssignable<Client>(new Client('', {}))
expectAssignable<Client>(
  new Client('', {
    maxRequestsPerClient: 10
  })
)
expectAssignable<Client>(
  new Client('', {
    connect: { rejectUnauthorized: false }
  })
)
expectAssignable<Client>(new Client(new URL('http://localhost'), {}))

/**
 * Tests for Client.Options:
 */
expectAssignable<Client>(
  new Client('', {
    maxHeaderSize: 16384
  })
)
expectAssignable<Client>(
  new Client('', {
    headersTimeout: 300e3
  })
)
expectAssignable<Client>(
  new Client('', {
    connectTimeout: 300e3
  })
)
expectAssignable<Client>(
  new Client('', {
    bodyTimeout: 300e3
  })
)
expectAssignable<Client>(
  new Client('', {
    keepAliveTimeout: 4e3
  })
)
expectAssignable<Client>(
  new Client('', {
    keepAliveMaxTimeout: 600e3
  })
)
expectAssignable<Client>(
  new Client('', {
    keepAliveTimeoutThreshold: 1e3
  })
)
expectAssignable<Client>(
  new Client('', {
    socketPath: '/var/run/docker.sock'
  })
)
expectAssignable<Client>(
  new Client('', {
    pipelining: 1
  })
)
expectAssignable<Client>(
  new Client('', {
    strictContentLength: true
  })
)
expectAssignable<Client>(
  new Client('', {
    maxCachedSessions: 1
  })
)
expectAssignable<Client>(
  new Client('', {
    maxCachedSessions: 1
  })
)
expectAssignable<Client>(
  new Client('', {
    maxRequestsPerClient: 1
  })
)
expectAssignable<Client>(
  new Client('', {
    localAddress: '127.0.0.1'
  })
)
expectAssignable<Client>(
  new Client('', {
    maxResponseSize: -1
  })
)
expectAssignable<Client>(
  new Client('', {
    autoSelectFamily: true
  })
)
expectAssignable<Client>(
  new Client('', {
    autoSelectFamilyAttemptTimeout: 300e3
  })
)

// TODO: (@metcoder95) - remove in further major
expectAssignable<Client>(
  new Client('', {
    allowH2: true
  })
)
expectAssignable<Client>(
  new Client('', {
    maxConcurrentStreams: 100
  })
)
expectAssignable<Client>(
  new Client('', {
    initialWindowSize: 262144
  })
)
expectAssignable<Client>(
  new Client('', {
    connectionWindowSize: 524288
  })
)
expectAssignable<Client>(
  new Client('', {
    pingInterval: 60e3
  })
)
expectAssignable<Client>(
  new Client('', {
    h2Options: {
      useH2c: false,
      pingInterval: 60e3,
      connectionWindowSize: 524288,
      maxConcurrentStreams: 100,
      settings: {
        initialWindowSize: 262144
      }
    }
  })
)

{
  const client = new Client('')

  // properties
  expectAssignable<number>(client.pipelining)
  expectAssignable<boolean>(client.closed)
  expectAssignable<boolean>(client.destroyed)

  // request
  expectAssignable<Promise<Dispatcher.ResponseData>>(
    client.request({ origin: '', path: '', method: 'GET' })
  )
  expectAssignable<Promise<Dispatcher.ResponseData>>(
    client.request({
      origin: new URL('http://localhost:3000'),
      path: '',
      method: 'GET'
    })
  )
  expectAssignable<void>(
    client.request({ origin: '', path: '', method: 'GET' }, (err, data) => {
      expectAssignable<Error | null>(err)
      expectAssignable<Dispatcher.ResponseData>(data)
    })
  )
  expectAssignable<void>(
    client.request(
      { origin: new URL('http://localhost:3000'), path: '', method: 'GET' },
      (err, data) => {
        expectAssignable<Error | null>(err)
        expectAssignable<Dispatcher.ResponseData>(data)
      }
    )
  )

  // stream
  expectAssignable<Promise<Dispatcher.StreamData>>(
    client.stream({ origin: '', path: '', method: 'GET' }, (data) => {
      expectAssignable<Dispatcher.StreamFactoryData>(data)
      return new Writable()
    })
  )
  expectAssignable<Promise<Dispatcher.StreamData>>(
    client.stream(
      { origin: new URL('http://localhost'), path: '', method: 'GET' },
      (data) => {
        expectAssignable<Dispatcher.StreamFactoryData>(data)
        return new Writable()
      }
    )
  )
  expectAssignable<void>(
    client.stream(
      { origin: '', path: '', method: 'GET' },
      (data) => {
        expectAssignable<Dispatcher.StreamFactoryData>(data)
        return new Writable()
      },
      (err, data) => {
        expectAssignable<Error | null>(err)
        expectAssignable<Dispatcher.StreamData>(data)
      }
    )
  )
  expectAssignable<void>(
    client.stream(
      { origin: new URL('http://localhost'), path: '', method: 'GET' },
      (data) => {
        expectAssignable<Dispatcher.StreamFactoryData>(data)
        return new Writable()
      },
      (err, data) => {
        expectAssignable<Error | null>(err)
        expectAssignable<Dispatcher.StreamData>(data)
      }
    )
  )

  // pipeline
  expectAssignable<Duplex>(
    client.pipeline({ origin: '', path: '', method: 'GET' }, (data) => {
      expectAssignable<Dispatcher.PipelineHandlerData>(data)
      return new Readable()
    })
  )
  expectAssignable<Duplex>(
    client.pipeline(
      { origin: new URL('http://localhost'), path: '', method: 'GET' },
      (data) => {
        expectAssignable<Dispatcher.PipelineHandlerData>(data)
        return new Readable()
      }
    )
  )

  // upgrade
  expectAssignable<Promise<Dispatcher.UpgradeData>>(
    client.upgrade({ path: '' })
  )
  expectAssignable<Promise<Dispatcher.UpgradeData>>(
    client.upgrade({ path: '', headers: [] })
  )
  expectAssignable<Promise<Dispatcher.UpgradeData>>(
    client.upgrade({ path: '', headers: {} })
  )
  expectAssignable<Promise<Dispatcher.UpgradeData>>(
    client.upgrade({ path: '', headers: null })
  )
  expectAssignable<void>(
    client.upgrade({ path: '' }, (err, data) => {
      expectAssignable<Error | null>(err)
      expectAssignable<Dispatcher.UpgradeData>(data)
    })
  )

  // connect
  expectAssignable<Promise<Dispatcher.ConnectData>>(
    client.connect({ path: '' })
  )
  expectAssignable<Promise<Dispatcher.ConnectData>>(
    client.connect({ path: '', headers: [] })
  )
  expectAssignable<Promise<Dispatcher.ConnectData>>(
    client.connect({ path: '', headers: {} })
  )
  expectAssignable<Promise<Dispatcher.ConnectData>>(
    client.connect({ path: '', headers: null })
  )
  expectAssignable<void>(
    client.connect({ path: '' }, (err, data) => {
      expectAssignable<Error | null>(err)
      expectAssignable<Dispatcher.ConnectData>(data)
    })
  )

  // dispatch
  expectAssignable<boolean>(
    client.dispatch({ origin: '', path: '', method: 'GET' }, {})
  )
  expectAssignable<boolean>(
    client.dispatch({ origin: '', path: '', method: 'GET', headers: [] }, {})
  )
  expectAssignable<boolean>(
    client.dispatch({ origin: '', path: '', method: 'GET', headers: {} }, {})
  )
  expectAssignable<boolean>(
    client.dispatch({ origin: '', path: '', method: 'GET', headers: null }, {})
  )
  expectAssignable<boolean>(
    client.dispatch(
      { origin: new URL('http://localhost'), path: '', method: 'GET' },
      {}
    )
  )

  // close
  expectAssignable<Promise<void>>(client.close())
  expectAssignable<void>(client.close(() => {}))

  // destroy
  expectAssignable<Promise<void>>(client.destroy())
  expectAssignable<Promise<void>>(client.destroy(new Error()))
  expectAssignable<Promise<void>>(client.destroy(null))
  expectAssignable<void>(client.destroy(() => {}))
  expectAssignable<void>(client.destroy(new Error(), () => {}))
  expectAssignable<void>(client.destroy(null, () => {}))

  // stats
  expectType<boolean>(client.stats.connected)
  expectType<number>(client.stats.pending)
  expectType<number>(client.stats.running)
  expectType<number>(client.stats.size)

  // opaque
  expectType<Promise<Dispatcher.ConnectData<number>>>(
    client.connect({ opaque: 123, path: '' })
  )
  expectType<Promise<Dispatcher.ConnectData<null>>>(
    client.connect({ path: '' })
  )
  expectType<Promise<Dispatcher.ConnectData<{ foo: string }>>>(
    client.connect({ opaque: { foo: 'bar' }, path: '' })
  )
}
