// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/security_interstitials/core/metrics_helper.h"

#include <utility>

#include "base/metrics/histogram.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "components/history/core/browser/history_service.h"
#include "components/rappor/rappor_service.h"
#include "components/rappor/rappor_utils.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"

using base::RecordAction;
using base::UserMetricsAction;

namespace security_interstitials {

namespace {

// Used for setting bits in Rappor's "interstitial.*.flags"
enum InterstitialFlagBits {
  DID_PROCEED = 0,
  IS_REPEAT_VISIT = 1,
  HIGHEST_USED_BIT = 1
};

// Directly adds to the UMA histograms, using the same properties as
// UMA_HISTOGRAM_ENUMERATION, because the macro doesn't allow non-constant
// histogram names.
void RecordSingleDecisionToMetrics(MetricsHelper::Decision decision,
                                   const std::string& histogram_name) {
  base::HistogramBase* histogram = base::LinearHistogram::FactoryGet(
      histogram_name, 1, MetricsHelper::MAX_DECISION,
      MetricsHelper::MAX_DECISION + 1,
      base::HistogramBase::kUmaTargetedHistogramFlag);
  histogram->Add(decision);
}

void RecordSingleInteractionToMetrics(MetricsHelper::Interaction interaction,
                                      const std::string& histogram_name) {
  base::HistogramBase* histogram = base::LinearHistogram::FactoryGet(
      histogram_name, 1, MetricsHelper::MAX_INTERACTION,
      MetricsHelper::MAX_INTERACTION + 1,
      base::HistogramBase::kUmaTargetedHistogramFlag);
  histogram->Add(interaction);
}

void MaybeRecordDecisionAsAction(MetricsHelper::Decision decision,
                                 const std::string& metric_name) {
  if (decision == MetricsHelper::PROCEED) {
    if (metric_name == "malware")
      RecordAction(UserMetricsAction("MalwareInterstitial.Proceed"));
    else if (metric_name == "harmful")
      RecordAction(UserMetricsAction("HarmfulInterstitial.Proceed"));
    else if (metric_name == "ssl_overridable")
      RecordAction(UserMetricsAction("SSLOverridableInterstitial.Proceed"));
  } else if (decision == MetricsHelper::DONT_PROCEED) {
    if (metric_name == "malware")
      RecordAction(UserMetricsAction("MalwareInterstitial.Back"));
    else if (metric_name == "harmful")
      RecordAction(UserMetricsAction("HarmfulInterstitial.Back"));
    else if (metric_name == "ssl_overridable")
      RecordAction(UserMetricsAction("SSLOverridableInterstitial.Back"));
    else if (metric_name == "ssl_nonoverridable")
      RecordAction(UserMetricsAction("SSLNonOverridableInsterstitial.Back"));
    else if (metric_name == "bad_clock")
      RecordAction(UserMetricsAction("BadClockInterstitial.Back"));
  }
}

void MaybeRecordInteractionAsAction(MetricsHelper::Interaction interaction,
                                    const std::string& metric_name) {
  if (interaction == MetricsHelper::TOTAL_VISITS) {
    if (metric_name == "malware")
      RecordAction(UserMetricsAction("MalwareInterstitial.Show"));
    else if (metric_name == "harmful")
      RecordAction(UserMetricsAction("HarmfulInterstitial.Show"));
    else if (metric_name == "ssl_overridable")
      RecordAction(UserMetricsAction("SSLOverridableInterstitial.Show"));
    else if (metric_name == "ssl_nonoverridable")
      RecordAction(UserMetricsAction("SSLNonOverridableInterstitial.Show"));
    else if (metric_name == "bad_clock")
      RecordAction(UserMetricsAction("BadClockInterstitial.Show"));
  } else if (interaction == MetricsHelper::SHOW_ADVANCED) {
    if (metric_name == "malware") {
      RecordAction(UserMetricsAction("MalwareInterstitial.Advanced"));
    } else if (metric_name == "harmful") {
      RecordAction(UserMetricsAction("HarmfulInterstitial.Advanced"));
    } else if (metric_name == "ssl_overridable" ||
               metric_name == "ssl_nonoverridable") {
      RecordAction(UserMetricsAction("SSLInterstitial.Advanced"));
    }
  } else if (interaction == MetricsHelper::RELOAD) {
    if (metric_name == "ssl_nonoverridable")
      RecordAction(UserMetricsAction("SSLInterstitial.Reload"));
  } else if (interaction == MetricsHelper::OPEN_TIME_SETTINGS) {
    if (metric_name == "bad_clock")
      RecordAction(UserMetricsAction("BadClockInterstitial.Settings"));
  }
}

}  // namespace

MetricsHelper::ReportDetails::ReportDetails()
    : rappor_report_type(rappor::NUM_RAPPOR_TYPES) {}

MetricsHelper::MetricsHelper(const GURL& request_url,
                             const ReportDetails settings,
                             history::HistoryService* history_service,
                             rappor::RapporService* rappor_service)
    : request_url_(request_url),
      settings_(settings),
      rappor_service_(rappor_service),
      num_visits_(-1) {
  DCHECK(!settings_.metric_prefix.empty());
  if (settings_.rappor_report_type == rappor::NUM_RAPPOR_TYPES)  // Default.
    rappor_service_ = nullptr;
  DCHECK(!rappor_service_ || !settings_.rappor_prefix.empty());
  if (history_service) {
    history_service->GetVisibleVisitCountToHost(
        request_url_,
        base::Bind(&MetricsHelper::OnGotHistoryCount, base::Unretained(this)),
        &request_tracker_);
  }
}

void MetricsHelper::RecordUserDecision(Decision decision) {
  const std::string histogram_name(
      "interstitial." + settings_.metric_prefix + ".decision");
  RecordUserDecisionToMetrics(decision, histogram_name);
  // Record additional information about sites that users have visited before.
  // Report |decision| and SHOW together, filtered by the same history state
  // so they they are paired regardless of when if num_visits_ is populated.
  if (num_visits_ > 0 && (decision == PROCEED || decision == DONT_PROCEED)) {
    RecordUserDecisionToMetrics(SHOW, histogram_name + ".repeat_visit");
    RecordUserDecisionToMetrics(decision, histogram_name + ".repeat_visit");
  }

  MaybeRecordDecisionAsAction(decision, settings_.metric_prefix);
  RecordUserDecisionToRappor(decision);
  RecordExtraUserDecisionMetrics(decision);
}

void MetricsHelper::RecordUserDecisionToMetrics(
    Decision decision,
    const std::string& histogram_name) {
  // Record the decision, and additionally |with extra_suffix|.
  RecordSingleDecisionToMetrics(decision, histogram_name);
  if (!settings_.extra_suffix.empty()) {
    RecordSingleDecisionToMetrics(
        decision, histogram_name + "." + settings_.extra_suffix);
  }
}

void MetricsHelper::RecordUserDecisionToRappor(Decision decision) {
  if (!rappor_service_ || (decision != PROCEED && decision != DONT_PROCEED))
    return;

  scoped_ptr<rappor::Sample> sample =
      rappor_service_->CreateSample(settings_.rappor_report_type);

  // This will populate, for example, "intersitial.malware.domain" or
  // "interstitial.ssl2.domain".  |domain| will be empty for hosts w/o TLDs.
  const std::string domain =
      rappor::GetDomainAndRegistrySampleFromGURL(request_url_);
  sample->SetStringField("domain", domain);

  // Only report history and decision if we have history data.
  if (num_visits_ >= 0) {
    int flags = 0;
    if (decision == PROCEED)
      flags |= 1 << InterstitialFlagBits::DID_PROCEED;
    if (num_visits_ > 0)
      flags |= 1 << InterstitialFlagBits::IS_REPEAT_VISIT;
    // e.g. "interstitial.malware.flags"
    sample->SetFlagsField("flags", flags,
                          InterstitialFlagBits::HIGHEST_USED_BIT + 1);
  }
  rappor_service_->RecordSampleObj("interstitial." + settings_.rappor_prefix,
                                   std::move(sample));
}

void MetricsHelper::RecordUserInteraction(Interaction interaction) {
  const std::string histogram_name(
      "interstitial." + settings_.metric_prefix + ".interaction");
  RecordSingleInteractionToMetrics(interaction, histogram_name);
  if (!settings_.extra_suffix.empty()) {
    RecordSingleInteractionToMetrics(
        interaction, histogram_name + "." + settings_.extra_suffix);
  }

  MaybeRecordInteractionAsAction(interaction, settings_.metric_prefix);
  RecordExtraUserInteractionMetrics(interaction);
}

void MetricsHelper::RecordShutdownMetrics() {
  RecordExtraShutdownMetrics();
}

int MetricsHelper::NumVisits() {
  return num_visits_;
}

void MetricsHelper::OnGotHistoryCount(bool success,
                                      int num_visits,
                                      base::Time /*first_visit*/) {
  if (success)
    num_visits_ = num_visits;
}

}  // namespace security_interstitials
