/**
 * HTTP Logger
 *
 * Structured logging for HTTP requests and responses using winston.
 * Provides detailed request/response logging with security-aware data handling.
 */

import winston from 'winston'

import type { HttpClientRequest, HttpClientResponse } from '@/types/httpClient'
import type { PlatformRequest, PlatformResponse } from '@/types/platformAdapter'

export interface LoggingConfig {
  /** Log level */
  level: 'debug' | 'info' | 'warn' | 'error'

  /** Whether to log request bodies */
  logRequestBodies: boolean

  /** Whether to log response bodies */
  logResponseBodies: boolean

  /** Whether to log sensitive data (signatures, keys, etc) */
  logSensitiveData: boolean

  /** Maximum body size to log (in bytes) */
  maxBodySize: number

  /** Platforms to exclude from logging */
  excludePlatforms: string[]

  /** Accounts to exclude from logging */
  excludeAccounts: string[]

  /** Custom logger instance */
  logger?: winston.Logger
}

export interface RequestLogData {
  /** Request identifier */
  requestId: string

  /** Timestamp */
  timestamp: string

  /** Platform identifier */
  platform: string

  /** Account identifier */
  accountId: string

  /** HTTP method */
  method: string

  /** Request URL (sanitized) */
  url: string

  /** Request headers (sanitized) */
  headers: Record<string, string>

  /** Request body (if enabled and sanitized) */
  body?: any

  /** Request options */
  options?: any

  /** Whether request requires authentication */
  requiresAuth: boolean

  /** Proxy information */
  proxy?: {
    enabled: boolean
    url?: string
  }
}

export interface ResponseLogData {
  /** Request identifier */
  requestId: string

  /** Timestamp */
  timestamp: string

  /** Platform identifier */
  platform: string

  /** Account identifier */
  accountId: string

  /** HTTP status code */
  status: number

  /** Whether request was successful */
  success: boolean

  /** Response time in milliseconds */
  duration: number

  /** Response headers (sanitized) */
  headers: Record<string, string>

  /** Response body (if enabled and sanitized) */
  body?: any

  /** Error information if failed */
  error?: {
    type: string
    message: string
    code: string
  }

  /** Metadata */
  metadata: {
    cacheHit?: boolean
    usedProxy: boolean
    retryCount: number
    networkLatency?: number
    processingTime?: number
    responseSize: number
    authenticated: boolean
    signatureAlgorithm?: string
    credentialManagerUsed: boolean
  }
}

export interface ErrorLogData {
  /** Request identifier */
  requestId: string

  /** Timestamp */
  timestamp: string

  /** Platform identifier */
  platform: string

  /** Account identifier */
  accountId: string

  /** Error type */
  errorType: 'client_error' | 'server_error' | 'network_error' | 'auth_error' | 'timeout_error' | 'proxy_error'

  /** Error message */
  message: string

  /** Error code */
  code: string

  /** Stack trace (if available) */
  stack?: string

  /** Additional context */
  context?: Record<string, any>
}

export class HttpLogger {
  private logger: winston.Logger
  private config: LoggingConfig

  constructor(config: Partial<LoggingConfig> = {}) {
    this.config = {
      level: 'info',
      logRequestBodies: false,
      logResponseBodies: false,
      logSensitiveData: false,
      maxBodySize: 1024 * 10, // 10KB
      excludePlatforms: [],
      excludeAccounts: [],
      ...config,
    }

    this.logger = config.logger || this.createDefaultLogger()
  }

  /**
   * Log HTTP request
   */
  logRequest(request: HttpClientRequest, metadata: { requestId: string; requiresAuth: boolean; proxy?: any }): void {
    if (this.shouldSkipLogging(request.platform, request.accountId)) {
      return
    }

    const logData: RequestLogData = {
      requestId: metadata.requestId,
      timestamp: new Date().toISOString(),
      platform: request.platform,
      accountId: this.sanitizeAccountId(request.accountId),
      method: request.method,
      url: this.sanitizeUrl(request.url),
      headers: this.sanitizeHeaders(request.headers || {}),
      requiresAuth: metadata.requiresAuth,
      proxy: metadata.proxy
        ? {
            enabled: Boolean(metadata.proxy.enabled),
            url: metadata.proxy.url ? this.sanitizeProxyUrl(metadata.proxy.url) : undefined,
          }
        : undefined,
    }

    // Add request body if enabled
    if (this.config.logRequestBodies && request.body) {
      logData.body = this.sanitizeBody(request.body)
    }

    // Add request options (sanitized)
    if (request.options) {
      logData.options = this.sanitizeOptions(request.options)
    }

    this.logger.info('HTTP Request', logData)
  }

  /**
   * Log HTTP response
   */
  logResponse(request: HttpClientRequest, response: HttpClientResponse, duration: number): void {
    if (this.shouldSkipLogging(request.platform, request.accountId)) {
      return
    }

    const logData: ResponseLogData = {
      requestId: response.metadata.requestId,
      timestamp: new Date().toISOString(),
      platform: request.platform,
      accountId: this.sanitizeAccountId(request.accountId),
      status: response.status,
      success: response.ok,
      duration,
      headers: this.sanitizeHeaders(response.headers),
      metadata: {
        cacheHit: response.metadata.cacheHit,
        usedProxy: response.metadata.usedProxy,
        retryCount: response.metadata.retryCount,
        networkLatency: response.metadata.networkLatency,
        processingTime: response.metadata.processingTime,
        responseSize: response.metadata.responseSize || 0,
        authenticated: response.metadata.authenticated || false,
        signatureAlgorithm: response.metadata.signatureAlgorithm,
        credentialManagerUsed: response.metadata.credentialManagerUsed || false,
      },
    }

    // Add response body if enabled
    if (this.config.logResponseBodies && response.data) {
      logData.body = this.sanitizeBody(response.data)
    }

    // Add error information if failed
    if (!response.ok) {
      logData.error = {
        type: this.inferErrorType(response.status),
        message: response.statusText,
        code: String(response.status),
      }
    }

    const logLevel = response.ok ? 'info' : 'warn'
    this.logger[logLevel]('HTTP Response', logData)
  }

  /**
   * Log HTTP error
   */
  logError(
    request: HttpClientRequest,
    error: Error,
    metadata: { requestId: string; errorType?: string; context?: Record<string, any> },
  ): void {
    if (this.shouldSkipLogging(request.platform, request.accountId)) {
      return
    }

    const logData: ErrorLogData = {
      requestId: metadata.requestId,
      timestamp: new Date().toISOString(),
      platform: request.platform,
      accountId: this.sanitizeAccountId(request.accountId),
      errorType: (metadata.errorType as any) || this.inferErrorTypeFromError(error),
      message: error.message,
      code: (error as any).code || 'UNKNOWN',
      stack: this.config.level === 'debug' ? error.stack : undefined,
      context: metadata.context ? this.sanitizeContext(metadata.context) : undefined,
    }

    this.logger.error('HTTP Error', logData)
  }

  /**
   * Log platform adapter request
   */
  logPlatformRequest(platform: string, request: PlatformRequest, metadata: { requestId: string }): void {
    if (this.shouldSkipLogging(platform, request.accountId)) {
      return
    }

    this.logger.debug('Platform Request', {
      requestId: metadata.requestId,
      timestamp: new Date().toISOString(),
      platform,
      accountId: this.sanitizeAccountId(request.accountId),
      method: request.method,
      path: request.path,
      hasBody: Boolean(request.body),
      hasParams: Boolean(request.params),
      requiresAuth: request.options?.requiresAuth,
      timeout: request.options?.timeout,
    })
  }

  /**
   * Log platform adapter response
   */
  logPlatformResponse(platform: string, response: PlatformResponse, metadata: { requestId: string }): void {
    const logData = {
      requestId: metadata.requestId,
      timestamp: new Date().toISOString(),
      platform,
      status: response.status,
      success: response.success,
      duration: response.metadata.duration,
      responseSize: response.metadata.responseSize,
      hasError: Boolean(response.error),
    }

    const logLevel = response.success ? 'debug' : 'warn'
    this.logger[logLevel]('Platform Response', logData)
  }

  /**
   * Log authentication events
   */
  logAuthentication(
    platform: string,
    accountId: string,
    event: 'start' | 'success' | 'failure',
    metadata: { requestId: string; algorithm?: string; duration?: number; error?: string },
  ): void {
    if (this.shouldSkipLogging(platform, accountId)) {
      return
    }

    const logData = {
      requestId: metadata.requestId,
      timestamp: new Date().toISOString(),
      platform,
      accountId: this.sanitizeAccountId(accountId),
      event,
      algorithm: metadata.algorithm,
      duration: metadata.duration,
      error: metadata.error,
    }

    const logLevel = event === 'failure' ? 'warn' : 'debug'
    this.logger[logLevel]('Authentication', logData)
  }

  /**
   * Update logging configuration
   */
  updateConfig(newConfig: Partial<LoggingConfig>): void {
    this.config = { ...this.config, ...newConfig }
  }

  /**
   * Get current logging configuration
   */
  getConfig(): LoggingConfig {
    return { ...this.config }
  }

  // Private helper methods

  private createDefaultLogger(): winston.Logger {
    return winston.createLogger({
      level: this.config.level,
      format: winston.format.combine(
        winston.format.timestamp(),
        winston.format.errors({ stack: true }),
        winston.format.json(),
      ),
      transports: [
        new winston.transports.Console({
          format: winston.format.combine(winston.format.colorize(), winston.format.simple()),
        }),
        new winston.transports.File({
          filename: 'logs/http-client.log',
          format: winston.format.json(),
        }),
      ],
    })
  }

  private shouldSkipLogging(platform: string, accountId: string): boolean {
    return this.config.excludePlatforms.includes(platform) || this.config.excludeAccounts.includes(accountId)
  }

  private sanitizeAccountId(accountId: string): string {
    if (!this.config.logSensitiveData && accountId.length > 8) {
      return accountId.substring(0, 4) + '****' + accountId.substring(accountId.length - 4)
    }
    return accountId
  }

  private sanitizeUrl(url: string): string {
    try {
      const urlObj = new URL(url)
      // Remove sensitive query parameters
      const sensitiveParams = ['signature', 'apikey', 'key', 'secret', 'token']
      sensitiveParams.forEach(param => {
        if (urlObj.searchParams.has(param)) {
          urlObj.searchParams.set(param, '[REDACTED]')
        }
      })
      return urlObj.toString()
    } catch {
      return url
    }
  }

  private sanitizeHeaders(headers: Record<string, string>): Record<string, string> {
    const sanitized: Record<string, string> = {}
    const sensitiveHeaders = [
      'authorization',
      'x-signature',
      'x-api-key',
      'x-mbx-apikey',
      'x-mbx-signature',
      'api-key',
      'api-secret',
    ]

    for (const [key, value] of Object.entries(headers)) {
      if (sensitiveHeaders.includes(key.toLowerCase()) && !this.config.logSensitiveData) {
        sanitized[key] = '[REDACTED]'
      } else {
        sanitized[key] = value
      }
    }

    return sanitized
  }

  private sanitizeBody(body: any): any {
    if (!body) return body

    const bodyStr = typeof body === 'string' ? body : JSON.stringify(body)
    if (bodyStr.length > this.config.maxBodySize) {
      return `[TRUNCATED - ${bodyStr.length} bytes]`
    }

    // If it's an object, sanitize sensitive fields
    if (typeof body === 'object') {
      const sanitized = { ...body }
      const sensitiveFields = ['signature', 'secret', 'privateKey', 'password', 'token']

      for (const field of sensitiveFields) {
        if (sanitized[field] && !this.config.logSensitiveData) {
          sanitized[field] = '[REDACTED]'
        }
      }

      return sanitized
    }

    return body
  }

  private sanitizeOptions(options: any): any {
    if (!options) return options

    const sanitized = { ...options }

    // Remove or redact sensitive option fields
    if (sanitized.proxy && !this.config.logSensitiveData) {
      if (sanitized.proxy.auth) {
        sanitized.proxy.auth = '[REDACTED]'
      }
    }

    return sanitized
  }

  private sanitizeProxyUrl(proxyUrl: string): string {
    try {
      const url = new URL(proxyUrl)
      if (url.username || url.password) {
        url.username = '[REDACTED]'
        url.password = '[REDACTED]'
      }
      return url.toString()
    } catch {
      return '[INVALID_URL]'
    }
  }

  private sanitizeContext(context: Record<string, any>): Record<string, any> {
    const sanitized: Record<string, any> = {}
    const sensitiveKeys = ['signature', 'secret', 'key', 'password', 'token', 'auth']

    for (const [key, value] of Object.entries(context)) {
      if (sensitiveKeys.some(sensitive => key.toLowerCase().includes(sensitive)) && !this.config.logSensitiveData) {
        sanitized[key] = '[REDACTED]'
      } else {
        sanitized[key] = value
      }
    }

    return sanitized
  }

  private inferErrorType(status: number): 'client_error' | 'server_error' | 'network_error' | 'auth_error' {
    if (status === 401 || status === 403) return 'auth_error'
    if (status >= 400 && status < 500) return 'client_error'
    if (status >= 500) return 'server_error'
    return 'network_error'
  }

  private inferErrorTypeFromError(error: Error): 'network_error' | 'timeout_error' | 'proxy_error' | 'client_error' {
    const message = error.message.toLowerCase()
    if (message.includes('timeout')) return 'timeout_error'
    if (message.includes('proxy')) return 'proxy_error'
    if (message.includes('network') || message.includes('connection')) return 'network_error'
    return 'client_error'
  }
}

/**
 * Global HTTP logger instance
 */
export const globalHttpLogger = new HttpLogger({
  level: 'info',
  logRequestBodies: false,
  logResponseBodies: false,
  logSensitiveData: false,
})

/**
 * Create HTTP logger with custom configuration
 */
export function createHttpLogger(config: Partial<LoggingConfig>): HttpLogger {
  return new HttpLogger(config)
}

export default HttpLogger
