// Copyright 2014 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/authentication/ui_bundled/authentication_ui_util.h"

#import "base/check.h"
#import "base/format_macros.h"
#import "base/i18n/number_formatting.h"
#import "base/metrics/user_metrics.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "components/browser_sync/sync_to_signin_migration.h"
#import "components/signin/public/base/consent_level.h"
#import "components/signin/public/base/gaia_id_hash.h"
#import "components/signin/public/base/signin_metrics.h"
#import "components/signin/public/identity_manager/identity_manager.h"
#import "components/strings/grit/components_strings.h"
#import "components/sync/base/account_pref_utils.h"
#import "components/sync/service/sync_service.h"
#import "components/sync_bookmarks/constants.h"
#import "google_apis/gaia/gaia_id.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_feature.h"
#import "ios/chrome/browser/policy/model/browser_policy_connector_ios.h"
#import "ios/chrome/browser/shared/coordinator/alert/action_sheet_coordinator.h"
#import "ios/chrome/browser/shared/coordinator/alert/alert_coordinator.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/profile/features.h"
#import "ios/chrome/browser/shared/model/profile/profile_attributes_storage_ios.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios_util.h"
#import "ios/chrome/browser/shared/model/profile/profile_manager_ios.h"
#import "ios/chrome/browser/signin/model/account_profile_mapper.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"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util.h"

namespace {

// Returns the title associated to the given user sign-in state.
// `account_profile_switch` is true if the flow was triggered for an account or
// profile switching.
// `signed_in_user_state` sign-in&sync state for the current primary account.
NSString* GetActionSheetCoordinatorTitle(
    signin::IdentityManager* identity_manager,
    SignedInUserState signed_in_user_state,
    bool account_profile_switch) {
  switch (signed_in_user_state) {
    case SignedInUserState::kNotSyncingAndReplaceSyncWithSignin:
      // This dialog is triggered only if there is unsync data.
      return l10n_util::GetNSString(
          IDS_IOS_SIGNOUT_DIALOG_SIGN_OUT_AND_DELETE_TITLE);
    case SignedInUserState::kManagedAccountAndMigratedFromSyncing: {
      std::u16string hostedDomain =
          HostedDomainForPrimaryAccount(identity_manager);
      return l10n_util::GetNSStringF(
          IDS_IOS_SIGNOUT_DIALOG_TITLE_WITH_SYNCING_MANAGED_ACCOUNT,
          hostedDomain);
    }
    case SignedInUserState::kManagedAccountClearsDataOnSignout: {
      return account_profile_switch
                 ? l10n_util::GetNSString(
                       IDS_IOS_SWITCH_CLEARS_DATA_DIALOG_TITLE_WITH_MANAGED_ACCOUNT)
                 : l10n_util::GetNSString(
                       IDS_IOS_SIGNOUT_CLEARS_DATA_DIALOG_TITLE_WITH_MANAGED_ACCOUNT);
    }
  }
  NOTREACHED();
}

// Returns the message associated to the given user sign-in state. Can return
// nil.
// `account_profile_switch` is true if the flow was triggered for an account or
// profile switching.
// `signed_in_user_state` sign-in&sync state for the current primary account.
NSString* GetActionSheetCoordinatorMessage(
    AuthenticationService* authentication_service,
    syncer::SyncService* sync_service,
    SignedInUserState signed_in_user_state,
    bool account_profile_switch) {
  switch (signed_in_user_state) {
    case SignedInUserState::kNotSyncingAndReplaceSyncWithSignin: {
      // This dialog is triggered only if there is unsync data.
      if (account_profile_switch) {
        NSString* userEmail =
            authentication_service->GetPrimaryIdentity().userEmail;
        return l10n_util::GetNSStringF(
            IDS_IOS_DATA_NOT_UPLOADED_SWITCH_DIALOG_BODY,
            base::SysNSStringToUTF16(userEmail));
      }

      if (sync_service && sync_service->GetUserActionableError() ==
                              syncer::SyncService::UserActionableError::
                                  kBookmarksLimitExceeded) {
        return l10n_util::GetNSStringF(
            IDS_IOS_SIGNOUT_DIALOG_MESSAGE_WITH_BOOKMARKS_LIMIT_EXCEEDED,
            base::FormatNumber(sync_bookmarks::kSyncBookmarksLimit));
      }

      return l10n_util::GetNSString(
          IDS_IOS_SIGNOUT_DIALOG_MESSAGE_WITH_NOT_SAVED_DATA);
    }
    case SignedInUserState::kManagedAccountClearsDataOnSignout:
      // Signing out may also cause tabs to be closed, see
      // `MainControllerAuthenticationServiceDelegate::
      //    ClearBrowsingDataForSignedinPeriod`.
      return l10n_util::GetNSString(
          IDS_IOS_SIGNOUT_CLOSES_TABS_AND_CLEARS_DATA_DIALOG_MESSAGE_WITH_MANAGED_ACCOUNT);

    case SignedInUserState::kManagedAccountAndMigratedFromSyncing: {
      return nil;
    }
  }
  NOTREACHED();
}

}  // namespace

std::u16string HostedDomainForPrimaryAccount(
    signin::IdentityManager* identity_manager) {
  return base::UTF8ToUTF16(
      identity_manager
          ->FindExtendedAccountInfo(identity_manager->GetPrimaryAccountInfo(
              signin::ConsentLevel::kSignin))
          .GetHostedDomain()
          .value_or(std::string()));
}

AlertCoordinator* ErrorCoordinator(NSError* error,
                                   ProceduralBlock dismissAction,
                                   UIViewController* viewController,
                                   Browser* browser) {
  DCHECK(error);

  AlertCoordinator* alertCoordinator =
      ErrorCoordinatorNoItem(error, viewController, browser);

  NSString* okButtonLabel = l10n_util::GetNSString(IDS_OK);
  [alertCoordinator addItemWithTitle:okButtonLabel
                              action:dismissAction
                               style:UIAlertActionStyleDefault];

  alertCoordinator.noInteractionAction = dismissAction;

  return alertCoordinator;
}

NSString* DialogMessageFromError(NSError* error) {
  NSMutableString* errorMessage = [[NSMutableString alloc] init];
  if (error.userInfo[NSLocalizedDescriptionKey]) {
    [errorMessage appendString:error.localizedDescription];
  } else {
    [errorMessage appendString:@"--"];
  }
  [errorMessage appendString:@" ("];
  NSError* errorCursor = error;
  for (int errorDepth = 0; errorDepth < 3 && errorCursor; ++errorDepth) {
    if (errorDepth > 0) {
      [errorMessage appendString:@", "];
    }
    [errorMessage appendFormat:@"%@: %ld", errorCursor.domain,
                               static_cast<long>(errorCursor.code)];
    errorCursor = errorCursor.userInfo[NSUnderlyingErrorKey];
  }
  [errorMessage appendString:@")"];
  return [errorMessage copy];
}

AlertCoordinator* ErrorCoordinatorNoItem(NSError* error,
                                         UIViewController* viewController,
                                         Browser* browser) {
  DCHECK(error);

  NSString* title = l10n_util::GetNSString(
      IDS_IOS_SYNC_AUTHENTICATION_ERROR_ALERT_VIEW_TITLE);
  NSString* errorMessage;
  if ([NSURLErrorDomain isEqualToString:error.domain] &&
      error.code == kCFURLErrorCannotConnectToHost) {
    errorMessage =
        l10n_util::GetNSString(IDS_IOS_SYNC_ERROR_INTERNET_DISCONNECTED);
  } else {
    errorMessage = DialogMessageFromError(error);
  }
  AlertCoordinator* alertCoordinator =
      [[AlertCoordinator alloc] initWithBaseViewController:viewController
                                                   browser:browser
                                                     title:title
                                                   message:errorMessage];
  return alertCoordinator;
}

NSString* ViewControllerPresentationStatusDescription(
    UIViewController* view_controller) {
  if (!view_controller) {
    return @"No view controller";
  } else if (view_controller.isBeingPresented) {
    return @"Being presented";
  } else if (view_controller.isBeingDismissed) {
    return @"Being dismissed";
  } else if (view_controller.presentingViewController) {
    return [NSString stringWithFormat:@"Presented by: %@",
                                      view_controller.presentingViewController];
  }
  return @"Not presented";
}

AlertCoordinator* ManagedConfirmationDialogContentForHostedDomain(
    NSString* hosted_domain,
    Browser* browser,
    UIViewController* view_controller,
    ProceduralBlock accept_block,
    ProceduralBlock cancel_block) {
  CHECK(!AreSeparateProfilesForManagedAccountsEnabled());
  NSString* title = l10n_util::GetNSString(IDS_IOS_MANAGED_SIGNIN_TITLE);
  NSString* subtitle =
      l10n_util::GetNSStringF(IDS_IOS_MANAGED_SIGNIN_WITH_USER_POLICY_SUBTITLE,
                              base::SysNSStringToUTF16(hosted_domain));
  // If we ever use this method in a test where migration was forced, the button
  // should be IDS_IOS_ENTERPRISE_PROFILE_CREATION_GOTIT.
  NSString* accept_label =
      l10n_util::GetNSString(IDS_IOS_ENTERPRISE_PROFILE_CREATION_CONTINUE);
  NSString* cancel_label = l10n_util::GetNSString(IDS_CANCEL);

  AlertCoordinator* managed_confirmation_alert_coordinator =
      [[AlertCoordinator alloc] initWithBaseViewController:view_controller
                                                   browser:browser
                                                     title:title
                                                   message:subtitle];

  [managed_confirmation_alert_coordinator
      addItemWithTitle:cancel_label
                action:cancel_block
                 style:UIAlertActionStyleCancel];
  [managed_confirmation_alert_coordinator
      addItemWithTitle:accept_label
                action:accept_block
                 style:UIAlertActionStyleDefault];
  managed_confirmation_alert_coordinator.noInteractionAction = cancel_block;
  [managed_confirmation_alert_coordinator start];
  return managed_confirmation_alert_coordinator;
}

SignedInUserState GetSignedInUserState(
    AuthenticationService* authentication_service,
    signin::IdentityManager* identity_manager,
    PrefService* profile_pref_service) {
  const bool is_managed_account_migrated_from_syncing =
      browser_sync::WasPrimaryAccountMigratedFromSyncingToSignedIn(
          identity_manager, profile_pref_service) &&
      authentication_service->HasPrimaryIdentityManaged();

  if (is_managed_account_migrated_from_syncing) {
    return SignedInUserState::kManagedAccountAndMigratedFromSyncing;
  }
  if (authentication_service->ShouldClearDataForSignedInPeriodOnSignOut()) {
    return SignedInUserState::kManagedAccountClearsDataOnSignout;
  }
  return SignedInUserState::kNotSyncingAndReplaceSyncWithSignin;
}

bool ForceLeavingPrimaryAccountConfirmationDialog(
    SignedInUserState signed_in_user_state,
    ProfileIOS* profile,
    const GaiaId& gaia_id_to_sign_in) {
  switch (signed_in_user_state) {
    case SignedInUserState::kNotSyncingAndReplaceSyncWithSignin:
      return false;
    case SignedInUserState::kManagedAccountClearsDataOnSignout:
    case SignedInUserState::kManagedAccountAndMigratedFromSyncing:
      if (!AreSeparateProfilesForManagedAccountsEnabled()) {
        return true;
      }

      // Show the dialog only if a managed account is signing out from the
      // personal profile. (This can only happen for managed accounts that were
      // already signed in before there was multi-profile support.)
      // If the new account is different from the one in the personal profile,
      // we are not actually signing it out.
      return IsPersonalProfile(profile) &&
             (gaia_id_to_sign_in.empty() ||
              IdentityManagerFactory::GetForProfile(profile)
                      ->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin)
                      .gaia == gaia_id_to_sign_in);
  }
  NOTREACHED();
}

ActionSheetCoordinator* GetLeavingPrimaryAccountConfirmationDialog(
    UIViewController* base_view_controller,
    Browser* browser,
    UIView* anchor_view,
    CGRect anchor_rect,
    SignedInUserState signed_in_user_state,
    bool account_profile_switch,
    LeavingPrimaryAccountConfirmationDialogCompletion completion) {
  ProfileIOS* profile = browser->GetProfile();
  signin::IdentityManager* identity_manager =
      IdentityManagerFactory::GetForProfile(profile);
  NSString* title = GetActionSheetCoordinatorTitle(
      identity_manager, signed_in_user_state, account_profile_switch);
  AuthenticationService* authentication_service =
      AuthenticationServiceFactory::GetForProfile(profile);
  syncer::SyncService* sync_service =
      SyncServiceFactory::GetForProfile(profile);
  NSString* message = GetActionSheetCoordinatorMessage(
      authentication_service, sync_service, signed_in_user_state,
      account_profile_switch);
  ActionSheetCoordinator* actionSheetCoordinator =
      [[ActionSheetCoordinator alloc]
          initWithBaseViewController:base_view_controller
                             browser:browser
                               title:title
                             message:message
                                rect:anchor_rect
                                view:anchor_view];
  switch (signed_in_user_state) {
    case SignedInUserState::kNotSyncingAndReplaceSyncWithSignin: {
      // This dialog is triggered only if there is unsynced data.
      actionSheetCoordinator.alertStyle = UIAlertControllerStyleAlert;
      NSString* const signOutButtonTitle =
          account_profile_switch
              ? l10n_util::GetNSString(
                    IDS_IOS_DATA_NOT_UPLOADED_SWITCH_DIALOG_BUTTON)
              : l10n_util::GetNSString(
                    IDS_IOS_SIGNOUT_DIALOG_SIGN_OUT_AND_DELETE_BUTTON);
      [actionSheetCoordinator
          addItemWithTitle:signOutButtonTitle
                    action:^{
                      base::RecordAction(base::UserMetricsAction(
                          "Signin_Signout_Confirm_Regular_UNO"));
                      signin_metrics::
                          RecordSignoutConfirmationFromDataLossAlert(
                              signin_metrics::SignoutDataLossAlertReason::
                                  kSignoutWithUnsyncedData,
                              true);
                      completion(YES);
                    }
                     style:UIAlertActionStyleDestructive];
      [actionSheetCoordinator
          addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
                    action:^{
                      base::RecordAction(base::UserMetricsAction(
                          "Signin_Signout_Cancel_Regular_UNO"));
                      signin_metrics::
                          RecordSignoutConfirmationFromDataLossAlert(
                              signin_metrics::SignoutDataLossAlertReason::
                                  kSignoutWithUnsyncedData,
                              false);
                      completion(NO);
                    }
                     style:UIAlertActionStyleCancel];
      break;
    }
    case SignedInUserState::kManagedAccountClearsDataOnSignout: {
      actionSheetCoordinator.alertStyle = UIAlertControllerStyleAlert;
      NSString* const signOutButtonTitle =
          account_profile_switch
              ? l10n_util::GetNSString(
                    IDS_IOS_DATA_NOT_UPLOADED_SWITCH_DIALOG_BUTTON)
              : l10n_util::GetNSString(
                    IDS_IOS_SIGNOUT_AND_DELETE_DIALOG_SIGN_OUT_BUTTON);
      [actionSheetCoordinator
          addItemWithTitle:signOutButtonTitle
                    action:^{
                      base::RecordAction(base::UserMetricsAction(
                          "Signin_Signout_Confirm_Managed_ClearDataOnSignout"));
                      signin_metrics::
                          RecordSignoutConfirmationFromDataLossAlert(
                              signin_metrics::SignoutDataLossAlertReason::
                                  kSignoutWithClearDataForManagedUser,
                              true);
                      completion(YES);
                    }
                     style:UIAlertActionStyleDestructive];
      [actionSheetCoordinator
          addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
                    action:^{
                      base::RecordAction(base::UserMetricsAction(
                          "Signin_Signout_Cancel_Managed_ClearDataOnSignout"));
                      signin_metrics::
                          RecordSignoutConfirmationFromDataLossAlert(
                              signin_metrics::SignoutDataLossAlertReason::
                                  kSignoutWithClearDataForManagedUser,
                              false);
                      completion(NO);
                    }
                     style:UIAlertActionStyleCancel];
      break;
    }
    case SignedInUserState::kManagedAccountAndMigratedFromSyncing: {
      actionSheetCoordinator.alertStyle = UIAlertControllerStyleAlert;
      NSString* const clearFromDeviceTitle =
          l10n_util::GetNSString(IDS_IOS_SIGNOUT_DIALOG_CLEAR_DATA_BUTTON);
      [actionSheetCoordinator
          addItemWithTitle:clearFromDeviceTitle
                    action:^{
                      base::RecordAction(base::UserMetricsAction(
                          "Signin_Signout_Confirm_Managed_Syncing"));
                      completion(YES);
                    }
                     style:UIAlertActionStyleDestructive];
      [actionSheetCoordinator
          addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
                    action:^{
                      base::RecordAction(
                          base::UserMetricsAction("Signin_Signout_Cancel"));
                      completion(NO);
                    }
                     style:UIAlertActionStyleCancel];
      break;
    }
  }
  return actionSheetCoordinator;
}
