// Copyright 2013 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/sync/sync_startup_tracker.h"

#include <optional>

#include "base/functional/bind.h"
#include "base/notreached.h"
#include "components/sync/service/sync_service.h"

namespace {

constexpr base::TimeDelta kDefaultWaitTimeout = base::Seconds(10);

std::optional<base::TimeDelta> g_wait_timeout = kDefaultWaitTimeout;

}  // namespace

SyncStartupTracker::SyncStartupTracker(syncer::SyncService* sync_service,
                                       SyncStartupStateChangedCallback callback)
    : sync_startup_status_changed_callback_(std::move(callback)) {
  DCHECK(sync_service);
  sync_service_observation_.Observe(sync_service);
  if (g_wait_timeout.has_value()) {
    timeout_waiter_.Start(FROM_HERE, g_wait_timeout.value(),
                          base::BindOnce(&SyncStartupTracker::OnStartupTimeout,
                                         weak_factory_.GetWeakPtr()));
  }

  CheckServiceState();
}

SyncStartupTracker::~SyncStartupTracker() = default;

void SyncStartupTracker::OnStateChanged(syncer::SyncService* sync) {
  CheckServiceState();
}

void SyncStartupTracker::OnSyncShutdown(syncer::SyncService* sync) {
  sync_service_observation_.Reset();
}

void SyncStartupTracker::OnStartupTimeout() {
  is_timed_out_ = true;
  CheckServiceState();
}

void SyncStartupTracker::CheckServiceState() {
  ServiceStartupState state =
      GetServiceStartupState(sync_service_observation_.GetSource());
  if (state == ServiceStartupState::kPending) {
    if (is_timed_out_) {
      state = ServiceStartupState::kTimeout;
    } else {
      // Do nothing - still waiting for sync to finish starting up.
      return;
    }
  }

  timeout_waiter_.Stop();
  sync_service_observation_.Reset();

  DCHECK(sync_startup_status_changed_callback_);
  std::move(sync_startup_status_changed_callback_).Run(state);
}

// static
SyncStartupTracker::ServiceStartupState
SyncStartupTracker::GetServiceStartupState(syncer::SyncService* sync_service) {
  // If no service exists or it can't be started, treat as a startup error.
  if (!sync_service || !sync_service->CanSyncFeatureStart()) {
    return ServiceStartupState::kError;
  }

  // Unrecoverable errors return false for CanSyncFeatureStart(), handled above.
  DCHECK(!sync_service->HasUnrecoverableError());

  switch (sync_service->GetTransportState()) {
    case syncer::SyncService::TransportState::DISABLED:
      NOTREACHED();
    case syncer::SyncService::TransportState::START_DEFERRED:
    case syncer::SyncService::TransportState::INITIALIZING:
      // No error detected yet, but the sync engine hasn't started up yet, so
      // we're in the pending state.
      return ServiceStartupState::kPending;
    case syncer::SyncService::TransportState::PAUSED:
      // Persistent auth errors lead to sync pausing.
      return ServiceStartupState::kError;
    case syncer::SyncService::TransportState::PENDING_DESIRED_CONFIGURATION:
    case syncer::SyncService::TransportState::CONFIGURING:
    case syncer::SyncService::TransportState::ACTIVE:
      DCHECK(sync_service->IsEngineInitialized());
      return ServiceStartupState::kComplete;
  }

  NOTREACHED();
}

namespace testing {

ScopedSyncStartupTimeoutOverride::ScopedSyncStartupTimeoutOverride(
    std::optional<base::TimeDelta> wait_timeout) {
  old_wait_timeout_ = g_wait_timeout;
  g_wait_timeout = wait_timeout;
}

ScopedSyncStartupTimeoutOverride::~ScopedSyncStartupTimeoutOverride() {
  g_wait_timeout = old_wait_timeout_;
}

}  // namespace testing
