import { Headers } from '../../../lib/headers/Headers.js'
import { IHttpRequest } from '../../../models/HttpRequest.js'
import * as PayloadSupport from '../PayloadSupport.js'
import { HTTP_VERSION, CRLF_BUFFER } from '../constants.js'
import { type Logger, type ILogObj } from '../../../lib/logging/index.js'

export interface MessageBuilderOptions {
  logger: Logger<ILogObj>
  request: IHttpRequest
  hasProxy: boolean
  isProxyTunnel: boolean
  hostHeader?: string
}

export interface MessageBuilderContext {
  headers: Headers
  buffer?: Buffer
  statusLine: string
  hostHeader?: string
}

/**
 * Builds HTTP messages for requests
 */
export class MessageBuilder {
  private logger: Logger<ILogObj>
  private request: IHttpRequest
  private hasProxy: boolean
  private isProxyTunnel: boolean
  private hostHeader?: string
  private hostTestReg = /^\s*host\s*:/im

  constructor(options: MessageBuilderOptions) {
    this.logger = options.logger
    this.request = options.request
    this.hasProxy = options.hasProxy
    this.isProxyTunnel = options.isProxyTunnel
    this.hostHeader = options.hostHeader
  }

  /**
   * Builds a complete HTTP message
   */
  buildMessage(headers: Headers, buffer?: Buffer): Buffer {
    this.logger.debug('Preparing HTTP message...')

    const context = this.createMessageContext(headers, buffer)
    const messageBuffer = this.assembleMessage(context)

    this.logger.debug('The message is ready.')
    return messageBuffer
  }

  /**
   * Creates the message context with all components
   */
  private createMessageContext(headers: Headers, buffer?: Buffer): MessageBuilderContext {
    const statusLine = this.createStatusLine()
    this.logger.debug(`Created message status: ${statusLine}`)

    const hostHeader = this.getHostHeader()
    if (hostHeader) {
      this.logger.debug(`Adding the "host" header: ${hostHeader}`)
    }

    return {
      headers,
      buffer,
      statusLine,
      hostHeader,
    }
  }

  /**
   * Assembles the complete HTTP message from components
   */
  private assembleMessage(context: MessageBuilderContext): Buffer {
    const { headers, buffer, statusLine, hostHeader } = context

    // Build the header lines
    const headerLines = [statusLine]

    if (hostHeader) {
      headerLines.push(`Host: ${hostHeader}`)
    }

    // Add custom headers
    const customHeaders = headers.toString()
    if (customHeaders) {
      this.logger.debug('Adding headers to the request...')
      this.logger.silly('Headers:', [...headers.keys()])
      headerLines.push(PayloadSupport.normalizeString(customHeaders))
    }

    // Join all header lines
    const headerString = headerLines.join('\r\n')

    // Create buffers
    const startBuffer = Buffer.from(headerString, 'utf8')
    const endBuffer = CRLF_BUFFER

    // Calculate total size
    let totalSize = startBuffer.length + endBuffer.length
    if (buffer) {
      totalSize += buffer.length
    }

    // Assemble the final message
    if (buffer) {
      return Buffer.concat([startBuffer, endBuffer, buffer], totalSize)
    } else {
      return Buffer.concat([startBuffer, endBuffer], totalSize)
    }
  }

  /**
   * Creates the HTTP status line
   */
  private createStatusLine(): string {
    const { request, hasProxy, isProxyTunnel } = this
    const parts = [request.method]

    if (hasProxy && !isProxyTunnel) {
      // For proxy requests, use the full URL
      parts.push(request.url)
    } else {
      // For direct requests, use the path
      const uri = new URL(request.url)
      let path = uri.pathname
      if (uri.search) {
        path += uri.search
      }
      parts.push(path)
    }

    parts.push(HTTP_VERSION)
    return parts.join(' ')
  }

  /**
   * Determines if a host header is required and returns it
   */
  private getHostHeader(): string | undefined {
    if (!this.hostRequired()) {
      return undefined
    }
    return this.hostHeader
  }

  /**
   * Tests if current connection is required to add `host` header.
   * It returns `false` only if `host` header has been already provided.
   */
  private hostRequired(): boolean {
    const headers = this.request.headers
    if (typeof headers !== 'string') {
      return true
    }
    return !this.hostTestReg.test(headers)
  }

  /**
   * Updates the host header for the message builder
   */
  updateHostHeader(hostHeader: string): void {
    this.hostHeader = hostHeader
  }

  /**
   * Creates a status line for a specific request
   */
  createStatusLineForRequest(request: IHttpRequest, hasProxy: boolean, isProxyTunnel: boolean): string {
    const parts = [request.method]

    if (hasProxy && !isProxyTunnel) {
      parts.push(request.url)
    } else {
      const uri = new URL(request.url)
      let path = uri.pathname
      if (uri.search) {
        path += uri.search
      }
      parts.push(path)
    }

    parts.push(HTTP_VERSION)
    return parts.join(' ')
  }

  /**
   * Validates if a request needs a host header
   */
  static hostRequiredForRequest(request: IHttpRequest): boolean {
    const hostTestReg = /^\s*host\s*:/im
    const headers = request.headers
    if (typeof headers !== 'string') {
      return true
    }
    return !hostTestReg.test(headers)
  }
}
