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

#import "ios/chrome/browser/settings/model/sync/utils/sync_error_infobar_delegate.h"

#import <utility>

#import "base/check.h"
#import "base/memory/ptr_util.h"
#import "base/strings/sys_string_conversions.h"
#import "base/time/time.h"
#import "components/infobars/core/infobar.h"
#import "components/infobars/core/infobar_delegate.h"
#import "components/infobars/core/infobar_manager.h"
#import "components/prefs/pref_service.h"
#import "components/signin/public/base/consent_level.h"
#import "components/signin/public/base/signin_switches.h"
#import "components/sync/service/sync_service.h"
#import "components/sync/service/sync_service_utils.h"
#import "ios/chrome/browser/infobars/model/infobar_ios.h"
#import "ios/chrome/browser/infobars/model/infobar_type.h"
#import "ios/chrome/browser/infobars/model/infobar_utils.h"
#import "ios/chrome/browser/settings/model/sync/utils/sync_util.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/public/commands/sync_presenter_commands.h"
#import "ios/chrome/browser/signin/model/authentication_service.h"
#import "ios/chrome/browser/signin/model/authentication_service_factory.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"

namespace {

// Whether the sync error notification timeout is still active since the last
// error dismissal time.
bool SyncErrorNotificationsPaused(ProfileIOS* profile) {
  CHECK(profile);
  base::Time now = base::Time::Now();
  base::Time last_dismissal = profile->GetPrefs()->GetTime(
      prefs::kIosSyncInfobarErrorLastDismissedTimestamp);

  // In case pref time is set too far in the future due to a system time
  // error, the infobar would not be displayed for a long period. To prevent
  // that, bypass the check if `last_dismissal` is set to more than twice the
  // timeout time in the future.
  if (now + 2 * kSyncErrorInfobarTimeout < last_dismissal) {
    return false;
  }

  return now < last_dismissal + kSyncErrorInfobarTimeout;
}

trusted_vault::TrustedVaultUserActionTriggerForUMA
TrustedVaultTriggerFromInfoBarTrigger(SyncErrorInfoBarTrigger trigger) {
  switch (trigger) {
    case SyncErrorInfoBarTrigger::kNewTabOpened:
      return trusted_vault ::TrustedVaultUserActionTriggerForUMA::
          kNewTabPageInfobar;
    case SyncErrorInfoBarTrigger::kPasswordFormParsed:
      return trusted_vault ::TrustedVaultUserActionTriggerForUMA::
          kPasswordManagerErrorMessage;
  }
  NOTREACHED();
}

}  // namespace

// static
bool SyncErrorInfoBarDelegate::Create(
    infobars::InfoBarManager* infobar_manager,
    ProfileIOS* profile,
    id<SyncPresenterCommands> sync_presenter_handler,
    SyncErrorInfoBarTrigger trigger) {
  if (SyncErrorNotificationsPaused(profile)) {
    return false;
  }

  CHECK(infobar_manager);
  std::unique_ptr<SyncErrorInfoBarDelegate> delegate(
      new SyncErrorInfoBarDelegate(profile, sync_presenter_handler, trigger));
  std::unique_ptr<InfoBarIOS> infobar = std::make_unique<InfoBarIOS>(
      InfobarType::kInfobarTypeSyncError, std::move(delegate));
  return !!infobar_manager->AddInfoBar(std::move(infobar));
}

SyncErrorInfoBarDelegate::SyncErrorInfoBarDelegate(
    ProfileIOS* profile,
    id<SyncPresenterCommands> sync_presenter_handler,
    SyncErrorInfoBarTrigger trigger)
    : profile_(profile),
      sync_presenter_handler_(sync_presenter_handler),
      trigger_(trigger) {
  CHECK(!profile->IsOffTheRecord(), base::NotFatalUntil::M151);
  syncer::SyncService* sync_service =
      SyncServiceFactory::GetForProfile(profile_);
  CHECK(sync_service, base::NotFatalUntil::M151);
  // Set all of the UI based on the sync state at the same time to ensure
  // they all correspond to the same sync error.
  error_state_ = sync_service->GetUserActionableError();
  title_ = GetSyncErrorInfoBarTitleForProfile(profile_);
  message_ = base::SysNSStringToUTF16(GetSyncErrorMessageForProfile(profile_));
  button_text_ =
      base::SysNSStringToUTF16(GetSyncErrorButtonTitleForProfile(profile_));

  // Register for sync status changes.
  sync_observation_.Observe(sync_service);
  signin::IdentityManager* identity_manager =
      IdentityManagerFactory::GetForProfile(profile);
  identity_manager_observation_.Observe(identity_manager);
}

SyncErrorInfoBarDelegate::~SyncErrorInfoBarDelegate() = default;

infobars::InfoBarDelegate::InfoBarIdentifier
SyncErrorInfoBarDelegate::GetIdentifier() const {
  return SYNC_ERROR_INFOBAR_DELEGATE_IOS;
}

std::u16string SyncErrorInfoBarDelegate::GetMessageText() const {
  return message_;
}

std::u16string SyncErrorInfoBarDelegate::GetTitleText() const {
  return title_;
}

int SyncErrorInfoBarDelegate::GetButtons() const {
  return button_text_.empty() ? BUTTON_NONE : BUTTON_OK;
}

std::u16string SyncErrorInfoBarDelegate::GetButtonLabel(
    InfoBarButton button) const {
  CHECK(button == BUTTON_OK, base::NotFatalUntil::M151);
  return button_text_;
}

bool SyncErrorInfoBarDelegate::Accept() {
  if (!infobar_is_relevant_) {
    // The user tapped on Accept while the view was being dismissed or replaced.
    return false;
  }
  switch (error_state_) {
    case syncer::SyncService::UserActionableError::kSignInNeedsUpdate:
      [sync_presenter_handler_ showPrimaryAccountReauth];
      break;

    case syncer::SyncService::UserActionableError::kDeviceManagementError: {
      AuthenticationService* authService =
          AuthenticationServiceFactory::GetForProfile(profile_);
      id<SystemIdentity> identity = authService->GetPrimaryIdentity();
      if (identity) {
        authService->ShowMDMErrorDialogForIdentity(identity);
      }
      break;
    }

    case syncer::SyncService::UserActionableError::kNone: {
      CHECK(ShouldShowSyncSettings(error_state_), base::NotFatalUntil::M151);
      AuthenticationService* authService =
          AuthenticationServiceFactory::GetForProfile(profile_);
      if (!authService->HasPrimaryIdentity() || !authService->SigninEnabled()) {
        // Due to race condition, the user may be signed-out, or sign-in may be
        // disabled between the time the user tap on the button and the
        // execution of this method. In this case, do nothing, the button will
        // disappear by itself.
        break;
      }

      [sync_presenter_handler_ showAccountSettings];
      break;
    }

    case syncer::SyncService::UserActionableError::kNeedsClientUpgrade:
      // TODO(crbug.com/370026230): Update this case once
      // GetAccountErrorUIInfo() returns a non-nil value for it.
      NOTREACHED();

    case syncer::SyncService::UserActionableError::kNeedsPassphrase:
      [sync_presenter_handler_ showSyncPassphraseSettings];
      break;

    case syncer::SyncService::UserActionableError::
        kNeedsTrustedVaultKeyForPasswords:
    case syncer::SyncService::UserActionableError::
        kNeedsTrustedVaultKeyForEverything:
      [sync_presenter_handler_
          showTrustedVaultReauthForFetchKeysWithTrigger:
              TrustedVaultTriggerFromInfoBarTrigger(trigger_)
                                             completion:nil];
      break;

    case syncer::SyncService::UserActionableError::
        kTrustedVaultRecoverabilityDegradedForPasswords:
    case syncer::SyncService::UserActionableError::
        kTrustedVaultRecoverabilityDegradedForEverything:
      [sync_presenter_handler_
          showTrustedVaultReauthForDegradedRecoverabilityWithTrigger:
              TrustedVaultTriggerFromInfoBarTrigger(trigger_)
                                                          completion:nil];
      break;
    case syncer::SyncService::UserActionableError::kBookmarksLimitExceeded:
      [sync_presenter_handler_ showBookmarksLimitExceededHelp];
      break;
  }

  return true;
}

void SyncErrorInfoBarDelegate::InfoBarDismissed() {
  profile_->GetPrefs()->SetTime(
      prefs::kIosSyncInfobarErrorLastDismissedTimestamp, base::Time::Now());
  LogSyncErrorInfobarDismissed(error_state_);
  ConfirmInfoBarDelegate::InfoBarDismissed();
}

void SyncErrorInfoBarDelegate::OnStateChanged(syncer::SyncService* sync) {
  // If the inforbar is in the process of being removed, nothing must be done.
  infobars::InfoBar* infobar = this->infobar();
  if (!infobar) {
    return;
  }
  syncer::SyncService* sync_service =
      SyncServiceFactory::GetForProfile(profile_);
  syncer::SyncService::UserActionableError new_error_state =
      sync_service->GetUserActionableError();
  if (error_state_ == new_error_state) {
    return;
  }
  // The current infobar was about the previour error state. We should not start
  // any action based on it. The infobar either is already being removed, or it
  // will be removed or replaced. No need to do anything more to close the view.
  infobar_is_relevant_ = false;
  error_state_ = new_error_state;
  if (new_error_state == syncer::SyncService::UserActionableError::kNone) {
    infobar->RemoveSelf();
  } else {
    infobars::InfoBarManager* infobar_manager = infobar->owner();
    if (infobar_manager) {
      std::unique_ptr<ConfirmInfoBarDelegate> new_infobar_delegate(
          new SyncErrorInfoBarDelegate(profile_, sync_presenter_handler_,
                                       trigger_));
      infobar_manager->ReplaceInfoBar(
          infobar, CreateConfirmInfoBar(std::move(new_infobar_delegate)));
    }
  }
}

void SyncErrorInfoBarDelegate::OnSyncShutdown(syncer::SyncService* sync) {
  NOTREACHED();
}

void SyncErrorInfoBarDelegate::OnPrimaryAccountChanged(
    const signin::PrimaryAccountChangeEvent& event_details) {
  switch (event_details.GetEventTypeFor(signin::ConsentLevel::kSignin)) {
    case signin::PrimaryAccountChangeEvent::Type::kNone:
    case signin::PrimaryAccountChangeEvent::Type::kSet:
      return;
    case signin::PrimaryAccountChangeEvent::Type::kCleared:
      break;
  }
  infobars::InfoBar* infobar = this->infobar();
  if (!infobar) {
    return;
  }
  infobar->RemoveSelf();
}

void SyncErrorInfoBarDelegate::OnIdentityManagerShutdown(
    signin::IdentityManager* identity_manager) {
  identity_manager_observation_.Reset();
}

void SyncErrorInfoBarDelegate::InfoBarDismissedByTimeout() const {
  profile_->GetPrefs()->SetTime(
      prefs::kIosSyncInfobarErrorLastDismissedTimestamp, base::Time::Now());
}

bool SyncErrorInfoBarDelegate::DisplayPasswordErrorIcon() const {
  switch (error_state_) {
    case syncer::SyncService::UserActionableError::
        kNeedsTrustedVaultKeyForPasswords:
    case syncer::SyncService::UserActionableError::
        kTrustedVaultRecoverabilityDegradedForPasswords:
      return true;
    case syncer::SyncService::UserActionableError::kNone:
    case syncer::SyncService::UserActionableError::kSignInNeedsUpdate:
    case syncer::SyncService::UserActionableError::kNeedsClientUpgrade:
    case syncer::SyncService::UserActionableError::kNeedsPassphrase:
    case syncer::SyncService::UserActionableError::
        kNeedsTrustedVaultKeyForEverything:
    case syncer::SyncService::UserActionableError::
        kTrustedVaultRecoverabilityDegradedForEverything:
    case syncer::SyncService::UserActionableError::kBookmarksLimitExceeded:
    case syncer::SyncService::UserActionableError::kDeviceManagementError:
      return false;
  }
  NOTREACHED();
}
