import * as fs from 'node:fs'
import { IncomingMessage, Server, ServerResponse } from 'node:http'
import P from 'pino'
import { expect } from 'tstyche'
import fastify, {
  FastifyBaseLogger,
  FastifyError,
  FastifyLogFn,
  FastifyReply,
  FastifyRequest,
  LogLevel
} from '../../fastify.js'
import { ResSerializerReply } from '../../types/logger.js'

expect(fastify().log).type.toBe<FastifyBaseLogger>()

class Foo { }

['trace', 'debug', 'info', 'warn', 'error', 'fatal'].forEach(logLevel => {
  expect(
    fastify<Server, IncomingMessage, ServerResponse, FastifyBaseLogger>().log[logLevel as LogLevel]
  ).type.toBe<FastifyLogFn>()
  expect(
    fastify<Server, IncomingMessage, ServerResponse, FastifyBaseLogger>().log[logLevel as LogLevel]('')
  ).type.toBe<void>()
  expect(
    fastify<Server, IncomingMessage, ServerResponse, FastifyBaseLogger>().log[logLevel as LogLevel]({})
  ).type.toBe<void>()
  expect(
    fastify<Server, IncomingMessage, ServerResponse, FastifyBaseLogger>().log[logLevel as LogLevel]({ foo: 'bar' })
  ).type.toBe<void>()
  expect(
    fastify<Server, IncomingMessage, ServerResponse, FastifyBaseLogger>().log[logLevel as LogLevel](new Error())
  ).type.toBe<void>()
  expect(
    fastify<Server, IncomingMessage, ServerResponse, FastifyBaseLogger>().log[logLevel as LogLevel](new Foo())
  ).type.toBe<void>()
})

interface CustomLogger extends FastifyBaseLogger {
  customMethod(msg: string, ...args: unknown[]): void;
}

class CustomLoggerImpl implements CustomLogger {
  level = 'info'
  customMethod (msg: string, ...args: unknown[]) { console.log(msg, args) }

  // Implementation signature must be compatible with all overloads of FastifyLogFn
  info (arg1: unknown, arg2?: unknown, ...args: unknown[]): void {
    console.log(arg1, arg2, ...args)
  }

  warn (...args: unknown[]) { console.log(args) }
  error (...args: unknown[]) { console.log(args) }
  fatal (...args: unknown[]) { console.log(args) }
  trace (...args: unknown[]) { console.log(args) }
  debug (...args: unknown[]) { console.log(args) }
  silent (...args: unknown[]) { }

  child (bindings: P.Bindings, options?: P.ChildLoggerOptions): CustomLoggerImpl { return new CustomLoggerImpl() }
}

const customLogger = new CustomLoggerImpl()

const serverWithCustomLogger = fastify<
  Server,
  IncomingMessage,
  ServerResponse,
  CustomLoggerImpl
>({ logger: customLogger })

expect(serverWithCustomLogger.log).type.toBe<CustomLoggerImpl>()

const serverWithPino = fastify<
  Server,
  IncomingMessage,
  ServerResponse,
  P.Logger
>({
  logger: P({
    level: 'info',
    redact: ['x-userinfo']
  })
})

expect(serverWithPino.log).type.toBe<P.Logger>()

serverWithPino.route({
  method: 'GET',
  url: '/',
  handler (request) {
    expect(this.log).type.toBe<P.Logger>()
    expect(request.log).type.toBe<P.Logger>()
  }
})

serverWithPino.get('/', function (request) {
  expect(this.log).type.toBe<P.Logger>()
  expect(request.log).type.toBe<P.Logger>()
})

const serverWithLogOptions = fastify<
  Server,
  IncomingMessage,
  ServerResponse
>({
  logger: {
    level: 'info'
  }
})

expect(serverWithLogOptions.log).type.toBe<FastifyBaseLogger>()

const serverWithFileOption = fastify<
  Server,
  IncomingMessage,
  ServerResponse
>({
  logger: {
    level: 'info',
    file: '/path/to/file'
  }
})

expect(serverWithFileOption.log).type.toBe<FastifyBaseLogger>()

const serverAutoInferringTypes = fastify({
  logger: {
    level: 'info'
  }
})

expect(serverAutoInferringTypes.log).type.toBe<FastifyBaseLogger>()

const serverWithLoggerInstance = fastify({
  loggerInstance: P({
    level: 'info',
    redact: ['x-userinfo']
  })
})

expect(serverWithLoggerInstance.log).type.toBe<P.Logger>()

const serverWithPinoConfig = fastify({
  logger: {
    level: 'info',
    serializers: {
      req (request) {
        expect(request).type.toBe<FastifyRequest>()
        return {
          method: 'method',
          url: 'url',
          version: 'version',
          host: 'fastify.test',
          remoteAddress: 'remoteAddress',
          remotePort: 80,
          other: ''
        }
      },
      res (reply) {
        expect(reply).type.toBe<ResSerializerReply<Server, FastifyReply>>()
        expect(reply).type.toBeAssignableTo<Partial<FastifyReply> & Pick<FastifyReply, 'statusCode'>>()
        expect(reply).type.not.toBeAssignableTo<FastifyReply>()
        return {
          statusCode: 'statusCode'
        }
      },
      err (error) {
        expect(error).type.toBe<FastifyError>()

        return {
          other: '',
          type: 'type',
          message: 'msg',
          stack: 'stack'
        }
      }
    }
  }
})

expect(serverWithPinoConfig.log).type.toBe<FastifyBaseLogger>()

const serverAutoInferredFileOption = fastify({
  logger: {
    level: 'info',
    file: '/path/to/file'
  }
})

expect(serverAutoInferredFileOption.log).type.toBe<FastifyBaseLogger>()

const serverAutoInferredSerializerResponseObjectOption = fastify({
  logger: {
    serializers: {
      res (reply) {
        expect(reply).type.toBe<ResSerializerReply<Server, FastifyReply>>()
        expect(reply).type.toBeAssignableTo<Partial<FastifyReply> & Pick<FastifyReply, 'statusCode'>>()
        expect(reply).type.not.toBeAssignableTo<FastifyReply>()
        return {
          status: '200'
        }
      }
    }
  }
})

expect(serverAutoInferredSerializerResponseObjectOption.log).type.toBe<FastifyBaseLogger>()

const serverAutoInferredSerializerObjectOption = fastify({
  logger: {
    serializers: {
      req (request) {
        expect(request).type.toBe<FastifyRequest>()
        return {
          method: 'method',
          url: 'url',
          version: 'version',
          host: 'fastify.test',
          remoteAddress: 'remoteAddress',
          remotePort: 80,
          other: ''
        }
      },
      res (reply) {
        expect(reply).type.toBe<ResSerializerReply<Server, FastifyReply>>()
        expect(reply).type.toBeAssignableTo<Partial<FastifyReply> & Pick<FastifyReply, 'statusCode'>>()
        expect(reply).type.not.toBeAssignableTo<FastifyReply>()
        return {
          statusCode: 'statusCode'
        }
      },
      err (FastifyError) {
        return {
          other: '',
          type: 'type',
          message: 'msg',
          stack: 'stack'
        }
      }
    }
  }
})

expect(serverAutoInferredSerializerObjectOption.log).type.toBe<FastifyBaseLogger>()

const passStreamAsOption = fastify({
  logger: {
    stream: fs.createWriteStream('/tmp/stream.out')
  }
})

expect(passStreamAsOption.log).type.toBe<FastifyBaseLogger>()

const passPinoOption = fastify({
  logger: {
    redact: ['custom'],
    messageKey: 'msg',
    nestedKey: 'nested',
    enabled: true
  }
})

expect(passPinoOption.log).type.toBe<FastifyBaseLogger>()

const childParent = fastify().log
// we test different option variant here
expect(childParent.child({}, { level: 'info' })).type.toBe<FastifyBaseLogger>()
expect(childParent.child({}, { level: 'silent' })).type.toBe<FastifyBaseLogger>()
expect(childParent.child({}, { redact: ['pass', 'pin'] })).type.toBe<FastifyBaseLogger>()
expect(childParent.child({}, { serializers: { key: () => { } } })).type.toBe<FastifyBaseLogger>()
expect(childParent.child({}, { level: 'info', redact: ['pass', 'pin'], serializers: { key: () => { } } })).type.toBe<FastifyBaseLogger>()

expect(childParent.child).type.not.toBeCallableWith()

expect(childParent.child).type.not.toBeCallableWith({}, { nonExist: true })
