# frozen_string_literal: true

require 'openssl'
require 'base64'
require 'securerandom'
require_relative '../config/settings'

# 密码加密/解密服务
#
# 业务逻辑背景:
# - 设备密码需要加密存储在本地数据库中
# - 下发任务执行时需要还原明文密码用于设备连接
# - BCrypt 是单向哈希，无法解密，不适合此场景
#
# 隐性知识:
# - 密钥存储在客户本地的配置文件中
# - 每个客户环境有独立的加密密钥
# - 密钥丢失将无法解密已存储的密码
#
# 技术方案:
# - 算法: AES-256-GCM (认证加密，防篡改)
# - IV: 每次加密随机生成 12 字节 IV
# - 格式: Base64(IV || Ciphertext || AuthTag)
# - 密钥: 32 字节 (256 位)，存储在 settings.yml
#
# 使用示例:
#   encryptor = PasswordEncryptor.new
#   encrypted = encryptor.encrypt("my_password")
#   decrypted = encryptor.decrypt(encrypted)
#
class PasswordEncryptor
  # 加密算法
  ALGORITHM = 'AES-256-GCM'
  
  # IV 长度 (GCM 推荐 12 字节)
  IV_LENGTH = 12
  
  # Auth Tag 长度 (GCM 标准 16 字节)
  AUTH_TAG_LENGTH = 16
  
  # 密钥长度 (AES-256 需要 32 字节)
  KEY_LENGTH = 32
  
  # 加密失败错误
  class EncryptionError < StandardError; end
  
  # 解密失败错误
  class DecryptionError < StandardError; end
  
  # 密钥未配置错误
  class KeyNotConfiguredError < StandardError; end
  
  attr_reader :key
  
  # 初始化加密器
  #
  # 参数:
  #   key: 可选，指定加密密钥。默认从 Settings 读取
  #
  # 抛出:
  #   KeyNotConfiguredError 当密钥未配置时
  #
  def initialize(key: nil)
    @key = key || load_or_generate_key
    validate_key!
  end
  
  # 加密明文密码
  #
  # 参数:
  #   plain_password: 明文密码字符串
  #
  # 返回:
  #   Base64 编码的加密字符串
  #
  # 抛出:
  #   EncryptionError 当加密失败时
  #
  # 示例:
  #   encryptor.encrypt("my_password")  # => "YWJjZGVm..." (Base64)
  #
  def encrypt(plain_password)
    raise EncryptionError, "密码不能为空" if plain_password.nil? || plain_password.empty?
    
    begin
      # 生成随机 IV
      iv = SecureRandom.random_bytes(IV_LENGTH)
      
      # 创建加密器
      cipher = OpenSSL::Cipher.new(ALGORITHM)
      cipher.encrypt
      cipher.key = @key
      cipher.iv = iv
      
      # 加密数据
      encrypted = cipher.update(plain_password.encode('UTF-8')) + cipher.final
      
      # 获取 Auth Tag
      auth_tag = cipher.auth_tag
      
      # 组合: IV + Ciphertext + AuthTag，然后 Base64 编码
      combined = iv + encrypted + auth_tag
      Base64.strict_encode64(combined)
      
    rescue OpenSSL::Cipher::CipherError => e
      raise EncryptionError, "加密失败: #{e.message}"
    rescue StandardError => e
      raise EncryptionError, "加密失败: #{e.message}"
    end
  end
  
  # 解密加密密码
  #
  # 参数:
  #   encrypted_password: Base64 编码的加密字符串
  #
  # 返回:
  #   明文密码字符串
  #
  # 抛出:
  #   DecryptionError 当解密失败时（密钥错误或数据损坏）
  #
  # 示例:
  #   encryptor.decrypt("YWJjZGVm...")  # => "my_password"
  #
  def decrypt(encrypted_password)
    raise DecryptionError, "加密数据不能为空" if encrypted_password.nil? || encrypted_password.empty?
    
    begin
      # Base64 解码
      combined = Base64.strict_decode64(encrypted_password)
      
      # 检查最小长度 (IV + 至少1字节密文 + AuthTag)
      min_length = IV_LENGTH + 1 + AUTH_TAG_LENGTH
      raise DecryptionError, "加密数据格式无效" if combined.length < min_length
      
      # 分离组件
      iv = combined[0, IV_LENGTH]
      auth_tag = combined[-AUTH_TAG_LENGTH, AUTH_TAG_LENGTH]
      ciphertext = combined[IV_LENGTH..(-AUTH_TAG_LENGTH - 1)]
      
      # 创建解密器
      cipher = OpenSSL::Cipher.new(ALGORITHM)
      cipher.decrypt
      cipher.key = @key
      cipher.iv = iv
      cipher.auth_tag = auth_tag
      
      # 解密数据
      decrypted = cipher.update(ciphertext) + cipher.final
      
      # 强制 UTF-8 编码
      decrypted.force_encoding('UTF-8')
      
    rescue OpenSSL::Cipher::CipherError => e
      # GCM 解密失败通常是密钥错误或数据被篡改
      raise DecryptionError, "解密失败: 密钥错误或数据损坏"
    rescue ArgumentError => e
      raise DecryptionError, "解密失败: #{e.message}"
    rescue StandardError => e
      raise DecryptionError, "解密失败: #{e.message}"
    end
  end
  
  # 检查是否为新的加密格式（非 BCrypt）
  #
  # 参数:
  #   encrypted_value: 加密后的值
  #
  # 返回:
  #   true 如果是新的 AES-GCM 格式
  #   false 如果是旧的 BCrypt 格式
  #
  # 示例:
  #   encryptor.aes_encrypted?("YWJj...")  # => true
  #   encryptor.aes_encrypted?("$2a$10$...")  # => false (BCrypt)
  #
  def aes_encrypted?(encrypted_value)
    return false if encrypted_value.nil? || encrypted_value.empty?
    
    # BCrypt 哈希以 $2 开头
    return false if encrypted_value.start_with?('$2')
    
    # 尝试 Base64 解码并检查格式
    begin
      decoded = Base64.strict_decode64(encrypted_value)
      # 检查长度是否合理
      decoded.length >= IV_LENGTH + 1 + AUTH_TAG_LENGTH
    rescue ArgumentError
      false
    end
  end
  
  # 检查是否为旧的 BCrypt 格式
  #
  # 参数:
  #   encrypted_value: 加密后的值
  #
  # 返回:
  #   true 如果是 BCrypt 格式
  #
  def bcrypt_encrypted?(encrypted_value)
    return false if encrypted_value.nil? || encrypted_value.empty?
    
    # BCrypt 哈希格式: $2a$10$... 或 $2b$10$...
    encrypted_value.match?(/\A\$2[aby]\$\d+\$/)
  end
  
  private
  
  # 从 Settings 加载或生成密钥
  #
  # 返回:
  #   32 字节密钥字符串
  #
  def load_or_generate_key
    key_hex = settings.get(:password_encryption_key)
    
    if key_hex.nil? || key_hex.empty?
      # 生成新密钥
      key_hex = SecureRandom.hex(KEY_LENGTH)
      settings.set(:password_encryption_key, key_hex)
      LogService.info("已生成新的密码加密密钥，请妥善保管配置文件")
    end
    
    # 将十六进制字符串转换为字节
    [key_hex].pack('H*')
  end
  
  # 验证密钥有效性
  def validate_key!
    raise KeyNotConfiguredError, "加密密钥未配置" if @key.nil?
    raise KeyNotConfiguredError, "加密密钥长度无效，需要 #{KEY_LENGTH} 字节" if @key.bytesize != KEY_LENGTH
  end
  
  # 获取 Settings 实例
  def settings
    Settings.instance
  end
end
