import { SerializableError } from '../../../models/SerializableError.js'
import { type Logger, type ILogObj } from '../../../lib/logging/index.js'
import { DigestAuthHandler } from './DigestAuthHandler.js'

export interface ProxyAuthOptions {
  proxyUsername?: string
  proxyPassword?: string
  /**
   * The type of proxy authentication to use.
   */
  proxyAuthorization?: 'Basic'
  logger: Logger<ILogObj>
}

export interface ProxyAuthChallenge {
  method: string
  realm?: string
  nonce?: string
  qop?: string
  algorithm?: string
}

/**
 * Handles proxy authentication challenges and responses
 */
export class ProxyAuthHandler {
  private options: ProxyAuthOptions
  private authState: 'none' | 'challenged' | 'authenticated' = 'none'
  private challenge?: ProxyAuthChallenge
  private digestHandler?: DigestAuthHandler

  constructor(options: ProxyAuthOptions) {
    this.options = options
    if (options.proxyUsername && options.proxyPassword) {
      this.digestHandler = new DigestAuthHandler(options.proxyUsername, options.proxyPassword, options.logger)
    }
  }

  /**
   * Check if we have proxy credentials configured
   */
  hasCredentials(): boolean {
    const { proxyUsername, proxyPassword, proxyAuthorization } = this.options
    return !!(proxyUsername && proxyPassword) || !!proxyAuthorization
  }

  /**
   * Generate the initial proxy authorization header
   */
  generateAuthHeader(): string | undefined {
    const { proxyUsername, proxyPassword, proxyAuthorization } = this.options

    if (!proxyUsername || !proxyPassword) {
      return undefined
    }

    if (!proxyAuthorization || proxyAuthorization === 'Basic') {
      // Basic authentication
      const token = Buffer.from(`${proxyUsername}:${proxyPassword}`).toString('base64')
      return `Basic ${token}`
    }

    if (proxyAuthorization === 'Digest') {
      // For Digest auth, we need a challenge first
      if (this.authState === 'none') {
        // Initial request without auth header
        return undefined
      }
      return this.generateDigestResponse()
    }

    throw new SerializableError(`Unsupported proxy authorization type: ${proxyAuthorization}`)
  }

  /**
   * Handle a proxy authentication challenge
   */
  handleChallenge(authenticateHeader: string): string | undefined {
    this.authState = 'challenged'
    this.challenge = this.parseChallenge(authenticateHeader)
    this.options.logger.debug('Received proxy authentication challenge:', this.challenge)

    if (this.challenge?.method === 'Basic') {
      // For Basic auth, we can respond immediately
      return this.generateAuthHeader()
    } else if (this.challenge?.method === 'Digest') {
      // For Digest auth, we need to generate a proper response
      return this.generateDigestResponse()
    }

    throw new SerializableError(`Unsupported proxy authentication method: ${this.challenge?.method}`)
  }

  /**
   * Parse the WWW-Authenticate or Proxy-Authenticate header
   */
  private parseChallenge(authenticateHeader: string): ProxyAuthChallenge {
    const challenge: ProxyAuthChallenge = { method: '' }

    // Extract the method (Basic, Digest, etc.)
    const methodMatch = authenticateHeader.match(/^(\w+)/)
    if (methodMatch) {
      challenge.method = methodMatch[1]
    }

    // Parse key-value pairs
    const paramRegex = /(\w+)="([^"]*)"/g
    let match
    while ((match = paramRegex.exec(authenticateHeader)) !== null) {
      const [, key, value] = match
      switch (key.toLowerCase()) {
        case 'realm':
          challenge.realm = value
          break
        case 'nonce':
          challenge.nonce = value
          break
        case 'qop':
          challenge.qop = value
          break
        case 'algorithm':
          challenge.algorithm = value
          break
      }
    }

    return challenge
  }

  /**
   * Generate a Digest authentication response
   */
  private generateDigestResponse(): string | undefined {
    if (!this.digestHandler || !this.challenge) {
      return undefined
    }

    try {
      // Parse the full Digest challenge
      const digestChallenge = this.digestHandler.parseChallenge(
        `Digest realm="${this.challenge.realm}", nonce="${this.challenge.nonce}"${
          this.challenge.qop ? `, qop="${this.challenge.qop}"` : ''
        }${this.challenge.algorithm ? `, algorithm="${this.challenge.algorithm}"` : ''}`
      )

      // Generate the response for CONNECT method
      const response = this.digestHandler.generateResponse(digestChallenge, 'CONNECT', '/')
      return response
    } catch (error) {
      this.options.logger.error('Failed to generate Digest response:', error)
      throw new SerializableError(`Digest authentication failed: ${(error as Error).message}`, 127)
    }
  }

  /**
   * Check if authentication was successful
   */
  isAuthenticated(): boolean {
    return this.authState === 'authenticated'
  }

  /**
   * Mark authentication as successful
   */
  markAuthenticated(): void {
    this.authState = 'authenticated'
  }

  /**
   * Reset authentication state
   */
  reset(): void {
    this.authState = 'none'
    this.challenge = undefined
    if (this.digestHandler) {
      this.digestHandler.reset()
    }
  }
}
