// Copyright 2014 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/suggestions/blacklist_store.h"

#include <memory>
#include <set>
#include <string>

#include "base/macros.h"
#include "base/test/metrics/histogram_tester.h"
#include "components/suggestions/proto/suggestions.pb.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "testing/gtest/include/gtest/gtest.h"

using sync_preferences::TestingPrefServiceSyncable;

namespace suggestions {

namespace {

const char kTestUrlA[] = "http://aaa.com/";
const char kTestUrlB[] = "http://bbb.com/";
const char kTestUrlC[] = "http://ccc.com/";
const char kTestUrlD[] = "http://ddd.com/";

SuggestionsProfile CreateSuggestions(std::set<std::string> urls) {
  SuggestionsProfile suggestions;
  for (std::set<std::string>::iterator it = urls.begin(); it != urls.end();
       ++it) {
    ChromeSuggestion* suggestion = suggestions.add_suggestions();
    suggestion->set_url(*it);
  }
  suggestions.set_timestamp(123);
  return suggestions;
}

void ValidateSuggestions(const SuggestionsProfile& expected,
                         const SuggestionsProfile& actual) {
  ASSERT_EQ(expected.suggestions_size(), actual.suggestions_size());
  for (int i = 0; i < expected.suggestions_size(); ++i) {
    EXPECT_EQ(expected.suggestions(i).url(), actual.suggestions(i).url());
    EXPECT_EQ(expected.suggestions(i).title(), actual.suggestions(i).title());
    EXPECT_EQ(expected.suggestions(i).favicon_url(),
              actual.suggestions(i).favicon_url());
    EXPECT_EQ(expected.suggestions(i).thumbnail(),
              actual.suggestions(i).thumbnail());
  }
}

}  // namespace

class BlacklistStoreTest : public testing::Test {
 public:
  BlacklistStoreTest()
      : pref_service_(new sync_preferences::TestingPrefServiceSyncable) {}

  void SetUp() override {
    BlacklistStore::RegisterProfilePrefs(pref_service()->registry());
  }

  sync_preferences::TestingPrefServiceSyncable* pref_service() {
    return pref_service_.get();
  }

 private:
  std::unique_ptr<sync_preferences::TestingPrefServiceSyncable> pref_service_;

  DISALLOW_COPY_AND_ASSIGN(BlacklistStoreTest);
};

// Tests adding, removing to the blacklist and filtering.
TEST_F(BlacklistStoreTest, BasicInteractions) {
  BlacklistStore blacklist_store(pref_service());

  // Create suggestions with A, B and C. C and D will be added to the blacklist.
  std::set<std::string> suggested_urls;
  suggested_urls.insert(kTestUrlA);
  suggested_urls.insert(kTestUrlB);
  const SuggestionsProfile suggestions_filtered =
      CreateSuggestions(suggested_urls);
  suggested_urls.insert(kTestUrlC);
  const SuggestionsProfile original_suggestions =
      CreateSuggestions(suggested_urls);
  SuggestionsProfile suggestions;

  // Filter with an empty blacklist.
  suggestions.CopyFrom(original_suggestions);
  blacklist_store.FilterSuggestions(&suggestions);
  ValidateSuggestions(original_suggestions, suggestions);

  // Add C and D to the blacklist and filter.
  suggestions.CopyFrom(original_suggestions);
  EXPECT_TRUE(blacklist_store.BlacklistUrl(GURL(kTestUrlC)));
  EXPECT_TRUE(blacklist_store.BlacklistUrl(GURL(kTestUrlD)));
  blacklist_store.FilterSuggestions(&suggestions);
  ValidateSuggestions(suggestions_filtered, suggestions);

  // Remove C from the blacklist and filter.
  suggestions.CopyFrom(original_suggestions);
  EXPECT_TRUE(blacklist_store.RemoveUrl(GURL(kTestUrlC)));
  blacklist_store.FilterSuggestions(&suggestions);
  ValidateSuggestions(original_suggestions, suggestions);
}

TEST_F(BlacklistStoreTest, BlacklistTwiceSuceeds) {
  BlacklistStore blacklist_store(pref_service());
  EXPECT_TRUE(blacklist_store.BlacklistUrl(GURL(kTestUrlA)));
  EXPECT_TRUE(blacklist_store.BlacklistUrl(GURL(kTestUrlA)));
}

TEST_F(BlacklistStoreTest, RemoveUnknownUrlFails) {
  BlacklistStore blacklist_store(pref_service());
  EXPECT_FALSE(blacklist_store.RemoveUrl(GURL(kTestUrlA)));
}

TEST_F(BlacklistStoreTest, TestGetTimeUntilReadyForUpload) {
  // Tests assumes completion within 1 hour.
  base::TimeDelta upload_delay = base::TimeDelta::FromHours(1);
  base::TimeDelta no_delay = base::TimeDelta::FromHours(0);
  std::unique_ptr<BlacklistStore> blacklist_store(
      new BlacklistStore(pref_service(), upload_delay));
  base::TimeDelta candidate_delta;

  // Blacklist is empty.
  EXPECT_FALSE(blacklist_store->GetTimeUntilReadyForUpload(&candidate_delta));
  EXPECT_FALSE(blacklist_store->GetTimeUntilURLReadyForUpload(
      GURL(kTestUrlA), &candidate_delta));

  // Blacklist contains kTestUrlA.
  EXPECT_TRUE(blacklist_store->BlacklistUrl(GURL(kTestUrlA)));
  candidate_delta = upload_delay + base::TimeDelta::FromDays(1);
  EXPECT_TRUE(blacklist_store->GetTimeUntilReadyForUpload(&candidate_delta));
  EXPECT_LE(candidate_delta, upload_delay);
  EXPECT_GE(candidate_delta, no_delay);
  candidate_delta = upload_delay + base::TimeDelta::FromDays(1);
  EXPECT_TRUE(blacklist_store->GetTimeUntilURLReadyForUpload(
      GURL(kTestUrlA), &candidate_delta));
  EXPECT_LE(candidate_delta, upload_delay);
  EXPECT_GE(candidate_delta, no_delay);
  EXPECT_FALSE(blacklist_store->GetTimeUntilURLReadyForUpload(
    GURL(kTestUrlB), &candidate_delta));

  // There should be no candidate for upload since the upload delay is 1 day.
  // Note: this is a test that relies on timing.
  GURL retrieved;
  EXPECT_FALSE(blacklist_store->GetCandidateForUpload(&retrieved));

  // Same, but with an upload delay of 0.
  blacklist_store.reset(new BlacklistStore(pref_service(), no_delay));
  EXPECT_TRUE(blacklist_store->BlacklistUrl(GURL(kTestUrlA)));
  candidate_delta = no_delay + base::TimeDelta::FromDays(1);
  EXPECT_TRUE(blacklist_store->GetTimeUntilReadyForUpload(&candidate_delta));
  EXPECT_EQ(candidate_delta, no_delay);
  candidate_delta = no_delay + base::TimeDelta::FromDays(1);
  EXPECT_TRUE(blacklist_store->GetTimeUntilURLReadyForUpload(
      GURL(kTestUrlA), &candidate_delta));
  EXPECT_EQ(candidate_delta, no_delay);
}

TEST_F(BlacklistStoreTest, GetCandidateForUpload) {
  BlacklistStore blacklist_store(pref_service(), base::TimeDelta::FromDays(0));
  // Empty blacklist.
  GURL retrieved;
  EXPECT_FALSE(blacklist_store.GetCandidateForUpload(&retrieved));

  // Blacklist A and B. Expect to retrieve A or B.
  EXPECT_TRUE(blacklist_store.BlacklistUrl(GURL(kTestUrlA)));
  EXPECT_TRUE(blacklist_store.BlacklistUrl(GURL(kTestUrlB)));
  EXPECT_TRUE(blacklist_store.GetCandidateForUpload(&retrieved));
  std::string retrieved_string = retrieved.spec();
  EXPECT_TRUE(retrieved_string == std::string(kTestUrlA) ||
              retrieved_string == std::string(kTestUrlB));
}

TEST_F(BlacklistStoreTest, LogsBlacklistSize) {
  base::HistogramTester histogram_tester;

  // Create a first store - blacklist is empty at this point.
  std::unique_ptr<BlacklistStore> blacklist_store(
      new BlacklistStore(pref_service()));
  histogram_tester.ExpectTotalCount("Suggestions.LocalBlacklistSize", 1);
  histogram_tester.ExpectUniqueSample("Suggestions.LocalBlacklistSize", 0, 1);

  // Add some content to the blacklist.
  EXPECT_TRUE(blacklist_store->BlacklistUrl(GURL(kTestUrlA)));
  EXPECT_TRUE(blacklist_store->BlacklistUrl(GURL(kTestUrlB)));

  // Create a new BlacklistStore and verify the counts.
  blacklist_store.reset(new BlacklistStore(pref_service()));
  histogram_tester.ExpectTotalCount("Suggestions.LocalBlacklistSize", 2);
  histogram_tester.ExpectBucketCount("Suggestions.LocalBlacklistSize", 0, 1);
  histogram_tester.ExpectBucketCount("Suggestions.LocalBlacklistSize", 2, 1);
}

}  // namespace suggestions
