import net from 'net'
import { type Logger, type ILogObj } from '../../../lib/logging/index.js'
import type { RequestStats, HttpEngineOptions } from '../CoreEngine.js'
import { DirectConnection } from './DirectConnection.js'
import { ProxyConnection } from './ProxyConnection.js'
import { TunnelConnection } from './TunnelConnection.js'
import { HttpCertificate } from '../../../models/ClientCertificate.js'

export interface ConnectionOptions {
  host: string
  port: number
  protocol?: string
  timeout?: number
  validateCertificates?: boolean
  certificates?: HttpCertificate[]
  logger: Logger<ILogObj>
  stats: RequestStats
}

export interface ProxyConnectionOptions extends ConnectionOptions {
  proxy: string
  proxyIsSsl: boolean
  targetUrl: string
  proxyUsername?: string
  proxyPassword?: string
  /**
   * The type of proxy authentication to use.
   */
  proxyAuthorization?: 'Basic'
}

export interface TunnelConnectionOptions extends ProxyConnectionOptions {
  targetHost: string
  targetPort: number
}

/**
 * Manages different types of HTTP connections
 */
export class ConnectionManager {
  private logger: Logger<ILogObj>
  private stats: RequestStats

  constructor(logger: Logger<ILogObj>, stats: RequestStats) {
    this.logger = logger
    this.stats = stats
  }

  /**
   * Create a direct connection (HTTP or HTTPS)
   */
  createDirectConnection(options: ConnectionOptions): DirectConnection {
    return new DirectConnection(options)
  }

  /**
   * Create a proxy connection for HTTP requests
   */
  createProxyConnection(options: ProxyConnectionOptions): ProxyConnection {
    return new ProxyConnection(options)
  }

  /**
   * Create a tunnel connection for HTTPS requests through proxy
   */
  createTunnelConnection(options: TunnelConnectionOptions): TunnelConnection {
    return new TunnelConnection(options)
  }

  /**
   * Determine the appropriate connection type and create it
   */
  createConnection(
    host: string,
    port: number,
    protocol: string,
    engineOptions: HttpEngineOptions
  ): Promise<net.Socket> {
    const connectionOptions: ConnectionOptions = {
      host,
      port,
      protocol,
      timeout: engineOptions.timeout,
      validateCertificates: engineOptions.validateCertificates,
      certificates: engineOptions.certificates,
      logger: this.logger,
      stats: this.stats,
    }

    // Direct connection
    if (!engineOptions.proxy) {
      this.logger.debug('Deciding to create a direct connection...')
      const directConnection = this.createDirectConnection(connectionOptions)
      return directConnection.connect()
    }

    // Proxy connection
    const proxyOptions: ProxyConnectionOptions = {
      ...connectionOptions,
      proxy: engineOptions.proxy,
      proxyIsSsl: engineOptions.proxy.startsWith('https:'),
      targetUrl: `${protocol}//${host}:${port}`,
      proxyUsername: engineOptions.proxyUsername,
      proxyPassword: engineOptions.proxyPassword,
      proxyAuthorization: engineOptions.proxyAuthorization,
    }

    // Check if we need a tunnel (HTTPS target through proxy)
    if (protocol === 'https:') {
      this.logger.debug('Deciding to create a tunnel connection through the proxy...')
      const tunnelOptions: TunnelConnectionOptions = {
        ...proxyOptions,
        targetHost: host,
        targetPort: port,
      }
      const tunnelConnection = this.createTunnelConnection(tunnelOptions)
      return tunnelConnection.connect()
    }
    this.logger.debug('Deciding to create a proxy connection...')
    const proxyConnection = this.createProxyConnection(proxyOptions)
    return proxyConnection.connect()
  }
}
