import { Headers } from '../../../lib/headers/Headers.js'
import { type Logger, type ILogObj } from '../../../lib/logging/index.js'
import type { INtlmAuthorization, IAuthorizationSettingsUnion } from '../../../models/Authorization.js'
import { NtlmAuth } from '../ntlm/NtlmAuth.js'
import type { IRequestAuthState } from '../CoreEngine.js'
import { HttpEngineErrorFactory } from '../errors/index.js'
import {
  HEADER_AUTHORIZATION,
  HEADER_CONNECTION,
  HEADER_WWW_AUTHENTICATE,
  NTLM_PREFIX,
  KEEP_ALIVE,
  BASIC_PREFIX,
} from '../constants.js'

export interface ProxyAuthCredentials {
  proxyUsername?: string
  proxyPassword?: string
  proxyAuthorization?: string
}

export interface AuthOptions {
  logger: Logger<ILogObj>
  proxyCredentials?: ProxyAuthCredentials
}

export interface AuthorizationConfig {
  type: string
  enabled: boolean
  config?: IAuthorizationSettingsUnion
}

/**
 * Manages all authentication logic for HTTP requests
 */
export class AuthManager {
  private logger: Logger<ILogObj>
  private proxyCredentials?: ProxyAuthCredentials
  private authState?: IRequestAuthState

  constructor(options: AuthOptions) {
    this.logger = options.logger
    this.proxyCredentials = options.proxyCredentials
  }

  /**
   * Handle all authorization types for request headers.
   * It applied authorization data defined on the request object.
   */
  handleRequestAuthorization(headers: Headers, authorizationConfig?: AuthorizationConfig[], hostname?: string): void {
    if (!authorizationConfig || !Array.isArray(authorizationConfig)) {
      this.logger.debug(`No authorization data on the request. Skipping setting up authorization.`)
      return
    }

    const enabled = authorizationConfig.filter((i) => i.enabled)
    if (!enabled.length) {
      this.logger.debug(`No enabled authorization data on the request. Skipping setting up authorization.`)
      return
    }

    const ntlm = enabled.find((i) => i.type === 'ntlm')
    if (ntlm && ntlm.config) {
      this.logger.debug(`Found NTLM authorization config.`)
      this.handleNtlmAuth(ntlm.config as unknown as INtlmAuthorization, headers, hostname)
    }

    const basic = enabled.find((i) => i.type === 'basic')
    if (basic && basic.config) {
      this.logger.debug(`Found basic authorization config.`)
      const config = basic.config as { username?: string; password?: string }
      if (config.username && config.password) {
        this.handleBasicAuth(config.username, config.password, headers)
      }
    }

    const bearer = enabled.find((i) => i.type === 'bearer')
    if (bearer && bearer.config) {
      this.logger.debug(`Found bearer authorization config.`)
      const config = bearer.config as { token?: string }
      if (config.token) {
        this.handleBearerAuth(config.token, headers)
      }
    }
  }

  /**
   * Handle NTLM authentication
   */
  handleNtlmAuth(authData: INtlmAuthorization, headers: Headers, hostname?: string): void {
    try {
      const auth = new NtlmAuth(authData)
      if (!this.authState) {
        this.authState = {
          method: 'ntlm',
          state: 0,
          headers: headers.toString(),
        }
        // Use the provided hostname or empty string for Message1
        const msg = auth.createMessage1(hostname || '')
        headers.set(HEADER_AUTHORIZATION, `${NTLM_PREFIX}${msg.toBase64()}`)
        headers.set(HEADER_CONNECTION, KEEP_ALIVE)
        this.logger.debug('Set NTLM message1 header with hostname:', hostname || '')
      } else if (this.authState && this.authState.state === 1) {
        if (!this.authState.challengeHeader) {
          throw new Error('NTLM challenge header is missing')
        }
        // Use the provided hostname or empty string for Message3
        const msg = auth.createMessage3(this.authState.challengeHeader, hostname || '')
        this.authState.state = 2
        headers.set(HEADER_AUTHORIZATION, `${NTLM_PREFIX}${msg.toBase64()}`)
        this.logger.debug('Set NTLM message3 header with hostname:', hostname || '')
      } else {
        this.logger.warn('Invalid NTLM state:', this.authState?.state)
      }
    } catch (error) {
      this.logger.error('NTLM authentication error:', error)
      delete this.authState
      throw HttpEngineErrorFactory.authenticationError(
        `NTLM authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
        undefined,
        'ntlm'
      )
    }
  }

  /**
   * Handle NTLM response from server
   */
  handleNtlmResponse(headers: Headers): boolean {
    if (!this.authState) {
      throw HttpEngineErrorFactory.authenticationError('No auth data.', undefined, 'ntlm')
    }
    this.logger.debug('Handling NTLM response.')
    if (this.authState.state === 0) {
      if (headers.has(HEADER_WWW_AUTHENTICATE)) {
        this.authState.state = 1
        const authHeader = headers.get(HEADER_WWW_AUTHENTICATE)
        // Extract just the base64 part after "NTLM " prefix
        this.authState.challengeHeader = authHeader?.replace(/^NTLM\s+/i, '') || ''

        if (!this.authState.challengeHeader) {
          this.logger.error('NTLM challenge header is empty after parsing')
          delete this.authState
          return false
        }

        this.logger.debug('NTLM challenge header was set:', this.authState.challengeHeader)
        return true // Indicates we need to retry with challenge
      }
    }

    // Authentication complete or failed
    this.logger.debug('NTLM authentication complete or failed.')
    delete this.authState
    return false
  }

  /**
   * Generate proxy authorization header
   */
  generateProxyAuthHeader(): string | undefined {
    const { proxyUsername, proxyPassword, proxyAuthorization } = this.proxyCredentials || {}

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

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

    if (proxyAuthorization === 'Digest') {
      // Note: Digest authentication is handled by ProxyAuthHandler in connections
      // This is just a placeholder for the header format
      this.logger.warn('Digest proxy authentication should be handled by ProxyAuthHandler')
      return undefined
    }

    throw HttpEngineErrorFactory.authenticationError(
      `Unsupported proxy authorization type: ${proxyAuthorization}`,
      undefined,
      'proxy'
    )
  }

  /**
   * Handle Basic authentication
   */
  handleBasicAuth(username: string, password: string, headers: Headers): void {
    const token = Buffer.from(`${username}:${password}`).toString('base64')
    headers.set(HEADER_AUTHORIZATION, `${BASIC_PREFIX}${token}`)
  }

  /**
   * Handle Bearer token authentication
   */
  handleBearerAuth(token: string, headers: Headers): void {
    headers.set(HEADER_AUTHORIZATION, `Bearer ${token}`)
  }

  /**
   * Get current authentication state
   */
  getAuthState(): IRequestAuthState | undefined {
    return this.authState
  }

  /**
   * Set authentication state
   */
  setAuthState(state: IRequestAuthState): void {
    this.authState = state
  }

  /**
   * Clear authentication state
   */
  clearAuthState(): void {
    delete this.authState
  }

  /**
   * Check if authentication is in progress
   */
  isAuthenticating(): boolean {
    return !!this.authState
  }

  /**
   * Check if we have credentials for proxy authentication
   */
  hasProxyCredentials(): boolean {
    const { proxyUsername, proxyPassword, proxyAuthorization } = this.proxyCredentials || {}
    return !!(proxyUsername && proxyPassword) || !!proxyAuthorization
  }
}
