require 'login_format_validation'
require 'local_email'
#
# Identity states:
#
#   DISABLED -- An identity is disabled if and only if its associated user
#               is also disabled. In the disabled state, incoming email
#               should bounce and outgoing email should not be relayed.
#
#   ORPHANED -- An identity is orphaned if it has lost its association
#               with a user account. This is in order to keep the name
#               reserved to prevent anyone else from using it.
#

class Identity < CouchRest::Model::Base
  include LoginFormatValidation

  use_database :identities

  belongs_to :user

  property :address, LocalEmail
  property :destination, Email
  property :keys, HashWithIndifferentAccess
  property :cert_fingerprints, Hash
  property :disabled_cert_fingerprints, Hash
  property :enabled, TrueClass, :default => true

  validates :address, presence: true
  validate :address_available
  validates :destination, presence: true, if: :user_id
  validates :destination, uniqueness: {scope: :address}
  validate :address_local_email
  validate :destination_email

  design do
    own_path = Pathname.new(File.dirname(__FILE__))
    load_views(own_path.join('..', 'designs', 'identity'), nil)
    view :by_user_id
    view :by_address_and_destination
    view :by_address
  end

  def self.address_starts_with(query)
    self.by_address.startkey(query).endkey(query + "\ufff0")
  end

  def self.for(user, attributes = {})
    find_for(user, attributes) || build_for(user, attributes)
  end

  def self.find_for(user, attributes = {})
    attributes.reverse_merge! attributes_from_user(user)
    id = find_by_address_and_destination attributes.values_at(:address, :destination)
    return id if id && id.user == user
  end

  def self.build_for(user, attributes = {})
    attributes.reverse_merge! attributes_from_user(user)
    Identity.new(attributes)
  end

  def self.create_for(user, attributes = {})
    identity = build_for(user, attributes)
    identity.save
    identity
  end

  # currently leap_mx ignores enabled property, so we
  # also disable the fingerprints instead of just marking
  # identity as disabled.

  def disable!
    self.disabled_cert_fingerprints = self.cert_fingerprints
    self.cert_fingerprints = {}
    self.write_attribute(:enabled, false)
    self.save
  end

  def enable!
    self.cert_fingerprints = self.disabled_cert_fingerprints
    self.disabled_cert_fingerprints = nil
    self.write_attribute(:enabled, true)
    self.save
  end

  # removes the association between this identity and the user.
  def orphan!
    self.destination = nil
    self.user_id = nil
    self.disable!
  end

  def self.destroy_all_orphaned
    Identity.orphaned.each do |identity|
      identity.destroy
    end
  end

  def self.attributes_from_user(user)
    { user_id: user.id,
      address: user.email_address,
      destination: user.email_address
    }
  end

  def status
    if !enabled? || orphaned?
      return :blocked
    else
      case destination
      when address
        :main_email
      when /@#{APP_CONFIG[:domain]}\Z/i,
        :alias
      else
        :forward
      end
    end
  end

  def actions
    if !orphaned?
      [] # [:show, :edit]
    else
      [:destroy]
    end
  end

  def keys
    read_attribute('keys') || HashWithIndifferentAccess.new
  end

  def set_key(type, key)
    return if keys[type] == key.to_s
    write_attribute('keys', keys.merge(type => key.to_s))
  end

  def cert_fingerprints
    read_attribute('cert_fingerprints') || Hash.new
  end

  def register_cert(cert)
    expiry = cert.expiry.to_date.to_s
    write_attribute 'cert_fingerprints',
      cert_fingerprints.merge(cert.fingerprint => expiry)
  end

  # for LoginFormatValidation
  def login
    address.handle if address.present?
  end

  def orphaned?
    self.user_id.nil?
  end

  def self.orphaned
    # the "disabled" view is a misnomer. it returns
    # identities that have been orphaned, not identities that
    # have been disabled.
    # TODO: fix the view name
    Identity.disabled
  end

  protected

  def address_available
    blocking_identities = Identity.by_address.key(address).all
    blocking_identities.delete self
    if self.user
      blocking_identities.reject! { |other| other.user == self.user }
    end
    if blocking_identities.any?
      errors.add :address, :taken
    end
  end

  def address_local_email
    # caught by presence validation
    return if address.blank?
    return if address.valid?
    address.errors.each do |attribute, error|
      self.errors.add(:address, error)
    end
  end

  def destination_email
    # caught by presence validation or this identity is disabled
    return if destination.blank?
    return if destination.valid?
    destination.errors.each do |attribute, error|
      self.errors.add(:destination, error)
    end
  end

  ActiveSupport.run_load_hooks(:identity, self)
end