#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Demonstrates various operations involved in remarketing, including:
# (a) creating a user list based on visitors to a website
# (b) targeting a user list with an ad group criterion
# (c) updating the bid modifier on an ad group criterion
# (d) finding and removing all ad group criteria under a given campaign
# (e) targeting a user list with a campaign criterion
# (f) updating the bid modifier on a campaign criterion
# It is unlikely that users will need to perform all of these operations
# consecutively, and all of the operations contained herein are meant of for
# illustrative purposes.

require 'optparse'
require 'google/ads/google_ads'
require 'date'

def setup_remarketing(customer_id, campaign_id, ad_group_id, bid_modifier_value)
  # GoogleAdsClient will read a config file from
  # ENV['HOME']/google_ads_config.rb when called without parameters
  client = Google::Ads::GoogleAds::GoogleAdsClient.new

  user_list = create_user_list(client, customer_id)

  ad_group_criterion_resource_name = target_ads_in_ad_group_to_user_list(
    client,
    customer_id,
    ad_group_id,
    user_list,
  )

  modify_ad_group_bids(
    client,
    customer_id,
    ad_group_criterion_resource_name,
    bid_modifier_value,
  )

  remove_existing_list_criteria_from_ad_group(
    client,
    customer_id,
    campaign_id,
  )

  campaign_criterion_resource_name = target_ads_in_campaign_to_user_list(
    client,
    customer_id,
    campaign_id,
    user_list,
  )

  modify_campaign_bids(
    client,
    customer_id,
    campaign_criterion_resource_name,
    bid_modifier_value,
  )
end

# Creates a user list targeting users that have visited a given url.
# [START setup_remarketing]
def create_user_list(client, customer_id)
  # Creates the user list operation.
  operation = client.operation.create_resource.user_list do |ul|
    ul.name = "All visitors to example.com ##{(Time.new.to_f * 1000).to_i}"
    ul.description = "Any visitor to any page of example.com"
    ul.membership_status = :OPEN
    ul.membership_life_span = 365
    # Defines a representation of a user list that is generated by a rule.
    ul.rule_based_user_list = client.resource.rule_based_user_list_info do |r|
      # To include past users in the user list, set the prepopulation_status
      # to REQUESTED.
      r.prepopulation_status = :REQUESTED
      # Specifies that the user list targets visitors of a page based on
      # the provided rule.
      r.expression_rule_user_list = client.resource.expression_rule_user_list_info do |expr|
        expr.rule = client.resource.user_list_rule_info do |u|
          u.rule_item_groups << client.resource.user_list_rule_item_group_info do |group|
            group.rule_items << client.resource.user_list_rule_item_info do |item|
              # Uses a built-in parameter to create a domain URL rule.
              item.name = "url__"
              item.string_rule_item = client.resource.user_list_string_rule_item_info do |s|
                s.operator = :CONTAINS
                s.value = "example.com"
              end
            end
          end
        end
      end
    end
  end

  # Issues a mutate request to add the user list.
  response = client.service.user_list.mutate_user_lists(
    customer_id: customer_id,
    operations: [operation],
  )
  user_list_resource_name = response.results.first.resource_name
  puts "Created user list with resource name '#{user_list_resource_name}'"

  user_list_resource_name
end
# [END setup_remarketing]

# Creates an ad group criterion that targets a user list with an ad group.
# [START setup_remarketing_1]
def target_ads_in_ad_group_to_user_list(
  client,
  customer_id,
  ad_group_id,
  user_list
)
  # Creates the ad group criterion targeting members of the user list.
  operation = client.operation.create_resource.ad_group_criterion do |agc|
    agc.ad_group = client.path.ad_group(customer_id, ad_group_id)
    agc.user_list = client.resource.user_list_info do |info|
      info.user_list = user_list
    end
  end

  # Issues a mutate request to create the ad group criterion.
  response = client.service.ad_group_criterion.mutate_ad_group_criteria(
    customer_id: customer_id,
    operations: [operation],
  )
  ad_group_criterion_resource_name = response.results.first.resource_name
  puts "Successfully created ad group criterion with resource name " \
    "'#{ad_group_criterion_resource_name}' targeting user list with resource name " \
    "'#{user_list}' with ad group with ID #{ad_group_id}"

  ad_group_criterion_resource_name
end
# [END setup_remarketing_1]

# Updates the bid modifier on an ad group criterion.
def modify_ad_group_bids(
  client,
  customer_id,
  ad_group_criterion_resource_name,
  bid_modifier_value
)
  # Creates the update operation to modify the bid.
  operation = client.operation.update_resource.ad_group_criterion(
    ad_group_criterion_resource_name) do |agc|
    agc.bid_modifier = bid_modifier_value
  end

  # Issues a mutate request to update the ad group criterion.
  response = client.service.ad_group_criterion.mutate_ad_group_criteria(
    customer_id: customer_id,
    operations: [operation],
  )
  puts "Successfully updated the bid for ad group criterion with resource name " \
    "#{response.results.first.resource_name}"
end

# Removes all ad group criteria targeting a user list under a given campaign.
# This is a necessary step before targeting a user list at the campaign level.
# [START setup_remarketing_3]
def remove_existing_list_criteria_from_ad_group(
  client,
  customer_id,
  campaign_id
)
  # Retrieves all of the ad group criteria under a campaign.
  ad_group_criteria = get_user_list_ad_group_criterion(
    client, customer_id, campaign_id)

  # Creates a list of remove operations.
  operations = []
  ad_group_criteria.each do |agc|
    operations << client.operation.remove_resource.ad_group_criterion(agc)
  end

  # Issues a mutate request to remove all ad group criteria.
  response = client.service.ad_group_criterion.mutate_ad_group_criteria(
    customer_id: customer_id,
    operations: operations,
  )
  puts "Removed #{response.results.size} ad group criteria."
  response.results.each do |result|
    puts "Successfully removed ad group criterion with resource name " \
      "'#{result.resource_name}'"
  end
end
# [END setup_remarketing_3]

# Finds all of user list ad group criteria under a campaign.
# [START setup_remarketing_2]
def get_user_list_ad_group_criterion(
  client,
  customer_id,
  campaign_id
)
  user_list_criteria = []

  # Creates a query that will retrieve all of the ad group criteria 
  # under a campaign.
  query = <<~QUERY
    SELECT ad_group_criterion.criterion_id
    FROM ad_group_criterion
    WHERE campaign.id = #{campaign_id}
    AND ad_group_criterion.type = 'USER_LIST'
  QUERY

  # Issues the search request.
  response = client.service.google_ads.search(
    customer_id: customer_id,
    query: query,
    page_size: PAGE_SIZE,
  )

  # Iterates over all rows in all pages. Prints the results and adds the ad
  # group criteria resource names to the list.
  response.each do |row|
    ad_group_criterion_resource_name = row.ad_group_criterion.resource_name
    puts "Ad group criterion with resource name " \
      "'#{ad_group_criterion_resource_name}' was found"
    user_list_criteria << ad_group_criterion_resource_name
  end

  user_list_criteria
end
# [END setup_remarketing_2]

# Creates a campaign criterion that targets a user list with a campaign.
# [START setup_remarketing_4]
def target_ads_in_campaign_to_user_list(
  client,
  customer_id,
  campaign_id,
  user_list
)
  # Creates the campaign criterion targeting members of the user list.
  operation = client.operation.create_resource.campaign_criterion do |cc|
    cc.campaign = client.path.campaign(customer_id, campaign_id)
    cc.user_list = client.resource.user_list_info do |info|
      info.user_list = user_list
    end
  end

  # Issues a mutate request to create the campaign criterion.
  response = client.service.campaign_criterion.mutate_campaign_criteria(
    customer_id: customer_id,
    operations: [operation],
  )
  campaign_criterion_resource_name = response.results.first.resource_name
  puts "Successfully created campaign criterion with resource name " \
    "'#{campaign_criterion_resource_name}' targeting user list with resource name " \
    "'#{user_list}' with campaign with ID #{campaign_id}"

  campaign_criterion_resource_name
end
# [END setup_remarketing_4]

# Updates the bid modifier on a campaign criterion.
def modify_campaign_bids(
  client,
  customer_id,
  campaign_criterion_resource_name,
  bid_modifier_value
)
  # Creates the update operation to modify the bid.
  operation = client.operation.update_resource.campaign_criterion(
    campaign_criterion_resource_name) do |cc|
    cc.bid_modifier = bid_modifier_value
  end

  # Issues a mutate request to update the campaign criterion.
  response = client.service.campaign_criterion.mutate_campaign_criteria(
    customer_id: customer_id,
    operations: [operation],
  )
  puts "Successfully updated the bid for campaign criterion with resource name " \
    "#{response.results.first.resource_name}"
end

if __FILE__ == $0
  PAGE_SIZE = 1000

  options = {}
  # The following parameter(s) should be provided to run the example. You can
  # either specify these by changing the INSERT_XXX_ID_HERE values below, or on
  # the command line.
  #
  # Parameters passed on the command line will override any parameters set in
  # code.
  #
  # Running the example with -h will print the command line usage.
  options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE'
  options[:campaign_id] = 'INSERT_CAMPAIGN_ID_HERE'
  options[:ad_group_id] = 'INSERT_AD_GROUP_ID_HERE'
  # To use a different bid modifier value from the default (1.5), pass the
  # value using -B option on the command line.
  options[:bid_modifier_value] = 1.5

  OptionParser.new do |opts|
    opts.banner = sprintf('Usage: %s [options]', File.basename(__FILE__))

    opts.separator ''
    opts.separator 'Options:'

    opts.on('-C', '--customer-id CUSTOMER-ID', String, 'Customer ID') do |v|
      options[:customer_id] = v
    end

    opts.on('-c', '--campaign-id CAMPAIGN-ID', String, 'Campaign ID') do |v|
      options[:campaign_id] = v
    end

    opts.on('-A', '--ad-group-id AD-GROUP-ID', String, 'Ad Group ID') do |v|
      options[:ad_group_id] = v
    end

    opts.on('-B', '--bid-modifier-value BID-MODIFIER-VALUE', Float, 
      'Bid Modifier Value (Optional)') do |v|
      options[:bid_modifier_value] = v
    end

    opts.separator ''
    opts.separator 'Help:'

    opts.on_tail('-h', '--help', 'Show this message') do
      puts opts
      exit
    end
  end.parse!

  begin
    setup_remarketing(
      options.fetch(:customer_id).tr("-", ""),
      options.fetch(:campaign_id),
      options.fetch(:ad_group_id),
      options.fetch(:bid_modifier_value),
    )
  rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e
    e.failure.errors.each do |error|
      STDERR.printf("Error with message: %s\n", error.message)
      if error.location
        error.location.field_path_elements.each do |field_path_element|
          STDERR.printf("\tOn field: %s\n", field_path_element.field_name)
        end
      end
      error.error_code.to_h.each do |k, v|
        next if v == :UNSPECIFIED
        STDERR.printf("\tType: %s\n\tCode: %s\n", k, v)
      end
    end
    raise
  end
end
