// Copyright 2024 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/safety_check_notifications/utils/utils.h"

#import "base/metrics/histogram_functions.h"
#import "base/strings/sys_string_conversions.h"
#import "base/time/time.h"
#import "google_apis/gaia/gaia_id.h"
#import "ios/chrome/browser/push_notification/model/push_notification_client.h"
#import "ios/chrome/browser/push_notification/model/push_notification_client_id.h"
#import "ios/chrome/browser/push_notification/model/push_notification_settings_util.h"
#import "ios/chrome/browser/safety_check_notifications/utils/constants.h"
#import "ios/chrome/browser/shared/public/features/system_flags.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util_mac.h"

namespace {

// Returns the corresponding number of insecure passwords for the given `state`
// using `insecure_password_counts`. If no insecure passwords are associated
// with the given `state`, returns `nil`.
NSNumber* InsecurePasswordCount(
    PasswordSafetyCheckState state,
    password_manager::InsecurePasswordCounts insecure_password_counts) {
  if (state == PasswordSafetyCheckState::kUnmutedCompromisedPasswords) {
    return [NSNumber numberWithInt:insecure_password_counts.compromised_count];
  }

  if (state == PasswordSafetyCheckState::kReusedPasswords) {
    return [NSNumber numberWithInt:insecure_password_counts.reused_count];
  }

  if (state == PasswordSafetyCheckState::kWeakPasswords) {
    return [NSNumber numberWithInt:insecure_password_counts.weak_count];
  }

  return nil;
}

// Base helper for creating each Safety Check notification's userInfo
// dictionary.
template <typename SafetyCheckStateType>
NSMutableDictionary* BaseUserInfoForNotification(NSString* type_id_key,
                                                 SafetyCheckStateType state) {
  return [@{
    type_id_key : @YES,
    kSafetyCheckNotificationCheckResultKey :
        base::SysUTF8ToNSString(NameForSafetyCheckState(state))
  } mutableCopy];
}

// Returns a user info dictionary for a Password safety check notification.
// Contains the check result state, insecure password count, and a notification
// ID for logging.
NSDictionary* UserInfoForPasswordNotification(
    PasswordSafetyCheckState state,
    password_manager::InsecurePasswordCounts insecure_password_counts) {
  NSMutableDictionary* user_info =
      BaseUserInfoForNotification(kSafetyCheckPasswordNotificationID, state);

  NSNumber* insecure_password_count =
      InsecurePasswordCount(state, insecure_password_counts);

  if (insecure_password_count) {
    user_info[kSafetyCheckNotificationInsecurePasswordCountKey] =
        insecure_password_count;
  }

  return user_info;
}

// Returns a user info dictionary for an Update Chrome safety check
// notification. Contains the check result state and a notification ID for
// logging.
NSDictionary* UserInfoForUpdateChromeNotification(
    UpdateChromeSafetyCheckState state) {
  return BaseUserInfoForNotification(kSafetyCheckUpdateChromeNotificationID,
                                     state);
}

// Returns a user info dictionary for a Safe Browsing safety check notification.
// Contains the check result state and a notification ID for logging.
NSDictionary* UserInfoForSafeBrowsingNotification(
    SafeBrowsingSafetyCheckState state) {
  return BaseUserInfoForNotification(kSafetyCheckSafeBrowsingNotificationID,
                                     state);
}

// Returns a notification content object with the provided `title`, `body`, and
// `user_info`. Includes a default notification sound.
UNNotificationContent* NotificationContent(NSString* title,
                                           NSString* body,
                                           NSDictionary* user_info) {
  UNMutableNotificationContent* content =
      [[UNMutableNotificationContent alloc] init];

  content.title = title;
  content.body = body;
  content.userInfo = user_info;
  content.sound = UNNotificationSound.defaultSound;

  return content;
}

// Returns the time duration of user inactivity required before Safety Check
// notifications are triggered. This duration can be modified through
// Experimental settings or Finch. If no override is set, a default value is
// used.
base::TimeDelta InactiveThresholdForNotifications() {
  // Check if an override is set via experimental flags.
  std::optional<int> forced_threshold_seconds = experimental_flags::
      GetForcedInactivityThresholdForSafetyCheckNotifications();

  if (forced_threshold_seconds.has_value()) {
    return base::Seconds(forced_threshold_seconds.value());
  }

  return kSafetyCheckNotificationDefaultDelay;
}

// Helper to create the final scheduled request if `content` is valid.
std::optional<ScheduledNotificationRequest> CreateScheduledNotificationRequest(
    UNNotificationContent* content,
    NSString* identifier) {
  if (!content) {
    return std::nullopt;
  }

  return ScheduledNotificationRequest{
      .identifier = identifier,
      .content = content,
      .time_interval = InactiveThresholdForNotifications()};
}

}  // namespace

void LogSafetyCheckNotificationOptInSource(
    SafetyCheckNotificationsOptInSource opt_in_source,
    SafetyCheckNotificationsOptInSource opt_out_source) {
  bool is_notifications_enabled = push_notification_settings::
      GetMobileNotificationPermissionStatusForClient(
          PushNotificationClientId::kSafetyCheck, GaiaId());

  base::UmaHistogramEnumeration(
      "IOS.Notifications.SafetyCheck.NotificationsOptInSource",
      is_notifications_enabled ? opt_out_source : opt_in_source);
}

std::optional<ScheduledNotificationRequest> GetPasswordNotificationRequest(
    PasswordSafetyCheckState state,
    password_manager::InsecurePasswordCounts insecure_password_counts) {
  UNNotificationContent* content =
      NotificationForPasswordCheckState(state, insecure_password_counts);

  return CreateScheduledNotificationRequest(content,
                                            kSafetyCheckPasswordNotificationID);
}

UNNotificationContent* NotificationForPasswordCheckState(
    PasswordSafetyCheckState state,
    password_manager::InsecurePasswordCounts insecure_password_counts) {
  if (state == PasswordSafetyCheckState::kUnmutedCompromisedPasswords &&
      insecure_password_counts.compromised_count > 0) {
    return NotificationContent(
        l10n_util::GetNSString(
            IDS_IOS_SAFETY_CHECK_NOTIFICATIONS_CHANGE_PASSWORDS),
        l10n_util::GetPluralNSStringF(
            IDS_IOS_SAFETY_CHECK_NOTIFICATIONS_COMPROMISED_PASSWORD,
            insecure_password_counts.compromised_count),
        UserInfoForPasswordNotification(state, insecure_password_counts));
  }

  if (state == PasswordSafetyCheckState::kReusedPasswords &&
      insecure_password_counts.reused_count > 0) {
    return NotificationContent(
        l10n_util::GetNSString(
            IDS_IOS_SAFETY_CHECK_NOTIFICATIONS_CHANGE_PASSWORDS),
        l10n_util::GetPluralNSStringF(
            IDS_IOS_SAFETY_CHECK_NOTIFICATIONS_REUSED_PASSWORD,
            insecure_password_counts.reused_count),
        UserInfoForPasswordNotification(state, insecure_password_counts));
  }

  if (state == PasswordSafetyCheckState::kWeakPasswords &&
      insecure_password_counts.weak_count > 0) {
    return NotificationContent(
        l10n_util::GetNSString(
            IDS_IOS_SAFETY_CHECK_NOTIFICATIONS_CHANGE_PASSWORDS),
        l10n_util::GetPluralNSStringF(
            IDS_IOS_SAFETY_CHECK_NOTIFICATIONS_WEAK_PASSWORD,
            insecure_password_counts.weak_count),
        UserInfoForPasswordNotification(state, insecure_password_counts));
  }

  return nil;
}

std::optional<ScheduledNotificationRequest> GetUpdateChromeNotificationRequest(
    UpdateChromeSafetyCheckState state) {
  UNNotificationContent* content = NotificationForUpdateChromeCheckState(state);

  return CreateScheduledNotificationRequest(
      content, kSafetyCheckUpdateChromeNotificationID);
}

UNNotificationContent* NotificationForUpdateChromeCheckState(
    UpdateChromeSafetyCheckState state) {
  if (state == UpdateChromeSafetyCheckState::kOutOfDate) {
    return NotificationContent(
        l10n_util::GetNSString(
            IDS_IOS_SAFETY_CHECK_NOTIFICATIONS_UPDATE_BROWSER),
        l10n_util::GetNSString(IDS_IOS_SAFETY_CHECK_DESCRIPTION_UPDATE_CHROME),
        UserInfoForUpdateChromeNotification(state));
  }

  return nil;
}

std::optional<ScheduledNotificationRequest> GetSafeBrowsingNotificationRequest(
    SafeBrowsingSafetyCheckState state) {
  UNNotificationContent* content = NotificationForSafeBrowsingCheckState(state);

  return CreateScheduledNotificationRequest(
      content, kSafetyCheckSafeBrowsingNotificationID);
}

UNNotificationContent* NotificationForSafeBrowsingCheckState(
    SafeBrowsingSafetyCheckState state) {
  if (state == SafeBrowsingSafetyCheckState::kUnsafe) {
    return NotificationContent(
        l10n_util::GetNSString(
            IDS_IOS_SAFETY_CHECK_NOTIFICATIONS_ADD_BROWSING_PROTECTION),
        l10n_util::GetNSString(
            IDS_IOS_SAFETY_CHECK_NOTIFICATIONS_BROWSING_PROTECTION_OFF),
        UserInfoForSafeBrowsingNotification(state));
  }

  return nil;
}

std::optional<SafetyCheckNotificationType> ParseSafetyCheckNotificationType(
    UNNotificationRequest* request) {
  if ([request.identifier
          isEqualToString:kSafetyCheckUpdateChromeNotificationID]) {
    return SafetyCheckNotificationType::kUpdateChrome;
  }

  if ([request.identifier isEqualToString:kSafetyCheckPasswordNotificationID]) {
    return SafetyCheckNotificationType::kPasswords;
  }

  if ([request.identifier
          isEqualToString:kSafetyCheckSafeBrowsingNotificationID]) {
    return SafetyCheckNotificationType::kSafeBrowsing;
  }

  return std::nullopt;
}

std::set<SafetyCheckNotificationType> GetResolvedSafetyCheckTypes(
    UpdateChromeSafetyCheckState update_chrome_state,
    SafeBrowsingSafetyCheckState safe_browsing_state,
    PasswordSafetyCheckState password_state) {
  std::set<SafetyCheckNotificationType> notification_types_to_remove;

  if (password_state == PasswordSafetyCheckState::kSignedOut ||
      password_state == PasswordSafetyCheckState::kSafe) {
    notification_types_to_remove.insert(
        SafetyCheckNotificationType::kPasswords);
  }

  if (safe_browsing_state == SafeBrowsingSafetyCheckState::kSafe) {
    notification_types_to_remove.insert(
        SafetyCheckNotificationType::kSafeBrowsing);
  }

  if (update_chrome_state == UpdateChromeSafetyCheckState::kUpToDate) {
    notification_types_to_remove.insert(
        SafetyCheckNotificationType::kUpdateChrome);
  }

  return notification_types_to_remove;
}
