import net from 'net'
import tls from 'tls'
import { ConnectionOptions } from './ConnectionManager.js'
import type { HttpCertificate } from '../../../models/ClientCertificate.js'
import { addClientCertificate, checkServerIdentity } from '../certificates/index.js'
import { type Logger, type ILogObj } from '../../../lib/logging/index.js'
import type { RequestStats } from '../CoreEngine.js'

/**
 * Handles direct HTTP and HTTPS connections
 */
export class DirectConnection {
  private options: ConnectionOptions

  constructor(options: ConnectionOptions) {
    this.options = options
  }

  /**
   * Establish a connection to the target host
   */
  async connect(): Promise<net.Socket> {
    const { host, port, timeout, validateCertificates, certificates, logger, stats } = this.options

    let socket: net.Socket
    // Check if this is an HTTPS connection based on port or protocol
    const isHttps = port === 443 || port === 8443 || this.options.protocol === 'https:'
    if (isHttps) {
      socket = await this.createTlsConnection(host, port, validateCertificates, certificates, logger, stats)
    } else {
      socket = await this.createTcpConnection(host, port, logger, stats)
    }

    if (timeout && timeout > 0) {
      socket.setTimeout(timeout)
    }

    return socket
  }

  /**
   * Create a TCP connection for HTTP
   */
  private createTcpConnection(
    host: string,
    port: number,
    logger: Logger<ILogObj>,
    stats: RequestStats
  ): Promise<net.Socket> {
    logger.debug(`Opening HTTP connection to ${host} on port ${port}`)
    return new Promise((resolve, reject) => {
      stats.connectionTime = Date.now()
      const isIp = net.isIP(host)
      if (isIp) {
        stats.lookupTime = Date.now()
      }

      const client = net.createConnection(port, host, () => {
        stats.connectedTime = Date.now()
        logger.debug('HTTP connection established.')
        resolve(client)
      })

      client.pause()
      if (!isIp) {
        client.once('lookup', () => {
          stats.lookupTime = Date.now()
        })
      }
      client.once('error', (err: Error) => reject(err))
    })
  }

  /**
   * Create a TLS connection for HTTPS
   */
  private createTlsConnection(
    host: string,
    port: number,
    validateCertificates: boolean | undefined,
    certificates: HttpCertificate[] | undefined,
    logger: Logger<ILogObj>,
    stats: RequestStats
  ): Promise<tls.TLSSocket> {
    logger.debug('Opening an SSL connection...')
    const options: tls.ConnectionOptions = {}
    const isIp = net.isIP(host)

    if (!isIp) {
      options.servername = host
    }

    if (validateCertificates) {
      options.checkServerIdentity = (hostname: string, cert: tls.PeerCertificate) => checkServerIdentity(hostname, cert)
    } else {
      options.rejectUnauthorized = false
      options.checkServerIdentity = (): Error | undefined => {
        return undefined
      }
    }

    // Add client certificates if provided
    if (Array.isArray(certificates)) {
      certificates.forEach((cert) => addClientCertificate(cert, options))
    }

    return new Promise((resolve, reject) => {
      const time = Date.now()
      stats.connectionTime = time
      if (isIp) {
        stats.lookupTime = time
      }

      const client = tls.connect(port, host, options, () => {
        logger.debug('SSL connection established.')
        const connectTime = Date.now()
        stats.connectedTime = connectTime
        stats.secureStartTime = connectTime
        resolve(client)
      })

      client.pause()
      client.once('error', (e: Error) => reject(e))
      if (!isIp) {
        client.once('lookup', () => {
          stats.lookupTime = Date.now()
        })
      }
      client.once('secureConnect', () => {
        stats.secureConnectedTime = Date.now()
      })
    })
  }
}
