const ldap = require('ldapjs')
const logger = require('../utils/logger')
const config = require('../../config/config')
const userService = require('./userService')

class LdapService {
  constructor() {
    this.config = config.ldap || {}
    this.client = null

    // 验证配置 - 只有在 LDAP 配置存在且启用时才验证
    if (this.config && this.config.enabled) {
      this.validateConfiguration()
    }
  }

  // 🔍 验证LDAP配置
  validateConfiguration() {
    const errors = []

    if (!this.config.server) {
      errors.push('LDAP server configuration is missing')
    } else {
      if (!this.config.server.url || typeof this.config.server.url !== 'string') {
        errors.push('LDAP server URL is not configured or invalid')
      }

      if (!this.config.server.bindDN || typeof this.config.server.bindDN !== 'string') {
        errors.push('LDAP bind DN is not configured or invalid')
      }

      if (
        !this.config.server.bindCredentials ||
        typeof this.config.server.bindCredentials !== 'string'
      ) {
        errors.push('LDAP bind credentials are not configured or invalid')
      }

      if (!this.config.server.searchBase || typeof this.config.server.searchBase !== 'string') {
        errors.push('LDAP search base is not configured or invalid')
      }

      if (!this.config.server.searchFilter || typeof this.config.server.searchFilter !== 'string') {
        errors.push('LDAP search filter is not configured or invalid')
      }
    }

    if (errors.length > 0) {
      logger.error('❌ LDAP configuration validation failed:', errors)
      // Don't throw error during initialization, just log warnings
      logger.warn('⚠️ LDAP authentication may not work properly due to configuration errors')
    } else {
      logger.info('✅ LDAP configuration validation passed')
    }
  }

  // 🔍 提取LDAP条目的DN
  extractDN(ldapEntry) {
    if (!ldapEntry) {
      return null
    }

    // Try different ways to get the DN
    let dn = null

    // Method 1: Direct dn property
    if (ldapEntry.dn) {
      ;({ dn } = ldapEntry)
    }
    // Method 2: objectName property (common in some LDAP implementations)
    else if (ldapEntry.objectName) {
      dn = ldapEntry.objectName
    }
    // Method 3: distinguishedName property
    else if (ldapEntry.distinguishedName) {
      dn = ldapEntry.distinguishedName
    }
    // Method 4: Check if the entry itself is a DN string
    else if (typeof ldapEntry === 'string' && ldapEntry.includes('=')) {
      dn = ldapEntry
    }

    // Convert DN to string if it's an object
    if (dn && typeof dn === 'object') {
      if (dn.toString && typeof dn.toString === 'function') {
        dn = dn.toString()
      } else if (dn.dn && typeof dn.dn === 'string') {
        ;({ dn } = dn)
      }
    }

    // Validate the DN format
    if (typeof dn === 'string' && dn.trim() !== '' && dn.includes('=')) {
      return dn.trim()
    }

    return null
  }

  // 🌐 从DN中提取域名，用于Windows AD UPN格式认证
  extractDomainFromDN(dnString) {
    try {
      if (!dnString || typeof dnString !== 'string') {
        return null
      }

      // 提取所有DC组件：DC=test,DC=demo,DC=com
      const dcMatches = dnString.match(/DC=([^,]+)/gi)
      if (!dcMatches || dcMatches.length === 0) {
        return null
      }

      // 提取DC值并连接成域名
      const domainParts = dcMatches.map((match) => {
        const value = match.replace(/DC=/i, '').trim()
        return value
      })

      if (domainParts.length > 0) {
        const domain = domainParts.join('.')
        logger.debug(`🌐 从DN提取域名: ${domain}`)
        return domain
      }

      return null
    } catch (error) {
      logger.debug('⚠️ 域名提取失败:', error.message)
      return null
    }
  }

  // 🔗 创建LDAP客户端连接
  createClient() {
    try {
      const clientOptions = {
        url: this.config.server.url,
        timeout: this.config.server.timeout,
        connectTimeout: this.config.server.connectTimeout,
        reconnect: true
      }

      // 如果使用 LDAPS (SSL/TLS)，添加 TLS 选项
      if (this.config.server.url.toLowerCase().startsWith('ldaps://')) {
        const tlsOptions = {}

        // 证书验证设置
        if (this.config.server.tls) {
          if (typeof this.config.server.tls.rejectUnauthorized === 'boolean') {
            tlsOptions.rejectUnauthorized = this.config.server.tls.rejectUnauthorized
          }

          // CA 证书
          if (this.config.server.tls.ca) {
            tlsOptions.ca = this.config.server.tls.ca
          }

          // 客户端证书和私钥 (双向认证)
          if (this.config.server.tls.cert) {
            tlsOptions.cert = this.config.server.tls.cert
          }

          if (this.config.server.tls.key) {
            tlsOptions.key = this.config.server.tls.key
          }

          // 服务器名称 (SNI)
          if (this.config.server.tls.servername) {
            tlsOptions.servername = this.config.server.tls.servername
          }
        }

        clientOptions.tlsOptions = tlsOptions

        logger.debug('🔒 Creating LDAPS client with TLS options:', {
          url: this.config.server.url,
          rejectUnauthorized: tlsOptions.rejectUnauthorized,
          hasCA: !!tlsOptions.ca,
          hasCert: !!tlsOptions.cert,
          hasKey: !!tlsOptions.key,
          servername: tlsOptions.servername
        })
      }

      const client = ldap.createClient(clientOptions)

      // 设置错误处理
      client.on('error', (err) => {
        if (err.code === 'CERT_HAS_EXPIRED' || err.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
          logger.error('🔒 LDAP TLS certificate error:', {
            code: err.code,
            message: err.message,
            hint: 'Consider setting LDAP_TLS_REJECT_UNAUTHORIZED=false for self-signed certificates'
          })
        } else {
          logger.error('🔌 LDAP client error:', err)
        }
      })

      client.on('connect', () => {
        if (this.config.server.url.toLowerCase().startsWith('ldaps://')) {
          logger.info('🔒 LDAPS client connected successfully')
        } else {
          logger.info('🔗 LDAP client connected successfully')
        }
      })

      client.on('connectTimeout', () => {
        logger.warn('⏱️ LDAP connection timeout')
      })

      return client
    } catch (error) {
      logger.error('❌ Failed to create LDAP client:', error)
      throw error
    }
  }

  // 🔒 绑定LDAP连接（管理员认证）
  async bindClient(client) {
    return new Promise((resolve, reject) => {
      // 验证绑定凭据
      const { bindDN } = this.config.server
      const { bindCredentials } = this.config.server

      if (!bindDN || typeof bindDN !== 'string') {
        const error = new Error('LDAP bind DN is not configured or invalid')
        logger.error('❌ LDAP configuration error:', error.message)
        reject(error)
        return
      }

      if (!bindCredentials || typeof bindCredentials !== 'string') {
        const error = new Error('LDAP bind credentials are not configured or invalid')
        logger.error('❌ LDAP configuration error:', error.message)
        reject(error)
        return
      }

      client.bind(bindDN, bindCredentials, (err) => {
        if (err) {
          logger.error('❌ LDAP bind failed:', err)
          reject(err)
        } else {
          logger.debug('🔑 LDAP bind successful')
          resolve()
        }
      })
    })
  }

  // 🔍 搜索用户
  async searchUser(client, username) {
    return new Promise((resolve, reject) => {
      // 防止LDAP注入：转义特殊字符
      // 根据RFC 4515，需要转义的特殊字符：* ( ) \ NUL
      const escapedUsername = username
        .replace(/\\/g, '\\5c') // 反斜杠必须先转义
        .replace(/\*/g, '\\2a') // 星号
        .replace(/\(/g, '\\28') // 左括号
        .replace(/\)/g, '\\29') // 右括号
        .replace(/\0/g, '\\00') // NUL字符
        .replace(/\//g, '\\2f') // 斜杠

      const searchFilter = this.config.server.searchFilter.replace('{{username}}', escapedUsername)
      const searchOptions = {
        scope: 'sub',
        filter: searchFilter,
        attributes: this.config.server.searchAttributes
      }

      logger.debug(`🔍 Searching for user: ${username} with filter: ${searchFilter}`)

      const entries = []

      client.search(this.config.server.searchBase, searchOptions, (err, res) => {
        if (err) {
          logger.error('❌ LDAP search error:', err)
          reject(err)
          return
        }

        res.on('searchEntry', (entry) => {
          logger.debug('🔍 LDAP search entry received:', {
            dn: entry.dn,
            objectName: entry.objectName,
            type: typeof entry.dn,
            entryType: typeof entry,
            hasAttributes: !!entry.attributes,
            attributeCount: entry.attributes ? entry.attributes.length : 0
          })
          entries.push(entry)
        })

        res.on('searchReference', (referral) => {
          logger.debug('🔗 LDAP search referral:', referral.uris)
        })

        res.on('error', (error) => {
          logger.error('❌ LDAP search result error:', error)
          reject(error)
        })

        res.on('end', (result) => {
          logger.debug(
            `✅ LDAP search completed. Status: ${result.status}, Found ${entries.length} entries`
          )

          if (entries.length === 0) {
            resolve(null)
          } else {
            // Log the structure of the first entry for debugging
            if (entries[0]) {
              logger.debug('🔍 Full LDAP entry structure:', {
                entryType: typeof entries[0],
                entryConstructor: entries[0].constructor?.name,
                entryKeys: Object.keys(entries[0]),
                entryStringified: JSON.stringify(entries[0], null, 2).substring(0, 500)
              })
            }

            if (entries.length === 1) {
              resolve(entries[0])
            } else {
              logger.warn(`⚠️ Multiple LDAP entries found for username: ${username}`)
              resolve(entries[0]) // 使用第一个结果
            }
          }
        })
      })
    })
  }

  // 🔐 验证用户密码
  async authenticateUser(userDN, password) {
    return new Promise((resolve, reject) => {
      // 验证输入参数
      if (!userDN || typeof userDN !== 'string') {
        const error = new Error('User DN is not provided or invalid')
        logger.error('❌ LDAP authentication error:', error.message)
        reject(error)
        return
      }

      if (!password || typeof password !== 'string') {
        logger.debug(`🚫 Invalid or empty password for DN: ${userDN}`)
        resolve(false)
        return
      }

      const authClient = this.createClient()

      authClient.bind(userDN, password, (err) => {
        authClient.unbind() // 立即关闭认证客户端

        if (err) {
          if (err.name === 'InvalidCredentialsError') {
            logger.debug(`🚫 Invalid credentials for DN: ${userDN}`)
            resolve(false)
          } else {
            logger.error('❌ LDAP authentication error:', err)
            reject(err)
          }
        } else {
          logger.debug(`✅ Authentication successful for DN: ${userDN}`)
          resolve(true)
        }
      })
    })
  }

  // 🔐 Windows AD兼容认证 - 在DN认证失败时尝试多种格式
  async tryWindowsADAuthentication(username, password) {
    if (!username || !password) {
      return false
    }

    // 从searchBase提取域名
    const domain = this.extractDomainFromDN(this.config.server.searchBase)

    const adFormats = []

    if (domain) {
      // UPN格式（Windows AD标准）
      adFormats.push(`${username}@${domain}`)

      // 如果域名有多个部分，也尝试简化版本
      const domainParts = domain.split('.')
      if (domainParts.length > 1) {
        adFormats.push(`${username}@${domainParts.slice(-2).join('.')}`) // 只取后两部分
      }

      // 域\用户名格式
      const firstDomainPart = domainParts[0]
      if (firstDomainPart) {
        adFormats.push(`${firstDomainPart}\\${username}`)
        adFormats.push(`${firstDomainPart.toUpperCase()}\\${username}`)
      }
    }

    // 纯用户名（最后尝试）
    adFormats.push(username)

    logger.info(`🔄 尝试 ${adFormats.length} 种Windows AD认证格式...`)

    for (const format of adFormats) {
      try {
        logger.info(`🔍 尝试格式: ${format}`)
        const result = await this.tryDirectBind(format, password)
        if (result) {
          logger.info(`✅ Windows AD认证成功: ${format}`)
          return true
        }
        logger.debug(`❌ 认证失败: ${format}`)
      } catch (error) {
        logger.debug(`认证异常 ${format}:`, error.message)
      }
    }

    logger.info(`🚫 所有Windows AD格式认证都失败了`)
    return false
  }

  // 🔐 直接尝试绑定认证的辅助方法
  async tryDirectBind(identifier, password) {
    return new Promise((resolve, reject) => {
      const authClient = this.createClient()

      authClient.bind(identifier, password, (err) => {
        authClient.unbind()

        if (err) {
          if (err.name === 'InvalidCredentialsError') {
            resolve(false)
          } else {
            reject(err)
          }
        } else {
          resolve(true)
        }
      })
    })
  }

  // 📝 提取用户信息
  extractUserInfo(ldapEntry, username) {
    try {
      const attributes = ldapEntry.attributes || []
      const userInfo = { username }

      // 创建属性映射
      const attrMap = {}
      attributes.forEach((attr) => {
        const name = attr.type || attr.name
        const values = Array.isArray(attr.values) ? attr.values : [attr.values]
        attrMap[name] = values.length === 1 ? values[0] : values
      })

      // 根据配置映射用户属性
      const mapping = this.config.userMapping

      userInfo.displayName = attrMap[mapping.displayName] || username
      userInfo.email = attrMap[mapping.email] || ''
      userInfo.firstName = attrMap[mapping.firstName] || ''
      userInfo.lastName = attrMap[mapping.lastName] || ''

      // 如果没有displayName，尝试组合firstName和lastName
      if (!userInfo.displayName || userInfo.displayName === username) {
        if (userInfo.firstName || userInfo.lastName) {
          userInfo.displayName = `${userInfo.firstName || ''} ${userInfo.lastName || ''}`.trim()
        }
      }

      logger.debug('📋 Extracted user info:', {
        username: userInfo.username,
        displayName: userInfo.displayName,
        email: userInfo.email
      })

      return userInfo
    } catch (error) {
      logger.error('❌ Error extracting user info:', error)
      return { username }
    }
  }

  // 🔍 验证和清理用户名
  validateAndSanitizeUsername(username) {
    if (!username || typeof username !== 'string' || username.trim() === '') {
      throw new Error('Username is required and must be a non-empty string')
    }

    const trimmedUsername = username.trim()

    // 用户名只能包含字母、数字、下划线和连字符
    const usernameRegex = /^[a-zA-Z0-9_-]+$/
    if (!usernameRegex.test(trimmedUsername)) {
      throw new Error('Username can only contain letters, numbers, underscores, and hyphens')
    }

    // 长度限制 (防止过长的输入)
    if (trimmedUsername.length > 64) {
      throw new Error('Username cannot exceed 64 characters')
    }

    // 不能以连字符开头或结尾
    if (trimmedUsername.startsWith('-') || trimmedUsername.endsWith('-')) {
      throw new Error('Username cannot start or end with a hyphen')
    }

    return trimmedUsername
  }

  // 🔐 主要的登录验证方法
  async authenticateUserCredentials(username, password) {
    if (!this.config.enabled) {
      throw new Error('LDAP authentication is not enabled')
    }

    // 验证和清理用户名 (防止LDAP注入)
    const sanitizedUsername = this.validateAndSanitizeUsername(username)

    if (!password || typeof password !== 'string' || password.trim() === '') {
      throw new Error('Password is required and must be a non-empty string')
    }

    // 验证LDAP服务器配置
    if (!this.config.server || !this.config.server.url) {
      throw new Error('LDAP server URL is not configured')
    }

    if (!this.config.server.bindDN || typeof this.config.server.bindDN !== 'string') {
      throw new Error('LDAP bind DN is not configured')
    }

    if (
      !this.config.server.bindCredentials ||
      typeof this.config.server.bindCredentials !== 'string'
    ) {
      throw new Error('LDAP bind credentials are not configured')
    }

    if (!this.config.server.searchBase || typeof this.config.server.searchBase !== 'string') {
      throw new Error('LDAP search base is not configured')
    }

    const client = this.createClient()

    try {
      // 1. 使用管理员凭据绑定
      await this.bindClient(client)

      // 2. 搜索用户 (使用已验证的用户名)
      const ldapEntry = await this.searchUser(client, sanitizedUsername)
      if (!ldapEntry) {
        logger.info(`🚫 User not found in LDAP: ${sanitizedUsername}`)
        return { success: false, message: 'Invalid username or password' }
      }

      // 3. 获取用户DN
      logger.debug('🔍 LDAP entry details for DN extraction:', {
        hasEntry: !!ldapEntry,
        entryType: typeof ldapEntry,
        entryKeys: Object.keys(ldapEntry || {}),
        dn: ldapEntry.dn,
        objectName: ldapEntry.objectName,
        dnType: typeof ldapEntry.dn,
        objectNameType: typeof ldapEntry.objectName
      })

      // Use the helper method to extract DN
      const userDN = this.extractDN(ldapEntry)

      logger.debug(`👤 Extracted user DN: ${userDN} (type: ${typeof userDN})`)

      // 验证用户DN
      if (!userDN) {
        logger.error(`❌ Invalid or missing DN for user: ${sanitizedUsername}`, {
          ldapEntryDn: ldapEntry.dn,
          ldapEntryObjectName: ldapEntry.objectName,
          ldapEntryType: typeof ldapEntry,
          extractedDN: userDN
        })
        return { success: false, message: 'Authentication service error' }
      }

      // 4. 验证用户密码 - 支持传统LDAP和Windows AD
      let isPasswordValid = false

      // 首先尝试传统的DN认证（保持原有LDAP逻辑）
      try {
        isPasswordValid = await this.authenticateUser(userDN, password)
        if (isPasswordValid) {
          logger.info(`✅ DN authentication successful for user: ${sanitizedUsername}`)
        }
      } catch (error) {
        logger.debug(
          `DN authentication failed for user: ${sanitizedUsername}, error: ${error.message}`
        )
      }

      // 如果DN认证失败，尝试Windows AD多格式认证
      if (!isPasswordValid) {
        logger.debug(`🔄 Trying Windows AD authentication formats for user: ${sanitizedUsername}`)
        isPasswordValid = await this.tryWindowsADAuthentication(sanitizedUsername, password)
        if (isPasswordValid) {
          logger.info(`✅ Windows AD authentication successful for user: ${sanitizedUsername}`)
        }
      }

      if (!isPasswordValid) {
        logger.info(`🚫 All authentication methods failed for user: ${sanitizedUsername}`)
        return { success: false, message: 'Invalid username or password' }
      }

      // 5. 提取用户信息
      const userInfo = this.extractUserInfo(ldapEntry, sanitizedUsername)

      // 6. 创建或更新本地用户
      const user = await userService.createOrUpdateUser(userInfo)

      // 7. 检查用户是否被禁用
      if (!user.isActive) {
        logger.security(
          `🔒 Disabled user LDAP login attempt: ${sanitizedUsername} from LDAP authentication`
        )
        return {
          success: false,
          message: 'Your account has been disabled. Please contact administrator.'
        }
      }

      // 8. 记录登录
      await userService.recordUserLogin(user.id)

      // 9. 创建用户会话
      const sessionToken = await userService.createUserSession(user.id)

      logger.info(`✅ LDAP authentication successful for user: ${sanitizedUsername}`)

      return {
        success: true,
        user,
        sessionToken,
        message: 'Authentication successful'
      }
    } catch (error) {
      // 记录详细错误供调试，但不向用户暴露
      logger.error('❌ LDAP authentication error:', {
        username: sanitizedUsername,
        error: error.message,
        stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
      })

      // 返回通用错误消息，避免信息泄露
      // 不要尝试解析具体的错误信息，因为不同LDAP服务器返回的格式不同
      return {
        success: false,
        message: 'Authentication service unavailable'
      }
    } finally {
      // 确保客户端连接被关闭
      if (client) {
        client.unbind((err) => {
          if (err) {
            logger.debug('Error unbinding LDAP client:', err)
          }
        })
      }
    }
  }

  // 🔍 测试LDAP连接
  async testConnection() {
    if (!this.config.enabled) {
      return { success: false, message: 'LDAP is not enabled' }
    }

    const client = this.createClient()

    try {
      await this.bindClient(client)

      return {
        success: true,
        message: 'LDAP connection successful',
        server: this.config.server.url,
        searchBase: this.config.server.searchBase
      }
    } catch (error) {
      logger.error('❌ LDAP connection test failed:', {
        error: error.message,
        server: this.config.server.url,
        stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
      })

      // 提供通用错误消息，避免泄露系统细节
      let userMessage = 'LDAP connection failed'

      // 对于某些已知错误类型，提供有用但不泄露细节的信息
      if (error.code === 'ECONNREFUSED') {
        userMessage = 'Unable to connect to LDAP server'
      } else if (error.code === 'ETIMEDOUT') {
        userMessage = 'LDAP server connection timeout'
      } else if (error.name === 'InvalidCredentialsError') {
        userMessage = 'LDAP bind credentials are invalid'
      }

      return {
        success: false,
        message: userMessage,
        server: this.config.server.url.replace(/:[^:]*@/, ':***@') // 隐藏密码部分
      }
    } finally {
      if (client) {
        client.unbind((err) => {
          if (err) {
            logger.debug('Error unbinding test LDAP client:', err)
          }
        })
      }
    }
  }

  // 📊 获取LDAP配置信息（不包含敏感信息）
  getConfigInfo() {
    const configInfo = {
      enabled: this.config.enabled,
      server: {
        url: this.config.server.url,
        searchBase: this.config.server.searchBase,
        searchFilter: this.config.server.searchFilter,
        timeout: this.config.server.timeout,
        connectTimeout: this.config.server.connectTimeout
      },
      userMapping: this.config.userMapping
    }

    // 添加 TLS 配置信息（不包含敏感数据）
    if (this.config.server.url.toLowerCase().startsWith('ldaps://') && this.config.server.tls) {
      configInfo.server.tls = {
        rejectUnauthorized: this.config.server.tls.rejectUnauthorized,
        hasCA: !!this.config.server.tls.ca,
        hasCert: !!this.config.server.tls.cert,
        hasKey: !!this.config.server.tls.key,
        servername: this.config.server.tls.servername
      }
    }

    return configInfo
  }
}

module.exports = new LdapService()
