require 'uaa'

module VCAP::Services::SSO::UAA
  class UaaClientManager
    ROUTER_404_KEY   = 'X-Cf-Routererror'.freeze
    ROUTER_404_VALUE = 'unknown_route'.freeze

    def initialize(opts={})
      @opts       = opts
      @uaa_client = create_uaa_client
    end

    delegate :get_clients, to: :@uaa_client

    def modify_transaction(changeset)
      return if changeset.empty?

      uri          = URI("#{uaa_target}/oauth/clients/tx/modify")
      request_body = batch_request(changeset)

      request                  = Net::HTTP::Post.new(uri.path)
      request.body             = request_body.to_json
      request.content_type     = 'application/json'
      request['Authorization'] = uaa_client.token_info.auth_header

      http             = Net::HTTP.new(uri.host, uri.port)
      http.use_ssl     = true
      http.verify_mode = OpenSSL::SSL::VERIFY_PEER
      http.ca_file     = uaa_ca_file
      http.cert_store  = OpenSSL::X509::Store.new
      http.cert_store.set_default_paths

      logger.info("POST UAA transaction: #{uri} - #{scrub(request_body).to_json}")
      response = http.request(request)

      case response.code.to_i
      when 200..299
        nil
      when 400
        log_bad_uaa_response(response)
        raise VCAP::CloudController::UaaResourceInvalid.new
      when 404
        log_bad_uaa_response(response)
        raise VCAP::CloudController::UaaUnavailable.new if response[ROUTER_404_KEY] == ROUTER_404_VALUE

        raise VCAP::CloudController::UaaResourceNotFound.new

      when 409
        log_bad_uaa_response(response)
        raise VCAP::CloudController::UaaResourceAlreadyExists.new
      else
        log_bad_uaa_response(response)
        raise VCAP::CloudController::UaaUnexpectedResponse.new
      end
    end

    private

    attr_reader :uaa_client

    def log_bad_uaa_response(response)
      logger.error("UAA request failed with code: #{response.code} - #{response.inspect}")
    end

    def scrub(transaction_body)
      transaction_body.map do |client_request|
        client_request.delete(:client_secret)
        client_request
      end
    end

    def batch_request(changeset)
      changeset.map do |change|
        client_info = sso_client_info(change.client_attrs)
        client_info.merge(change.uaa_command)
      end
    end

    def sso_client_info(client_attrs)
      {
        client_id: client_attrs['id'],
        client_secret: client_attrs['secret'],
        redirect_uri: client_attrs['redirect_uri'],
        scope: filter_uaa_client_scope,
        authorities: ['uaa.resource'],
        authorized_grant_types: ['authorization_code']
      }
    end

    def logger
      @logger ||= Steno.logger('cc.uaa_client_manager')
    end

    def filter_uaa_client_scope
      configured_scope = VCAP::CloudController::Config.config.get(:uaa_client_scope).split(',')
      configured_scope.select do |val|
        ['cloud_controller.write', 'openid', 'cloud_controller.read', 'cloud_controller_service_permissions.read'].include?(val)
      end
    end

    def create_uaa_client
      VCAP::CloudController::UaaClient.new(
        uaa_target: uaa_target,
        client_id: VCAP::CloudController::Config.config.get(:uaa_client_name),
        secret: VCAP::CloudController::Config.config.get(:uaa_client_secret),
        ca_file: uaa_ca_file
      )
    end

    def uaa_ca_file
      VCAP::CloudController::Config.config.get(:uaa, :ca_file)
    end

    def uaa_target
      VCAP::CloudController::Config.config.get(:uaa, :internal_url)
    end
  end
end
