module VCAP::Services::SSO
  class DashboardClientManager
    attr_reader :errors, :warnings

    REQUESTED_FEATURE_DISABLED_WARNING = [
      'Warning: This broker includes configuration for a dashboard client.',
      'Auto-creation of OAuth2 clients has been disabled in this Cloud Foundry instance.',
      'The broker catalog has been updated but its dashboard client configuration will be ignored.'
    ].join(' ').freeze

    def initialize(broker, services_event_repository)
      @broker   = broker
      @errors   = VCAP::Services::ValidationErrors.new
      @warnings = []

      @client_manager = VCAP::Services::SSO::UAA::UaaClientManager.new

      @differ = DashboardClientDiffer.new(broker)

      @services_event_repository = services_event_repository
    end

    def synchronize_clients_with_catalog(catalog)
      requested_clients = catalog.services.map(&:dashboard_client).compact
      requested_client_ids = requested_clients.pluck('id')

      unless cc_configured_to_modify_uaa_clients?
        warnings << REQUESTED_FEATURE_DISABLED_WARNING unless requested_clients.empty?
        return true
      end

      return false unless all_clients_can_be_claimed_in_db?(catalog)

      existing_ccdb_clients    = VCAP::CloudController::ServiceDashboardClient.find_claimed_client(broker)
      existing_ccdb_client_ids = existing_ccdb_clients.map(&:uaa_id)

      existing_uaa_client_ids = fetch_clients_from_uaa(requested_client_ids | existing_ccdb_client_ids).pluck('client_id')
      return false unless all_clients_can_be_claimed_in_uaa?(existing_uaa_client_ids, catalog)

      claim_clients_and_update_uaa(requested_clients, existing_ccdb_clients, existing_uaa_client_ids)
      true
    end

    def remove_clients_for_broker
      return unless cc_configured_to_modify_uaa_clients?

      requested_clients       = []
      existing_db_clients     = VCAP::CloudController::ServiceDashboardClient.find_claimed_client(broker)
      existing_db_client_ids  = existing_db_clients.map(&:uaa_id)
      existing_uaa_client_ids = fetch_clients_from_uaa(existing_db_client_ids).pluck('client_id')

      claim_clients_and_update_uaa(requested_clients, existing_db_clients, existing_uaa_client_ids)
    end

    def has_warnings?
      !warnings.empty?
    end

    private

    attr_reader :client_manager, :differ, :broker

    def all_clients_can_be_claimed_in_db?(catalog)
      requested_clients = catalog.services.map(&:dashboard_client).compact

      unclaimable_ids = []
      requested_clients.each do |client|
        existing_client_in_ccdb = VCAP::CloudController::ServiceDashboardClient.find_client_by_uaa_id(client['id'])
        unclaimable_ids << existing_client_in_ccdb.uaa_id unless client_claimable_by_broker?(existing_client_in_ccdb)
      end

      unless unclaimable_ids.empty?
        populate_uniqueness_errors(catalog, unclaimable_ids)
        return false
      end
      true
    end

    def all_clients_can_be_claimed_in_uaa?(existing_uaa_client_ids, catalog)
      unclaimable_ids = []
      existing_uaa_client_ids.each do |id|
        existing_client_in_ccdb = VCAP::CloudController::ServiceDashboardClient.find_client_by_uaa_id(id)
        unclaimable_ids << id if existing_client_in_ccdb.nil?
      end

      unless unclaimable_ids.empty?
        populate_uniqueness_errors(catalog, unclaimable_ids)
        return false
      end
      true
    end

    def fetch_clients_from_uaa(requested_client_ids)
      client_manager.get_clients(requested_client_ids)
    rescue VCAP::CloudController::UaaError => e
      raise CloudController::Errors::ApiError.new_from_details('ServiceBrokerDashboardClientFailure', e.message)
    end

    def client_claimable_by_broker?(existing_client_in_ccdb)
      existing_client_in_ccdb.nil? ||
        existing_client_in_ccdb.service_broker.nil? ||
        existing_client_in_ccdb.service_broker.id == broker.id
    end

    def claim_clients_and_update_uaa(requested_clients, existing_db_clients, existing_uaa_clients)
      db_changeset  = differ.create_db_changeset(requested_clients, existing_db_clients)
      uaa_changeset = differ.create_uaa_changeset(requested_clients, existing_uaa_clients)

      begin
        broker.db.transaction do
          db_changeset.each(&:db_command)
          client_manager.modify_transaction(uaa_changeset)
        end
      rescue VCAP::CloudController::UaaError => e
        raise CloudController::Errors::ApiError.new_from_details('ServiceBrokerDashboardClientFailure', e.message)
      end

      uaa_changeset.each do |uaa_cmd|
        case uaa_cmd.uaa_command[:action]
        when 'add'
          @services_event_repository.record_service_dashboard_client_event(
            :create, uaa_cmd.client_attrs, broker
          )
        when 'delete'
          @services_event_repository.record_service_dashboard_client_event(
            :delete, uaa_cmd.client_attrs, broker
          )
        end
      end
    end

    def populate_uniqueness_errors(catalog, non_unique_ids)
      catalog.services.each do |service|
        errors.add_nested(service).add('Service dashboard client id must be unique') if service.dashboard_client && non_unique_ids.include?(service.dashboard_client['id'])
      end
    end

    def cc_configured_to_modify_uaa_clients?
      uaa_client = VCAP::CloudController::Config.config.get(:uaa_client_name)
      uaa_client_secret = VCAP::CloudController::Config.config.get(:uaa_client_secret)
      uaa_client && uaa_client_secret
    end
  end
end
