class PagesDomain < ActiveRecord::Base
  belongs_to :project

  validates :domain, hostname: true
  validates_uniqueness_of :domain, case_sensitive: false
  validates :certificate, certificate: true, allow_nil: true, allow_blank: true
  validates :key, certificate_key: true, allow_nil: true, allow_blank: true

  validate :validate_matching_key, if: ->(domain) { domain.certificate.present? && domain.key.present? }
  validate :validate_intermediates, if: ->(domain) { domain.certificate.present? }

  attr_encrypted :key, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base

  after_create :update
  after_save :update
  after_destroy :update

  def to_param
    domain
  end

  def url
    return unless domain

    if certificate
      return "https://#{domain}"
    else
      return "http://#{domain}"
    end
  end

  def has_matching_key?
    return unless x509
    return unless pkey

    # We compare the public key stored in certificate with public key from certificate key
    x509.check_private_key(pkey)
  end

  def has_intermediates?
    return false unless x509

    store = OpenSSL::X509::Store.new
    store.set_default_paths

    # This forces to load all intermediate certificates stored in `certificate`
    Tempfile.open('certificate_chain') do |f|
      f.write(certificate)
      f.flush
      store.add_file(f.path)
    end

    store.verify(x509)
  rescue OpenSSL::X509::StoreError
    false
  end

  def expired?
    return false unless x509
    current = Time.new
    return current < x509.not_before || x509.not_after < current
  end

  def subject
    return unless x509
    return x509.subject.to_s
  end

  def fingerprint
    return unless x509
    @fingeprint ||= OpenSSL::Digest::SHA256.new(x509.to_der).to_s
  end

  private

  def x509
    return unless certificate
    @x509 ||= OpenSSL::X509::Certificate.new(certificate)
  rescue OpenSSL::X509::CertificateError
    nil
  end

  def pkey
    return unless key
    @pkey ||= OpenSSL::PKey::RSA.new(key)
  rescue OpenSSL::PKey::PKeyError, OpenSSL::Cipher::CipherError
    nil
  end

  def update
    ::Projects::UpdatePagesConfigurationService.new(project).execute
  end

  def validate_matching_key
    unless has_matching_key?
      self.errors.add(:key, "doesn't match the certificate")
    end
  end

  def validate_intermediates
    unless has_intermediates?
      self.errors.add(:certificate, 'misses intermediates')
    end
  end
end