// Copyright 2020 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 "content/browser/conversions/conversion_storage_delegate_impl.h"

namespace content {

ConversionStorageDelegateImpl::ConversionStorageDelegateImpl(bool debug_mode)
    : debug_mode_(debug_mode) {
  DETACH_FROM_SEQUENCE(sequence_checker_);
}

void ConversionStorageDelegateImpl::ProcessNewConversionReports(
    std::vector<ConversionReport>* reports) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(!reports->empty());
  ConversionReport* last_report = &(reports->at(0));

  // Assign attribution credits to each report that will be sent at report time.
  // This performs "last click" attribution which assigns the report
  // for the most recent impression a credit of 100, and the rest a credit of 0.
  for (ConversionReport& report : *reports) {
    report.report_time = GetReportTimeForConversion(report);

    report.attribution_credit = 0;
    if (report.impression.impression_time() >
        last_report->impression.impression_time())
      last_report = &report;
  }

  last_report->attribution_credit = 100;
}

int ConversionStorageDelegateImpl::GetMaxConversionsPerImpression() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return 3;
}

int ConversionStorageDelegateImpl::GetMaxImpressionsPerOrigin() const {
  return 1024;
}

int ConversionStorageDelegateImpl::GetMaxConversionsPerOrigin() const {
  return 1024;
}

base::Time ConversionStorageDelegateImpl::GetReportTimeForConversion(
    const ConversionReport& report) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  //  |report.report_time| is roughly ~now, for newly created conversion
  //  reports. If in debug mode, the report should be sent immediately.
  if (debug_mode_)
    return report.report_time;

  // After the initial impression, a schedule of reporting windows and deadlines
  // associated with that impression begins. The time between impression time
  // and impression expiry is split into multiple reporting windows. At the end
  // of each window, the browser will send all scheduled reports for that
  // impression.
  //
  // Each reporting window has a deadline and only conversions registered before
  // that deadline are sent in that window. Each deadline is one hour prior to
  // the window report time. The deadlines relative to impression time are <2
  // days minus 1 hour, 7 days minus 1 hour, impression expiry>. The impression
  // expiry window is only used for conversions that occur after the 7 day
  // deadline. For example, a conversion which happens one hour after an
  // impression with an expiry of two hours, is still reported in the 2 day
  // window.
  constexpr base::TimeDelta kWindowDeadlineOffset =
      base::TimeDelta::FromHours(1);
  base::TimeDelta expiry_deadline =
      report.impression.expiry_time() - report.impression.impression_time();
  const base::TimeDelta kReportingWindowDeadlines[] = {
      base::TimeDelta::FromDays(2) - kWindowDeadlineOffset,
      base::TimeDelta::FromDays(7) - kWindowDeadlineOffset, expiry_deadline};

  base::TimeDelta deadline_to_use;

  // Given a conversion report that was created at |report.report_time|, find
  // the first applicable reporting window this conversion should be reported
  // at.
  for (base::TimeDelta report_window_deadline : kReportingWindowDeadlines) {
    // If this window is valid for the conversion, use it. |report.report_time|
    // is roughly ~now, as the conversion time is used as the default value for
    // newly created reports that have not had a report time set.
    if (report.impression.impression_time() + report_window_deadline >=
        report.report_time) {
      deadline_to_use = report_window_deadline;
      break;
    }
  }

  // Valid conversion reports should always have a valid reporting deadline.
  DCHECK(!deadline_to_use.is_zero());

  // If the expiry deadline falls after the first window, but before another
  // window, use it instead. For example, if expiry is at 3 days, we can send
  // reports at the 2 day deadline and the expiry deadline instead of at the 7
  // day deadline.
  if (expiry_deadline > kReportingWindowDeadlines[0] &&
      expiry_deadline < deadline_to_use) {
    deadline_to_use = expiry_deadline;
  }

  return report.impression.impression_time() + deadline_to_use +
         kWindowDeadlineOffset;
}

}  // namespace content
