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

#ifndef CHROME_BROWSER_SYNC_TEST_INTEGRATION_SAVED_TAB_GROUPS_HELPER_H_
#define CHROME_BROWSER_SYNC_TEST_INTEGRATION_SAVED_TAB_GROUPS_HELPER_H_

#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/uuid.h"
#include "chrome/browser/sync/test/integration/fake_server_match_status_checker.h"
#include "chrome/browser/sync/test/integration/status_change_checker.h"
#include "components/saved_tab_groups/public/saved_tab_group.h"
#include "components/saved_tab_groups/public/saved_tab_group_tab.h"
#include "components/saved_tab_groups/public/tab_group_sync_service.h"
#include "components/saved_tab_groups/public/types.h"
#include "components/sync/base/data_type.h"
#include "testing/gmock/include/gmock/gmock.h"

namespace sync_pb {
class SavedTabGroupSpecifics;
}  // namespace sync_pb

namespace tab_groups {

MATCHER_P2(HasSpecificsSavedTabGroup, title, color, "") {
  return arg.group().title() == title && arg.group().color() == color;
}

MATCHER_P2(HasSpecificsSavedTab, title, url, "") {
  return arg.tab().title() == title && arg.tab().url() == url;
}

MATCHER_P2(HasSavedGroupMetadata, title, color, "") {
  return arg.title() == title && arg.color() == color;
}

// Checks that a tab or group with a particular uuid exists in the service.
class SavedTabOrGroupExistsChecker : public StatusChangeChecker,
                                     public TabGroupSyncService::Observer {
 public:
  // `service` must not be null and must outlive this object.
  SavedTabOrGroupExistsChecker(TabGroupSyncService* service,
                               const base::Uuid& uuid);
  SavedTabOrGroupExistsChecker(const SavedTabOrGroupExistsChecker&) = delete;
  SavedTabOrGroupExistsChecker& operator=(const SavedTabOrGroupExistsChecker&) =
      delete;
  ~SavedTabOrGroupExistsChecker() override;

  // StatusChangeChecker:
  bool IsExitConditionSatisfied(std::ostream* os) override;

  // TabGroupSyncService::Observer
  void OnTabGroupAdded(const SavedTabGroup& group,
                       TriggerSource source) override;
  void OnTabGroupUpdated(const SavedTabGroup& group,
                         TriggerSource source) override;
  void OnTabGroupMigrated(const SavedTabGroup& shared_group,
                          const base::Uuid& old_sync_id,
                          TriggerSource source) override;

 private:
  const base::Uuid uuid_;
  raw_ptr<TabGroupSyncService> const service_;
};

// Checks that a tab or group with a particular uuid does not exist in the
// service.
class SavedTabOrGroupDoesNotExistChecker
    : public StatusChangeChecker,
      public TabGroupSyncService::Observer {
 public:
  // `service` must not be null and must outlive this object.
  SavedTabOrGroupDoesNotExistChecker(TabGroupSyncService* service,
                                     const base::Uuid& uuid);
  SavedTabOrGroupDoesNotExistChecker(
      const SavedTabOrGroupDoesNotExistChecker&) = delete;
  SavedTabOrGroupDoesNotExistChecker& operator=(
      const SavedTabOrGroupDoesNotExistChecker&) = delete;
  ~SavedTabOrGroupDoesNotExistChecker() override;

  // StatusChangeChecker implementation.
  bool IsExitConditionSatisfied(std::ostream* os) override;

  // TabGroupSyncService::Observer
  // Note: Also handles the removal of tabs.
  void OnTabGroupUpdated(const SavedTabGroup& group,
                         TriggerSource source) override;
  void OnTabGroupRemoved(const base::Uuid& sync_id,
                         TriggerSource source) override;

 private:
  const base::Uuid uuid_;
  raw_ptr<TabGroupSyncService> const service_;
};

// Checks that the number of expected saved tab groups matches with the service.
class SavedTabGroupCountMatchesChecker : public StatusChangeChecker,
                                         public TabGroupSyncService::Observer {
 public:
  // The caller must ensure that `service` is not null and will outlive this
  // object.
  SavedTabGroupCountMatchesChecker(TabGroupSyncService* service,
                                   size_t expected_tab_group_count,
                                   bool count_shared_only);
  SavedTabGroupCountMatchesChecker(const SavedTabGroupCountMatchesChecker&) =
      delete;
  SavedTabGroupCountMatchesChecker& operator=(
      const SavedTabGroupCountMatchesChecker&) = delete;
  ~SavedTabGroupCountMatchesChecker() override;

  // StatusChangeChecker implementation.
  bool IsExitConditionSatisfied(std::ostream* os) override;

  // TabGroupSyncService::Observer.
  void OnTabGroupAdded(const SavedTabGroup& group,
                       TriggerSource source) override;
  void OnTabGroupRemoved(const base::Uuid& sync_id,
                         TriggerSource source) override;

 private:
  raw_ptr<TabGroupSyncService> const service_;
  const size_t expected_tab_group_count_;
  const bool count_shared_only_;
};

// Checks that a matching group exists in the service.
class SavedTabGroupMatchesChecker : public StatusChangeChecker,
                                    public TabGroupSyncService::Observer {
 public:
  // The caller must ensure that `service` is not null and will outlive this
  // object.
  SavedTabGroupMatchesChecker(TabGroupSyncService* service,
                              SavedTabGroup group);
  SavedTabGroupMatchesChecker(const SavedTabGroupMatchesChecker&) = delete;
  SavedTabGroupMatchesChecker& operator=(const SavedTabGroupMatchesChecker&) =
      delete;
  ~SavedTabGroupMatchesChecker() override;

  // StatusChangeChecker implementation.
  bool IsExitConditionSatisfied(std::ostream* os) override;

  // TabGroupSyncService::Observer
  void OnTabGroupAdded(const SavedTabGroup& group,
                       TriggerSource source) override;
  void OnTabGroupUpdated(const SavedTabGroup& group,
                         TriggerSource source) override;

 private:
  const SavedTabGroup group_;
  raw_ptr<TabGroupSyncService> const service_;
};

// Checks that a matching tab exists in the service.
class SavedTabMatchesChecker : public StatusChangeChecker,
                               public TabGroupSyncService::Observer {
 public:
  // The caller must ensure that `service` is not null and will outlive this
  // object.
  SavedTabMatchesChecker(TabGroupSyncService* service, SavedTabGroupTab tab);
  SavedTabMatchesChecker(const SavedTabMatchesChecker&) = delete;
  SavedTabMatchesChecker& operator=(const SavedTabMatchesChecker&) = delete;
  ~SavedTabMatchesChecker() override;

  // StatusChangeChecker implementation.
  bool IsExitConditionSatisfied(std::ostream* os) override;

  // TabGroupSyncService::Observer
  void OnTabGroupAdded(const SavedTabGroup& group,
                       TriggerSource source) override;
  void OnTabGroupUpdated(const SavedTabGroup& group,
                         TriggerSource source) override;

 private:
  const SavedTabGroupTab tab_;
  raw_ptr<TabGroupSyncService> const service_;
};

// Checks that the service contains saved groups in a certain order.
class GroupOrderChecker : public StatusChangeChecker,
                          public TabGroupSyncService::Observer {
 public:
  // The caller must ensure that `service` is not null and will outlive this
  // object.
  GroupOrderChecker(TabGroupSyncService* service,
                    std::vector<base::Uuid> group_ids);
  GroupOrderChecker(const GroupOrderChecker&) = delete;
  GroupOrderChecker& operator=(const GroupOrderChecker&) = delete;
  ~GroupOrderChecker() override;

  // StatusChangeChecker implementation.
  bool IsExitConditionSatisfied(std::ostream* os) override;

  // TabGroupSyncService::Observer
  void OnTabGroupAdded(const SavedTabGroup& group,
                       TriggerSource source) override;
  void OnTabGroupUpdated(const SavedTabGroup& group,
                         TriggerSource source) override;
  void OnTabGroupRemoved(const base::Uuid& sync_id,
                         TriggerSource source) override;

 private:
  const std::vector<base::Uuid> group_ids_;
  raw_ptr<TabGroupSyncService> const service_;
};

// Checks that a saved group in the service contains tabs in a certain order.
class TabOrderChecker : public StatusChangeChecker,
                        public TabGroupSyncService::Observer {
 public:
  // The caller must ensure that `service` is not null and will outlive this
  // object.
  TabOrderChecker(TabGroupSyncService* service,
                  base::Uuid group_id,
                  std::vector<base::Uuid> tab_ids);
  TabOrderChecker(const TabOrderChecker&) = delete;
  TabOrderChecker& operator=(const TabOrderChecker&) = delete;
  ~TabOrderChecker() override;

  // StatusChangeChecker implementation.
  bool IsExitConditionSatisfied(std::ostream* os) override;

  // TabGroupSyncService::Observer
  void OnTabGroupAdded(const SavedTabGroup& group,
                       TriggerSource source) override;
  void OnTabGroupUpdated(const SavedTabGroup& group,
                         TriggerSource source) override;

 private:
  const base::Uuid group_id_;
  const std::vector<base::Uuid> tab_ids_;

  raw_ptr<TabGroupSyncService> const service_;
};

// A helper class that waits for the SAVED_TAB_GROUP entities on the FakeServer
// to match a given GMock matcher.
class ServerSavedTabGroupMatchChecker
    : public fake_server::FakeServerMatchStatusChecker {
 public:
  using Matcher =
      testing::Matcher<std::vector<sync_pb::SavedTabGroupSpecifics>>;

  explicit ServerSavedTabGroupMatchChecker(const Matcher& matcher);
  ~ServerSavedTabGroupMatchChecker() override;

  // fake_server::FakeServerMatchStatusChecker overrides.
  bool IsExitConditionSatisfied(std::ostream* os) override;

 private:
  const Matcher matcher_;
};

}  // namespace tab_groups

#endif  // CHROME_BROWSER_SYNC_TEST_INTEGRATION_SAVED_TAB_GROUPS_HELPER_H_
