import { FastifyError } from '@fastify/error'
import * as http from 'node:http'
import { expect } from 'tstyche'
import fastify, { FastifyInstance, FastifyReply, FastifyRequest, RouteHandlerMethod } from '../../fastify.js'
import { RequestPayload } from '../../types/hooks.js'
import { FindMyWayFindResult } from '../../types/instance.js'
import { HTTPMethods, RawServerDefault } from '../../types/utils.js'

/*
 * Testing Fastify HTTP Routes and Route Shorthands.
 * Verifies Request and Reply types as well.
 * For the route shorthand tests the argument orders are:
 * - `(path, handler)`
 * - `(path, options, handler)`
 * - `(path, options)`
 */

declare module '../../fastify' {
  interface FastifyContextConfig {
    foo: string;
    bar: number;
    includeMessage?: boolean;
  }

  interface FastifyRequest<
    RouteGeneric,
    RawServer,
    RawRequest,
    SchemaCompiler,
    TypeProvider,
    ContextConfig,
    Logger,
    RequestType
  > {
    message: ContextConfig extends { includeMessage: true }
      ? string
      : null;
  }
}

const routeHandler: RouteHandlerMethod = function (request, reply) {
  expect(this).type.toBe<FastifyInstance>()
  expect(request).type.toBe<FastifyRequest>()
  expect(reply).type.toBe<FastifyReply>()
}

const routeHandlerWithReturnValue: RouteHandlerMethod = function (request, reply) {
  expect(this).type.toBe<FastifyInstance>()
  expect(request).type.toBe<FastifyRequest>()
  expect(reply).type.toBe<FastifyReply>()

  return reply.send()
}

const asyncPreHandler = async (request: FastifyRequest) => {
  expect(request).type.toBe<FastifyRequest>()
}

fastify().get('/', { preHandler: asyncPreHandler }, async () => 'this is an example')

fastify().get(
  '/',
  { config: { foo: 'bar', bar: 100, includeMessage: true } },
  (req) => {
    expect(req.message).type.toBe<string>()
  }
)

fastify().get(
  '/',
  { config: { foo: 'bar', bar: 100, includeMessage: false } },
  (req) => {
    expect(req.message).type.toBe<null>()
  }
)

type LowerCaseHTTPMethods = 'delete' | 'get' | 'head' | 'patch' | 'post' | 'put' |
  'options' | 'query' | 'propfind' | 'proppatch' | 'mkcol' | 'copy' | 'move' | 'lock' |
  'unlock' | 'trace' | 'search' | 'mkcalendar' | 'report'

  ;['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS', 'QUERY', 'PROPFIND',
  'PROPPATCH', 'MKCOL', 'COPY', 'MOVE', 'LOCK', 'UNLOCK', 'TRACE', 'SEARCH', 'MKCALENDAR', 'REPORT'
].forEach(method => {
  // route method
  expect(fastify().route({
    method: method as HTTPMethods,
    url: '/',
    handler: routeHandler
  })).type.toBe<FastifyInstance>()

  const lowerCaseMethod: LowerCaseHTTPMethods = method.toLowerCase() as LowerCaseHTTPMethods

  // method as method
  expect(fastify()[lowerCaseMethod]('/', routeHandler)).type.toBe<FastifyInstance>()
  expect(fastify()[lowerCaseMethod]('/', {}, routeHandler)).type.toBe<FastifyInstance>()
  expect(fastify()[lowerCaseMethod]('/', { handler: routeHandler })).type.toBe<FastifyInstance>()

  expect(fastify()[lowerCaseMethod]('/', {
    handler: routeHandler,
    errorHandler: (error, request, reply) => {
      expect(error).type.toBe<FastifyError>()
      reply.send('error')
    },
    childLoggerFactory: function (logger, bindings, opts) {
      return logger.child(bindings, opts)
    }
  })).type.toBe<FastifyInstance>()

  interface BodyInterface { prop: string }
  interface QuerystringInterface { prop: number }
  interface ParamsInterface { prop: boolean }
  interface HeadersInterface { prop: string }
  interface RouteSpecificContextConfigType {
    extra: boolean
  }
  interface RouteGeneric {
    Body: BodyInterface;
    Querystring: QuerystringInterface;
    Params: ParamsInterface;
    Headers: HeadersInterface;
  }

  fastify()[lowerCaseMethod]<RouteGeneric, RouteSpecificContextConfigType>('/', { config: { foo: 'bar', bar: 100, extra: true } }, (req, res) => {
    expect(req.body).type.toBe<BodyInterface>()
    expect(req.query).type.toBe<QuerystringInterface>()
    expect(req.params).type.toBe<ParamsInterface>()
    expect(req.headers).type.toBe<http.IncomingHttpHeaders & HeadersInterface>()
    expect(req.routeOptions.config.foo).type.toBe<string>()
    expect(req.routeOptions.config.bar).type.toBe<number>()
    expect(req.routeOptions.config.extra).type.toBe<boolean>()
    expect(req.routeOptions.config.url).type.toBe<string>()
    expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
    expect(res.routeOptions.config.foo).type.toBe<string>()
    expect(res.routeOptions.config.bar).type.toBe<number>()
    expect(res.routeOptions.config.extra).type.toBe<boolean>()
    expect(req.routeOptions.config.url).type.toBe<string>()
    expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
  })

  fastify().route<RouteGeneric>({
    url: '/',
    method: method as HTTPMethods,
    config: { foo: 'bar', bar: 100 },
    prefixTrailingSlash: 'slash',
    onRequest: (req, res, done) => { // these handlers are tested in `hooks.test-d.ts`
      expect(req.body).type.toBe<BodyInterface>()
      expect(req.query).type.toBe<QuerystringInterface>()
      expect(req.params).type.toBe<ParamsInterface>()
      expect(req.headers).type.toBe<http.IncomingHttpHeaders & HeadersInterface>()
      expect(req.routeOptions.config.foo).type.toBe<string>()
      expect(req.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(res.routeOptions.config.foo).type.toBe<string>()
      expect(res.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
    },
    preParsing: (req, res, payload, done) => {
      expect(req.body).type.toBe<BodyInterface>()
      expect(req.query).type.toBe<QuerystringInterface>()
      expect(req.params).type.toBe<ParamsInterface>()
      expect(req.headers).type.toBe<http.IncomingHttpHeaders & HeadersInterface>()
      expect(req.routeOptions.config.foo).type.toBe<string>()
      expect(req.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(res.routeOptions.config.foo).type.toBe<string>()
      expect(res.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(payload).type.toBe<RequestPayload>()
      expect(done).type.toBeAssignableTo<(err?: FastifyError | null, res?: RequestPayload) => void>()
      expect(done).type.toBeAssignableTo<(err?: NodeJS.ErrnoException) => void>()
    },
    preValidation: (req, res, done) => {
      expect(req.body).type.toBe<BodyInterface>()
      expect(req.query).type.toBe<QuerystringInterface>()
      expect(req.params).type.toBe<ParamsInterface>()
      expect(req.headers).type.toBe<http.IncomingHttpHeaders & HeadersInterface>()
      expect(req.routeOptions.config.foo).type.toBe<string>()
      expect(req.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(res.routeOptions.config.foo).type.toBe<string>()
      expect(res.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
    },
    preHandler: (req, res, done) => {
      expect(req.body).type.toBe<BodyInterface>()
      expect(req.query).type.toBe<QuerystringInterface>()
      expect(req.params).type.toBe<ParamsInterface>()
      expect(req.headers).type.toBe<http.IncomingHttpHeaders & HeadersInterface>()
      expect(req.routeOptions.config.foo).type.toBe<string>()
      expect(req.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(res.routeOptions.config.foo).type.toBe<string>()
      expect(res.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
    },
    onResponse: (req, res, done) => {
      expect(req.body).type.toBe<BodyInterface>()
      expect(req.query).type.toBe<QuerystringInterface>()
      expect(req.params).type.toBe<ParamsInterface>()
      expect(req.headers).type.toBe<http.IncomingHttpHeaders & HeadersInterface>()
      expect(req.routeOptions.config.foo).type.toBe<string>()
      expect(req.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(res.routeOptions.config.foo).type.toBe<string>()
      expect(res.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(res.statusCode).type.toBe<number>()
    },
    onError: (req, res, error, done) => {
      expect(req.body).type.toBe<BodyInterface>()
      expect(req.query).type.toBe<QuerystringInterface>()
      expect(req.params).type.toBe<ParamsInterface>()
      expect(req.headers).type.toBe<http.IncomingHttpHeaders & HeadersInterface>()
      expect(req.routeOptions.config.foo).type.toBe<string>()
      expect(req.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(res.routeOptions.config.foo).type.toBe<string>()
      expect(res.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
    },
    preSerialization: (req, res, payload, done) => {
      expect(req.body).type.toBe<BodyInterface>()
      expect(req.query).type.toBe<QuerystringInterface>()
      expect(req.params).type.toBe<ParamsInterface>()
      expect(req.headers).type.toBe<http.IncomingHttpHeaders & HeadersInterface>()
      expect(req.routeOptions.config.foo).type.toBe<string>()
      expect(req.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(res.routeOptions.config.foo).type.toBe<string>()
      expect(res.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
    },
    onSend: (req, res, payload, done) => {
      expect(req.body).type.toBe<BodyInterface>()
      expect(req.query).type.toBe<QuerystringInterface>()
      expect(req.params).type.toBe<ParamsInterface>()
      expect(req.headers).type.toBe<http.IncomingHttpHeaders & HeadersInterface>()
      expect(req.routeOptions.config.foo).type.toBe<string>()
      expect(req.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(res.routeOptions.config.foo).type.toBe<string>()
      expect(res.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
    },
    handler: (req, res) => {
      expect(req.body).type.toBe<BodyInterface>()
      expect(req.query).type.toBe<QuerystringInterface>()
      expect(req.params).type.toBe<ParamsInterface>()
      expect(req.headers).type.toBe<http.IncomingHttpHeaders & HeadersInterface>()
      expect(req.routeOptions.config.foo).type.toBe<string>()
      expect(req.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(res.routeOptions.config.foo).type.toBe<string>()
      expect(res.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
    }
  })

  fastify().route<RouteGeneric>({
    url: '/',
    method: method as HTTPMethods,
    config: { foo: 'bar', bar: 100 },
    prefixTrailingSlash: 'slash',
    onRequest: async (req, res, done) => { // these handlers are tested in `hooks.test-d.ts`
      expect(req.body).type.toBe<BodyInterface>()
      expect(req.query).type.toBe<QuerystringInterface>()
      expect(req.params).type.toBe<ParamsInterface>()
      expect(req.headers).type.toBe<http.IncomingHttpHeaders & HeadersInterface>()
      expect(req.routeOptions.config.foo).type.toBe<string>()
      expect(req.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(res.routeOptions.config.foo).type.toBe<string>()
      expect(res.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
    },
    preParsing: async (req, res, payload, done) => {
      expect(req.body).type.toBe<BodyInterface>()
      expect(req.query).type.toBe<QuerystringInterface>()
      expect(req.params).type.toBe<ParamsInterface>()
      expect(req.headers).type.toBe<http.IncomingHttpHeaders & HeadersInterface>()
      expect(req.routeOptions.config.foo).type.toBe<string>()
      expect(req.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(res.routeOptions.config.foo).type.toBe<string>()
      expect(res.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(payload).type.toBe<RequestPayload>()
      expect(done).type.toBeAssignableTo<(err?: FastifyError | null, res?: RequestPayload) => void>()
      expect(done).type.toBeAssignableTo<(err?: NodeJS.ErrnoException) => void>()
    },
    preValidation: async (req, res, done) => {
      expect(req.body).type.toBe<BodyInterface>()
      expect(req.query).type.toBe<QuerystringInterface>()
      expect(req.params).type.toBe<ParamsInterface>()
      expect(req.headers).type.toBe<http.IncomingHttpHeaders & HeadersInterface>()
      expect(req.routeOptions.config.foo).type.toBe<string>()
      expect(req.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(res.routeOptions.config.foo).type.toBe<string>()
      expect(res.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
    },
    preHandler: async (req, res, done) => {
      expect(req.body).type.toBe<BodyInterface>()
      expect(req.query).type.toBe<QuerystringInterface>()
      expect(req.params).type.toBe<ParamsInterface>()
      expect(req.headers).type.toBe<http.IncomingHttpHeaders & HeadersInterface>()
      expect(req.routeOptions.config.foo).type.toBe<string>()
      expect(req.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(res.routeOptions.config.foo).type.toBe<string>()
      expect(res.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
    },
    onResponse: async (req, res, done) => {
      expect(req.body).type.toBe<BodyInterface>()
      expect(req.query).type.toBe<QuerystringInterface>()
      expect(req.params).type.toBe<ParamsInterface>()
      expect(req.headers).type.toBe<http.IncomingHttpHeaders & HeadersInterface>()
      expect(req.routeOptions.config.foo).type.toBe<string>()
      expect(req.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(res.routeOptions.config.foo).type.toBe<string>()
      expect(res.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(res.statusCode).type.toBe<number>()
    },
    onError: async (req, res, error, done) => {
      expect(req.body).type.toBe<BodyInterface>()
      expect(req.query).type.toBe<QuerystringInterface>()
      expect(req.params).type.toBe<ParamsInterface>()
      expect(req.headers).type.toBe<http.IncomingHttpHeaders & HeadersInterface>()
      expect(req.routeOptions.config.foo).type.toBe<string>()
      expect(req.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(res.routeOptions.config.foo).type.toBe<string>()
      expect(res.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
    },
    preSerialization: async (req, res, payload, done) => {
      expect(req.body).type.toBe<BodyInterface>()
      expect(req.query).type.toBe<QuerystringInterface>()
      expect(req.params).type.toBe<ParamsInterface>()
      expect(req.headers).type.toBe<http.IncomingHttpHeaders & HeadersInterface>()
      expect(req.routeOptions.config.foo).type.toBe<string>()
      expect(req.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(res.routeOptions.config.foo).type.toBe<string>()
      expect(res.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
    },
    onSend: async (req, res, payload, done) => {
      expect(req.body).type.toBe<BodyInterface>()
      expect(req.query).type.toBe<QuerystringInterface>()
      expect(req.params).type.toBe<ParamsInterface>()
      expect(req.headers).type.toBe<http.IncomingHttpHeaders & HeadersInterface>()
      expect(req.routeOptions.config.foo).type.toBe<string>()
      expect(req.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(res.routeOptions.config.foo).type.toBe<string>()
      expect(res.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
    },
    handler: (req, res) => {
      expect(req.body).type.toBe<BodyInterface>()
      expect(req.query).type.toBe<QuerystringInterface>()
      expect(req.params).type.toBe<ParamsInterface>()
      expect(req.headers).type.toBe<http.IncomingHttpHeaders & HeadersInterface>()
      expect(req.routeOptions.config.foo).type.toBe<string>()
      expect(req.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
      expect(res.routeOptions.config.foo).type.toBe<string>()
      expect(res.routeOptions.config.bar).type.toBe<number>()
      expect(req.routeOptions.config.url).type.toBe<string>()
      expect(req.routeOptions.config.method).type.toBe<HTTPMethods | HTTPMethods[]>()
    }
  })
})

expect(fastify().route({
  url: '/',
  method: 'CONNECT', // not a valid method but could be implemented by the user
  handler: routeHandler
})).type.toBe<FastifyInstance>()

expect(fastify().route({
  url: '/',
  method: 'OPTIONS',
  handler: routeHandler
})).type.toBe<FastifyInstance>()

expect(fastify().route({
  url: '/',
  method: 'OPTION', // OPTION is a typo for OPTIONS
  handler: routeHandler
})).type.toBe<FastifyInstance>()

expect(fastify().route({
  url: '/',
  method: ['GET', 'POST'],
  handler: routeHandler
})).type.toBe<FastifyInstance>()

expect(fastify().route({
  url: '/',
  method: ['GET', 'POST', 'OPTION'], // OPTION is a typo for OPTIONS
  handler: routeHandler
})).type.toBe<FastifyInstance>()

expect(fastify().route({
  url: '/',
  method: 'GET',
  handler: routeHandler,
  schemaErrorFormatter: (errors, dataVar) => new Error('')
})).type.toBe<FastifyInstance>()

expect(fastify().route).type.not.toBeCallableWith({
  url: '/',
  method: 'GET',
  handler: routeHandler,
  schemaErrorFormatter: 500
})

expect(fastify().route({
  url: '/',
  method: 'GET',
  handler: routeHandler,
  prefixTrailingSlash: 'slash'
})).type.toBe<FastifyInstance>()

expect(fastify().route({
  url: '/',
  method: 'GET',
  handler: routeHandler,
  prefixTrailingSlash: 'no-slash'
})).type.toBe<FastifyInstance>()

expect(fastify().route({
  url: '/',
  method: 'GET',
  handler: routeHandler,
  prefixTrailingSlash: 'both'
})).type.toBe<FastifyInstance>()

expect(fastify().route).type.not.toBeCallableWith({
  url: '/',
  method: 'GET',
  handler: routeHandler,
  prefixTrailingSlash: true
})

expect(fastify().route({
  url: '/',
  method: 'GET',
  handler: routeHandlerWithReturnValue
})).type.toBe<FastifyInstance>()

expect(fastify().hasRoute({
  url: '/',
  method: 'GET'
})).type.toBe<boolean>()

expect(fastify().hasRoute({
  url: '/',
  method: 'GET',
  constraints: { version: '1.2.0' }
})).type.toBe<boolean>()

expect(fastify().hasRoute({
  url: '/',
  method: 'GET',
  constraints: { host: 'auth.fastify.test' }
})).type.toBe<boolean>()

expect(fastify().hasRoute({
  url: '/',
  method: 'GET',
  constraints: { host: /.*\.fastify\.test$/ }
})).type.toBe<boolean>()

expect(fastify().hasRoute({
  url: '/',
  method: 'GET',
  constraints: { host: /.*\.fastify\.test$/, version: '1.2.3' }
})).type.toBe<boolean>()

expect(fastify().hasRoute({
  url: '/',
  method: 'GET',
  constraints: {
    // constraints value should accept any value
    number: 12,
    date: new Date(),
    boolean: true,
    function: () => { },
    object: { foo: 'bar' }
  }
})).type.toBe<boolean>()

expect(
  fastify().findRoute({
    url: '/',
    method: 'get'
  })
).type.toBe<Omit<FindMyWayFindResult<RawServerDefault>, 'store'>>()

// we should not expose store
expect(fastify().findRoute({
  url: '/',
  method: 'get'
})).type.not.toHaveProperty('store')

expect(fastify().route({
  url: '/',
  method: 'get',
  handler: routeHandlerWithReturnValue
})).type.toBe<FastifyInstance>()

expect(fastify().route({
  url: '/',
  method: ['put', 'patch'],
  handler: routeHandlerWithReturnValue
})).type.toBe<FastifyInstance>()

expect(fastify().route({
  url: '/',
  method: 'GET',
  handler: (req) => {
    expect(req.routeOptions.method).type.toBe<HTTPMethods | HTTPMethods[]>()
    expect(req.routeOptions.method).type.toBeAssignableTo<string | Array<string>>()
  }
})).type.toBe<FastifyInstance>()

expect(fastify().route({
  url: '/',
  method: ['HEAD', 'GET'],
  handler: (req) => {
    expect(req.routeOptions.method).type.toBe<HTTPMethods | HTTPMethods[]>()
    expect(req.routeOptions.method).type.toBeAssignableTo<string | Array<string>>()
  }
})).type.toBe<FastifyInstance>()
