class MarketoClient
  attr_accessor :base_url, :client_id, :client_secret, :access_token
  attr_reader :http_client

  CONTENT_TYPE_HEADER = { 'Content-Type'.freeze => 'application/json'.freeze }.freeze

  def initialize(config)
    @base_url = config.fetch(:base_url)
    @client_id = config.fetch(:client_id)
    @client_secret = config.fetch(:client_secret)
    @http_client = HTTPClient.new
    #@http_client.debug_dev = $stdout
  end

  def create_lead(attributes)
    body = {
      action: 'createOrUpdate',
      input:[ attributes ]
    }
    resp = post("/rest/v1/leads.json", body, authorization)
    resp.fetch('result').first.fetch('id')
  end

  def update_lead(attributes)
    body = {
      action: 'updateOnly',
      input:[ attributes ]
    }
    post("/rest/v1/leads.json", body, authorization)
  end

  def associate_lead(lead_id, mkt_cookie)
    uri = URI("/rest/v1/leads/#{lead_id}/associate.json")
    uri.query = {cookie: mkt_cookie, access_token: access_token}.to_query
    res = post(uri)
  end

  private

  def access_token
    @access_token ||= begin
      get(oauth_path).fetch('access_token')
    end
  rescue HTTPError
    raise "MarketoClient could not authenticate #{client_id}."
  end

  def oauth_path
    path = URI("/identity/oauth/token")
    path.query = {
      client_id: client_id,
      client_secret: client_secret,
      grant_type: :client_credentials
    }.to_query
    path
  end

  def get(path, headers = {})
    uri = URI.join(base_url, path)
    handle_response @http_client.get(uri)
  end

  def post(path, body = nil, headers = {})
    uri = URI.join(base_url, path)
    handle_response @http_client.post(uri, body.to_json, content_type.merge(headers))
  end

  def handle_response(response)
    raise InvalidResponseError, response unless response.ok?

    body = JSON.parse(response.body)

    if body.fetch('success', true)
      body
    else
      raise RequestError, body
    end
  end

  def content_type
    CONTENT_TYPE_HEADER
  end

  def authorization
    { 'Authorization' => "Bearer #{access_token}" }
  end


  class HTTPError < StandardError
  end

  class InvalidResponseError < HTTPError
    def initialize(response)
      super("Expected OK, got #{response.status} instead")
    end
  end

  class RequestError < HTTPError
    attr_reader :request_id, :code, :message

    def initialize(response)
      @request_id = response['requestId']

      if @error = response['errors'].first
        @code = @error['code']
        @message = @error['message']
        super(@message)
      end
    end
  end
end
