module VCAP::CloudController
  class OrganizationQuotasUpdate
    class Error < ::StandardError
    end

    MAX_ORGS_TO_LIST_ON_FAILURE = 2

    # rubocop:disable Metrics/CyclomaticComplexity
    def self.update(quota, message)
      if log_rate_limit(message) != QuotaDefinition::UNLIMITED
        orgs = orgs_with_unlimited_processes(quota)
        unlimited_processes_exist_error!(orgs) if orgs.any?
      end

      quota.db.transaction do
        quota.lock!

        quota.name = message.name if message.requested? :name

        quota.memory_limit = memory_limit(message) if message.apps_limits_message.requested? :total_memory_in_mb
        quota.instance_memory_limit = instance_memory_limit(message) if message.apps_limits_message.requested? :per_process_memory_in_mb
        quota.app_instance_limit = app_instance_limit(message) if message.apps_limits_message.requested? :total_instances
        quota.app_task_limit = app_task_limit(message) if message.apps_limits_message.requested? :per_app_tasks
        quota.log_rate_limit = log_rate_limit(message) if message.apps_limits_message.requested? :log_rate_limit_in_bytes_per_second

        quota.total_services = total_services(message) if message.services_limits_message.requested? :total_service_instances
        quota.total_service_keys = total_service_keys(message) if message.services_limits_message.requested? :total_service_keys
        quota.non_basic_services_allowed = non_basic_services_allowed(message) if message.services_limits_message.requested? :paid_services_allowed

        quota.total_reserved_route_ports = total_reserved_route_ports(message) if message.routes_limits_message.requested? :total_reserved_ports
        quota.total_routes = total_routes(message) if message.routes_limits_message.requested? :total_routes

        quota.total_private_domains = total_private_domains(message) if message.domains_limits_message.requested? :total_domains

        quota.save
      end
      # rubocop:enable Metrics/CyclomaticComplexity

      quota
    rescue Sequel::ValidationFailed => e
      raise Error.new("Organization Quota '#{message.name}' already exists.") if e.errors.on(:name)&.include?(:unique)

      raise Error.new(e.message)
    end

    def self.default_if_nil(message_value, default)
      message_value.nil? ? default : message_value
    end

    def self.memory_limit(message)
      default_if_nil(message.total_memory_in_mb, QuotaDefinition::UNLIMITED)
    end

    def self.instance_memory_limit(message)
      default_if_nil(message.per_process_memory_in_mb, QuotaDefinition::UNLIMITED)
    end

    def self.app_instance_limit(message)
      default_if_nil(message.total_instances, QuotaDefinition::UNLIMITED)
    end

    def self.app_task_limit(message)
      default_if_nil(message.per_app_tasks, QuotaDefinition::UNLIMITED)
    end

    def self.log_rate_limit(message)
      default_if_nil(message.log_rate_limit_in_bytes_per_second, QuotaDefinition::UNLIMITED)
    end

    def self.total_services(message)
      default_if_nil(message.total_service_instances, QuotaDefinition::UNLIMITED)
    end

    def self.total_service_keys(message)
      default_if_nil(message.total_service_keys, QuotaDefinition::UNLIMITED)
    end

    def self.non_basic_services_allowed(message)
      default_if_nil(message.paid_services_allowed, QuotaDefinition::DEFAULT_NON_BASIC_SERVICES_ALLOWED)
    end

    def self.total_reserved_route_ports(message)
      default_if_nil(message.total_reserved_ports, QuotaDefinition::UNLIMITED)
    end

    def self.total_routes(message)
      default_if_nil(message.total_routes, QuotaDefinition::UNLIMITED)
    end

    def self.total_private_domains(message)
      default_if_nil(message.total_domains, QuotaDefinition::UNLIMITED)
    end

    def self.orgs_with_unlimited_processes(quota)
      quota.organizations_dataset.
        distinct.
        select(Sequel[:organizations][:name]).
        join(:spaces, organization_id: :id).
        join(:apps, space_guid: :guid).
        join(:processes, app_guid: :guid).
        where(log_rate_limit: QuotaDefinition::UNLIMITED).
        order(:name).
        all
    end

    def self.unlimited_processes_exist_error!(orgs)
      named_orgs = orgs.take(MAX_ORGS_TO_LIST_ON_FAILURE).map(&:name).join("', '")
      message = 'This quota is applied to ' +
                if orgs.size == 1
                  "org '#{named_orgs}' which contains"
                elsif orgs.size > MAX_ORGS_TO_LIST_ON_FAILURE
                  "orgs '#{named_orgs}' and #{orgs.drop(MAX_ORGS_TO_LIST_ON_FAILURE).size} " \
                    'other orgs which contain'
                else
                  "orgs '#{named_orgs}' which contain"
                end + ' apps running with an unlimited log rate limit.'

      raise Error.new("Current usage exceeds new quota values. #{message}")
    end

    private_class_method :orgs_with_unlimited_processes,
                         :unlimited_processes_exist_error!
  end
end
