// 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 <stddef.h>

#include <sstream>

#include "base/command_line.h"
#include "base/macros.h"
#include "base/memory/linked_ptr.h"
#include "base/observer_list_threadsafe.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/extensions/api/passwords_private/passwords_private_delegate.h"
#include "chrome/browser/extensions/api/passwords_private/passwords_private_delegate_factory.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/common/extensions/api/passwords_private.h"
#include "chrome/test/base/testing_profile.h"
#include "components/keyed_service/core/keyed_service.h"
#include "content/public/test/test_utils.h"
#include "extensions/common/switches.h"

namespace extensions {

namespace {

static const size_t kNumMocks = 3;
static const int kNumCharactersInPassword = 10;
static const char kPlaintextPassword[] = "plaintext";

linked_ptr<api::passwords_private::PasswordUiEntry> CreateEntry(size_t num) {
  api::passwords_private::PasswordUiEntry* entry =
      new api::passwords_private::PasswordUiEntry();
  std::stringstream ss;
  ss << "http://test" << num << ".com";
  entry->login_pair.origin_url = ss.str();
  ss.clear();
  ss << "testName" << num;
  entry->login_pair.username = ss.str();
  entry->num_characters_in_password = kNumCharactersInPassword;
  return make_linked_ptr(entry);
}

std::string CreateException(size_t num) {
  std::stringstream ss;
  ss << "http://exception" << num << ".com";
  return ss.str();
}

// A test PasswordsPrivateDelegate implementation which uses mock data.
// TestDelegate starts out with kNumMocks mocks of each type (saved password
// and password exception) and removes one mock each time RemoveSavedPassword()
// or RemovePasswordException() is called.
class TestDelegate : public PasswordsPrivateDelegate {
 public:
  TestDelegate() : observers_(new base::ObserverListThreadSafe<Observer>()) {
    // Create mock data.
    for (size_t i = 0; i < kNumMocks; i++) {
      current_entries_.push_back(CreateEntry(i));
      current_exceptions_.push_back(CreateException(i));
    }
  }
  ~TestDelegate() override {}

  void AddObserver(Observer* observer) override {
    observers_->AddObserver(observer);
    SendSavedPasswordsList();
    SendPasswordExceptionsList();
  }

  void RemoveObserver(Observer* observer) override {
    observers_->RemoveObserver(observer);
  }

  void RemoveSavedPassword(
      const std::string& origin_url, const std::string& username) override {
    if (!current_entries_.size())
      return;

    // Since this is just mock data, remove the first entry regardless of
    // the data contained.
    current_entries_.erase(current_entries_.begin());
    SendSavedPasswordsList();
  }

  void RemovePasswordException(const std::string& exception_url) override {
    if (!current_exceptions_.size())
      return;

    // Since this is just mock data, remove the first entry regardless of
    // the data contained.
    current_exceptions_.erase(current_exceptions_.begin());
    SendPasswordExceptionsList();
  }

  void RequestShowPassword(const std::string& origin_url,
                           const std::string& username,
                           content::WebContents* web_contents) override {
    // Return a mocked password value.
    std::string plaintext_password(kPlaintextPassword);
    observers_->Notify(
        FROM_HERE,
        &Observer::OnPlaintextPasswordFetched,
        origin_url,
        username,
        plaintext_password);
  }

 private:
  void SendSavedPasswordsList() {
    observers_->Notify(
        FROM_HERE,
        &Observer::OnSavedPasswordsListChanged,
        current_entries_);
  }

  void SendPasswordExceptionsList() {
    observers_->Notify(
        FROM_HERE,
        &Observer::OnPasswordExceptionsListChanged,
        current_exceptions_);
  }

  // The current list of entries/exceptions. Cached here so that when new
  // observers are added, this delegate can send the current lists without
  // having to request them from |password_manager_presenter_| again.
  std::vector<linked_ptr<api::passwords_private::PasswordUiEntry>>
      current_entries_;
  std::vector<std::string> current_exceptions_;

  // The observers.
  scoped_refptr<base::ObserverListThreadSafe<Observer>> observers_;
};

class PasswordsPrivateApiTest : public ExtensionApiTest {
 public:
  PasswordsPrivateApiTest() {
    if (!s_test_delegate_) {
      s_test_delegate_ = new TestDelegate();
    }
  }
  ~PasswordsPrivateApiTest() override {}

  static scoped_ptr<KeyedService> GetPasswordsPrivateDelegate(
      content::BrowserContext* profile) {
    CHECK(s_test_delegate_);
    return make_scoped_ptr(s_test_delegate_);
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    ExtensionApiTest::SetUpCommandLine(command_line);
  }

  void SetUp() override {
    ExtensionApiTest::SetUp();
  }

  void SetUpOnMainThread() override {
    ExtensionApiTest::SetUpOnMainThread();
    PasswordsPrivateDelegateFactory::GetInstance()->SetTestingFactory(
        profile(), &PasswordsPrivateApiTest::GetPasswordsPrivateDelegate);
    content::RunAllPendingInMessageLoop();
  }

 protected:
  bool RunPasswordsSubtest(const std::string& subtest) {
    return RunExtensionSubtest("passwords_private",
                               "main.html?" + subtest,
                               kFlagLoadAsComponent);
  }

 private:
  static TestDelegate* s_test_delegate_;

  DISALLOW_COPY_AND_ASSIGN(PasswordsPrivateApiTest);
};

// static
TestDelegate* PasswordsPrivateApiTest::s_test_delegate_ = nullptr;

}  // namespace

IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, RemoveSavedPassword) {
  EXPECT_TRUE(RunPasswordsSubtest("removeSavedPassword")) << message_;
}

IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, RemovePasswordException) {
  EXPECT_TRUE(RunPasswordsSubtest("removePasswordException")) << message_;
}

IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, RequestPlaintextPassword) {
  EXPECT_TRUE(RunPasswordsSubtest("requestPlaintextPassword")) << message_;
}

}  // namespace extensions