module Account::Domains
  extend ActiveSupport::Concern

  SUPERDOMAIN = ThreeScale.config.superdomain

  included do

    with_options :if => :validate_domains? do |provider|
      provider.validate :domain_uniqueness, :self_domain_uniqueness, :domain_not_self_domain

      provider.validates_presence_of :domain, :unless => :signup?
      provider.validates_presence_of :self_domain, :unless => :signup?

      # special banned domains
      provider.validates_exclusion_of :domain, :in => BANNED_DOMAINS, :allow_blank => true, :message => "has already been taken"
      provider.validates_exclusion_of :self_domain, :in => BANNED_DOMAINS, :allow_blank => true, :message => "has already been taken"


      # this is for signup (we care about subdomain)
      provider.validates_presence_of :subdomain, :if => :signup?
      provider.validates_presence_of :self_subdomain, :if => :signup?

      provider.validates_format_of :subdomain, :with => /\A[a-z0-9](?:[a-z0-9\-]*?[a-z0-9])?\z/i,
                                   :allow_blank => true, :message => :domain

      provider.validates_format_of :subdomain, :with => /\A[^A-Z]*\z/, :message => "must be downcase", :on => :create, :allow_blank => true, :if => :signup?
      provider.validates_format_of :domain, :with => /\A[^A-Z]*\z/, :message => "must be downcase", :allow_blank => true, :unless => :signup?
      provider.validates_format_of :self_domain, :with => /\A[^A-Z]*\z/, :message => "must be downcase", :allow_blank => true, :unless => :signup?

    end

    scope :by_domain, lambda { |domain| where(domain: domain) }
    scope :by_self_domain, lambda { |domain| where(self_domain: domain) }
    scope :by_admin_domain, lambda { |domain|
      table = self.table_name
      where("(#{table}.self_domain = :domain) OR (#{table}.self_domain IS NULL AND provider_accounts_accounts.domain = :domain)",
            { :domain => domain })
      .joins(:provider_account)
      .readonly(false)
    }
  end

  module ClassMethods
    def find_by_domain(domain)
      return false if domain.blank?

      find_by(domain: domain)
    end

    def find_by_domain!(domain)
      find_by_domain(domain) ||
      raise(ActiveRecord::RecordNotFound, "Couldn't find #{name} with domain=#{domain.inspect}")
    end

    def is_domain?(domain)
      return unless domain.present?
      providers.where(:domain => domain).exists?
    end

    def is_admin_domain?(domain)
      providers.where(:self_domain => domain).exists?
    end

    def is_master_domain?(domain)
      master.domain == domain or master.self_domain == domain
    end

    def same_domain(domain)
      # TODO: case insensitive
      where(["(domain = :domain OR self_domain = :domain)", {:domain => domain}])
    end
  end

  def generate_domains
    if self.org_name.present? && (not domain?)
      generated = generate_subdomain(self.name)

      self.subdomain = generated
    end

    if subdomain.present? && (not self_domain?)
      self.self_subdomain = subdomain + "-admin"
    elsif !self_domain? # for connect usage

      if subdomain = generate_subdomain(name).presence
        self.self_subdomain = subdomain + "-admin"
      end
    end
  end

  def subdomain= name
    self.domain = if name.present?
      [name, superdomain].join('.')
                  else
      name
                  end
  end

  def subdomain
    subdomain_from(domain)
  end

  def superdomain
    # master account has different domain now
    SUPERDOMAIN
  end

  def dedicated_domain
    superdomain = provider_account.try!(:superdomain)

    if superdomain && !domain.nil? && domain.ends_with?(superdomain)
      nil
    else
      domain
    end
  end

  attr_writer :dedicated_domain

  # Generate subdomain name from arbitrary string. Assure only valid characters are used.
  # Also assure that the generated subdomain name is not already taken by one of the
  # existing accounts.
  def generate_subdomain(name)
    result = name.to_s.parameterize

    while result.present? &&
          ( !unique?(:domain, "#{result}.#{superdomain}") ||
            !unique?(:domain, "#{result}-admin.#{superdomain}") )
      base, sequence = result.match(/\A(.*?)(?:\-(\d+))?\z/).to_a[1, 2]
      result = base + '-' + ((sequence || 1).to_i + 1).to_s
    end

    result
  end

  def self_subdomain= name
    self.self_domain = if name.present?
      [name, superdomain].join('.')
                       else
      name
                       end
  end

  def self_subdomain
    subdomain_from(self_domain)
  end

  private

  def validate_domains?
    provider? and not master?
  end

  def subdomain_from(domain)
    domain.to_s[/\A(.+)\.#{Regexp.quote(superdomain)}\z/, 1]
  end

  def unique?(attr, val = self[attr])
    scope = if new_record?
      self.class.where({}) # after rails 4.0 we could use .all
            else
      self.class.where(['id <> ?', id])
    end

    scope = case attr
            when :domain, :self_domain
        scope.same_domain(val)
            else
        scope.where(attr => val)
      end

    scope.pluck(:id).empty?
  end

  def domain_uniqueness
    unless unique?(:domain)
      if subdomain
        errors.add(:subdomain, :taken)
      else
        errors.add(:domain, :taken)
      end
    end
  end

  def self_domain_uniqueness
    unless unique?(:self_domain)
      if subdomain
        errors.add(:self_subdomain, :taken)
      else
        errors.add(:self_domain, :taken)
      end
    end
  end

  def domain_not_self_domain
    if domain && self_domain && domain == self_domain
      if subdomain
        errors.add(:subdomain, :same)
        errors.add(:self_subdomain, :same)
      else
        errors.add(:domain, :same)
        errors.add(:self_domain, :same)
      end
    end
  end

end
