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

#include "chrome/browser/notifications/scheduler/internal/scheduler_utils.h"

#include <algorithm>
#include <vector>

#include "base/containers/circular_deque.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "base/uuid.h"
#include "chrome/browser/notifications/scheduler/internal/impression_types.h"
#include "chrome/browser/notifications/scheduler/test/fake_clock.h"
#include "chrome/browser/notifications/scheduler/test/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace notifications {
namespace {

const char kFakeNow[] = "01/01/18 01:23:45 AM";

class SchedulerUtilsTest : public testing::Test {
 public:
  SchedulerUtilsTest() = default;
  SchedulerUtilsTest(const SchedulerUtilsTest&) = delete;
  SchedulerUtilsTest& operator=(const SchedulerUtilsTest&) = delete;
  ~SchedulerUtilsTest() override = default;

  void SetUp() override { config_.initial_daily_shown_per_type = 100; }

  void InitFakeClock() {
    clock_.SetNow(kFakeNow);
    ToLocalHour(0, clock_.Now(), 0, &beginning_of_today_);
  }

  void CreateFakeImpressions(ClientState* client_state,
                             const std::vector<base::Time>& times) {
    DCHECK(client_state);
    client_state->impressions.clear();
    auto type = client_state->type;
    for (const auto& time : times) {
      client_state->impressions.emplace_back(
          type, base::Uuid::GenerateRandomV4().AsLowercaseString(), time);
    }
  }

  std::unique_ptr<ClientState> CreateFakeClientStateWithImpression(
      SchedulerClientType type,
      const SchedulerConfig& config,
      const std::vector<base::Time>& times) {
    auto client_state = std::make_unique<ClientState>();
    client_state->type = type;
    client_state->current_max_daily_show = config.initial_daily_shown_per_type;
    CreateFakeImpressions(client_state.get(), times);
    return client_state;
  }

 protected:
  SchedulerConfig& config() { return config_; }
  test::FakeClock* clock() { return &clock_; }
  base::Time& beginning_of_today() { return beginning_of_today_; }

 private:
  base::test::TaskEnvironment task_environment_;
  test::FakeClock clock_;
  SchedulerConfig config_;
  base::Time beginning_of_today_;
};

// Verifies we can get the correct time stamp at certain hour in yesterday.
TEST_F(SchedulerUtilsTest, ToLocalHour) {
  base::Time today, another_day, expected;

  // Timestamp of another day in the past.
  EXPECT_TRUE(base::Time::FromString("10/15/07 12:45:12 PM", &today));
  EXPECT_TRUE(ToLocalHour(6, today, -1, &another_day));
  EXPECT_TRUE(base::Time::FromString("10/14/07 06:00:00 AM", &expected));
  EXPECT_EQ(expected, another_day);

  EXPECT_TRUE(base::Time::FromString("03/25/19 00:00:00 AM", &today));
  EXPECT_TRUE(ToLocalHour(0, today, -1, &another_day));
  EXPECT_TRUE(base::Time::FromString("03/24/19 00:00:00 AM", &expected));
  EXPECT_EQ(expected, another_day);

  // Timestamp of the same day.
  EXPECT_TRUE(base::Time::FromString("03/25/19 00:00:00 AM", &today));
  EXPECT_TRUE(ToLocalHour(0, today, 0, &another_day));
  EXPECT_TRUE(base::Time::FromString("03/25/19 00:00:00 AM", &expected));
  EXPECT_EQ(expected, another_day);

  // Timestamp of another day in the future.
  EXPECT_TRUE(base::Time::FromString("03/25/19 06:35:27 AM", &today));
  EXPECT_TRUE(ToLocalHour(16, today, 7, &another_day));
  EXPECT_TRUE(base::Time::FromString("04/01/19 16:00:00 PM", &expected));
  EXPECT_EQ(expected, another_day);
}

TEST_F(SchedulerUtilsTest, NotificationsShownTodayMultipleClients) {
  InitFakeClock();
  base::Time now = clock()->Now();
  // Create fake clients.
  std::map<SchedulerClientType, const ClientState*> client_states;
  //            begin_of_today         now                  end_of_today
  // client1  * |                   *  *                    | *
  // client2  * *   *                  |  *                 | *
  // client3  * |                      |                    | *

  std::vector<base::Time> create_times = {
      now - base::Seconds(2) /*today*/, now - base::Seconds(1) /*today*/,
      beginning_of_today() - base::Seconds(1) /*yesterday*/,
      beginning_of_today() + base::Days(1) /*tomorrow*/};
  auto new_client1 = CreateFakeClientStateWithImpression(
      SchedulerClientType::kTest1, config(), create_times);

  create_times = {now /*today*/,
                  beginning_of_today() + base::Days(1) /*tomorrow*/,
                  beginning_of_today() - base::Seconds(1) /*yesterday*/,
                  beginning_of_today() + base::Seconds(1) /*today*/,
                  beginning_of_today() /*today*/};
  auto new_client2 = CreateFakeClientStateWithImpression(
      SchedulerClientType::kTest2, config(), create_times);

  create_times = {
      beginning_of_today() - base::Seconds(2), /*yesterday*/
      beginning_of_today() + base::Days(1)     /*tomorrow*/
  };
  auto new_client3 = CreateFakeClientStateWithImpression(
      SchedulerClientType::kTest3, config(), create_times);

  client_states[SchedulerClientType::kTest1] = new_client1.get();
  client_states[SchedulerClientType::kTest2] = new_client2.get();
  client_states[SchedulerClientType::kTest3] = new_client3.get();

  std::map<SchedulerClientType, int> shown_per_type;
  int shown_total = 0;
  SchedulerClientType last_shown_type = SchedulerClientType::kUnknown;
  NotificationsShownToday(client_states, &shown_per_type, &shown_total,
                          &last_shown_type, clock());
  EXPECT_EQ(shown_total, 5);
  EXPECT_EQ(last_shown_type, SchedulerClientType::kTest2);
  EXPECT_EQ(shown_per_type.at(SchedulerClientType::kTest1), 2);
  EXPECT_EQ(shown_per_type.at(SchedulerClientType::kTest2), 3);
  EXPECT_EQ(shown_per_type.at(SchedulerClientType::kTest3), 0);
}

TEST_F(SchedulerUtilsTest, NotificationsShownToday) {
  // Create fake client.
  auto new_client = CreateNewClientState(SchedulerClientType::kTest1, config());
  InitFakeClock();
  base::Time now = clock()->Now();
  // Test case 1:
  int count = NotificationsShownToday(new_client.get(), clock());
  EXPECT_EQ(count, 0);

  // Test case 2:
  std::vector<base::Time> create_times = {
      now /*today*/, beginning_of_today() + base::Days(1) /*tomorrow*/,
      beginning_of_today() - base::Seconds(1) /*yesterday*/,
      beginning_of_today() + base::Seconds(1) /*today*/,
      beginning_of_today() /*today*/};

  CreateFakeImpressions(new_client.get(), create_times);
  count = NotificationsShownToday(new_client.get(), clock());
  EXPECT_EQ(count, 3);

  // Test case 3:
  create_times = {
      beginning_of_today() - base::Seconds(2), /*yesterday*/
      beginning_of_today() + base::Days(1),    /*tomorrow*/
  };
  CreateFakeImpressions(new_client.get(), create_times);
  count = NotificationsShownToday(new_client.get(), clock());
  EXPECT_EQ(count, 0);

  // Test case 4:
  create_times = {now /*today*/, now - base::Seconds(1) /*today*/,
                  beginning_of_today() - base::Seconds(1) /*yesterday*/,
                  beginning_of_today() + base::Days(1) /*tomorrow*/};
  CreateFakeImpressions(new_client.get(), create_times);
  count = NotificationsShownToday(new_client.get(), clock());
  EXPECT_EQ(count, 2);
}

}  // namespace
}  // namespace notifications
