// Copyright 2022 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/ash/net/apn_migrator.h"

#include "ash/constants/ash_features.h"
#include "ash/public/cpp/network_config_service.h"
#include "base/values.h"
#include "chromeos/ash/components/network/managed_cellular_pref_handler.h"
#include "chromeos/ash/components/network/managed_network_configuration_handler.h"
#include "chromeos/ash/components/network/metrics/cellular_network_metrics_logger.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_metadata_store.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/services/network_config/public/cpp/cros_network_config_util.h"
#include "components/device_event_log/device_event_log.h"
#include "components/session_manager/core/session.h"
#include "components/session_manager/core/session_manager.h"
#include "components/user_manager/user_manager.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

namespace ash {

namespace {

using chromeos::network_config::mojom::ApnPropertiesPtr;
using chromeos::network_config::mojom::ApnState;
using chromeos::network_config::mojom::ApnType;
using chromeos::network_config::mojom::ManagedApnPropertiesPtr;

std::optional<ApnPropertiesPtr> GetPreRevampApnFromDict(
    const base::DictValue* cellular_dict,
    const char* key) {
  const base::DictValue* apn_dict =
      chromeos::network_config::GetDictionary(cellular_dict, key);
  if (!apn_dict) {
    return std::nullopt;
  }

  // Pre-revamp APNs with empty kAccessPointName will be ignored as they
  // indicate shill tried to send a NULL APN to modemmanager. If shill
  // uses a custom APN or modem DB APN, the kAccessPointName will be
  // non-empty.
  const std::string* access_point_name =
      apn_dict->FindString(::onc::cellular_apn::kAccessPointName);
  if (!access_point_name || access_point_name->empty()) {
    return std::nullopt;
  }

  return chromeos::network_config::GetApnProperties(
      *apn_dict,
      /*is_apn_revamp_enabled=*/false);
}

std::vector<ApnType> GetMigratedApnTypes(
    const ApnPropertiesPtr& pre_revamp_apn) {
  if (pre_revamp_apn->attach.has_value() &&
      !(*pre_revamp_apn->attach).empty()) {
    return {ApnType::kDefault, ApnType::kAttach};
  }
  return {ApnType::kDefault};
}

}  // namespace

ApnMigrator::ApnMigrator(
    ManagedCellularPrefHandler* managed_cellular_pref_handler,
    ManagedNetworkConfigurationHandler* network_configuration_handler,
    NetworkStateHandler* network_state_handler)
    : managed_cellular_pref_handler_(managed_cellular_pref_handler),
      network_configuration_handler_(network_configuration_handler),
      network_state_handler_(network_state_handler) {
  if (!NetworkHandler::IsInitialized()) {
    return;
  }
  // TODO(b/162365553): Only bind this lazily when CrosNetworkConfig is actually
  // used.
  ash::GetNetworkConfigService(
      remote_cros_network_config_.BindNewPipeAndPassReceiver());
  network_state_handler_observer_.Observe(network_state_handler_.get());
}

ApnMigrator::~ApnMigrator() = default;

bool ApnMigrator::has_iccids_changed(base::flat_set<std::string> new_iccids,
                                     base::flat_set<std::string> old_iccids) {
  if (new_iccids.size() != old_iccids.size()) {
    return true;
  }

  return !std::equal(new_iccids.begin(), new_iccids.end(), old_iccids.begin());
}

base::flat_set<std::string> ApnMigrator::extract_iccids(
    NetworkStateHandler::NetworkStateList& network_list) {
  base::flat_set<std::string> iccids;

  for (const NetworkState* network : network_list) {
    if (!network->iccid().empty()) {
      iccids.insert(network->iccid());
    }
  }

  return iccids;
}

void ApnMigrator::ResetOldIccidsForTesting() {
  old_iccids_.clear();
}

void ApnMigrator::NetworkListChanged() {
  NetworkStateHandler::NetworkStateList network_list;
  network_state_handler_->GetVisibleNetworkListByType(
      NetworkTypePattern::Cellular(), &network_list);
  base::flat_set<std::string> new_iccids = extract_iccids(network_list);

  if (has_iccids_changed(new_iccids, old_iccids_)) {
    old_iccids_ = new_iccids;

    std::string username_hash;
    auto* primary_session =
        session_manager::SessionManager::Get()->GetPrimarySession();
    if (primary_session) {
      const AccountId& account_id = primary_session->account_id();
      if (auto* user = user_manager::UserManager::Get()->FindUser(account_id)) {
        username_hash = user->username_hash();
      }
    }

    for (const NetworkState* network : network_list) {
      // Only attempt to migrate networks known by Shill.
      if (network->IsNonShillCellularNetwork()) {
        continue;
      }

      if (network->iccid().empty()) {
        // If the network somehow has no iccid, don't attempt to migrate.
        NET_LOG(DEBUG) << "Network has no iccid, not migrating: "
                       << network->guid();
        continue;
      }

      // The network has already been updated in Shill with the correct logic
      // depending on if the flag is enabled or disabled. Finish early so we
      // don't redundantly update Shill.
      if (shill_updated_iccids_.contains(network->iccid())) {
        continue;
      }

      bool has_network_been_migrated =
          managed_cellular_pref_handler_->ContainsApnMigratedIccid(
              network->iccid());
      if (!ash::features::IsApnRevampEnabled()) {
        // If the network has been marked as migrated, but the ApnRevamp flag is
        // disabled, the flag was disabled after being enabled. Clear
        // CustomApnList so that Shill knows to use legacy APN selection logic.
        if (has_network_been_migrated) {
          NET_LOG(EVENT) << "Network has been migrated but the revamp flag is "
                         << "disabled. Clearing CustomAPNList: "
                         << network->iccid();
          network_configuration_handler_->ClearShillProperties(
              network->path(), {shill::kCellularCustomApnListProperty},
              base::BindOnce(&ApnMigrator::OnClearPropertiesSuccess,
                             weak_factory_.GetWeakPtr(), network->iccid()),
              base::BindOnce(&ApnMigrator::OnClearPropertiesFailure,
                             weak_factory_.GetWeakPtr(), network->iccid(),
                             network->guid()));
        }
        continue;
      }
      if (username_hash.empty()) {
        // There's no primary session on the login screen, avoid spamming logs.
        if (primary_session) {
          NET_LOG(DEBUG)
              << "Delaying APN migration/application until user logs in: "
              << network->iccid();
        }
        continue;
      }

      if (!has_network_been_migrated) {
        NET_LOG(EVENT)
            << "Network has not been migrated, attempting to migrate: "
            << network->iccid();
        MigrateNetwork(*network, username_hash);
        continue;
      }

      // The network has already been migrated, either the last time the flag
      // was on, or this time. Send Shill the revamp APN list.
      if (const base::ListValue* custom_apn_list =
              GetNetworkMetadataStore()->GetCustomApnList(network->guid())) {
        if (!ash::features::IsAllowApnModificationPolicyEnabled() ||
            network_configuration_handler_->AllowApnModification()) {
          NET_LOG(EVENT)
              << "Network has already been migrated, setting with the "
              << "populated custom APN list: " << network->iccid();
          SetShillCustomApnListForNetwork(*network, custom_apn_list);
        } else if (!network_configuration_handler_->AllowApnModification()) {
          NET_LOG(EVENT)
              << "Not setting custom APN list as admin has restricted "
                 "use of custom APNs";
          base::ListValue empty_custom_apn_list;
          SetShillCustomApnListForNetwork(*network, &empty_custom_apn_list);
        }
        continue;
      }

      NET_LOG(EVENT) << "Network has already been migrated, setting with the "
                     << "empty custom APN list: " << network->iccid();
      base::ListValue empty_custom_apn_list;
      SetShillCustomApnListForNetwork(*network, &empty_custom_apn_list);
    }
  }
}

void ApnMigrator::OnClearPropertiesSuccess(const std::string iccid) {
  NET_LOG(EVENT) << "Successfully cleared CustomAPNList for: " << iccid;
  shill_updated_iccids_.emplace(iccid);
}

void ApnMigrator::OnClearPropertiesFailure(const std::string iccid,
                                           const std::string guid,
                                           const std::string& error_name) {
  NET_LOG(ERROR) << "Failed to clear CustomAPNList for: " << iccid;
}

void ApnMigrator::SetShillCustomApnListForNetwork(
    const NetworkState& network,
    const base::ListValue* apn_list) {
  network_configuration_handler_->SetProperties(
      network.path(),
      chromeos::network_config::CustomApnListToOnc(network.guid(), apn_list),
      base::BindOnce(&ApnMigrator::OnSetShillCustomApnListSuccess,
                     weak_factory_.GetWeakPtr(), network.iccid()),
      base::BindOnce(&ApnMigrator::OnSetShillCustomApnListFailure,
                     weak_factory_.GetWeakPtr(), network.iccid(),
                     network.guid()));
}

void ApnMigrator::OnSetShillCustomApnListSuccess(const std::string iccid) {
  // Shill has successfully updated the network with the revamp APN list.
  shill_updated_iccids_.emplace(iccid);
  NET_LOG(EVENT) << "ApnMigrator: Update the custom APN "
                 << "list in Shill for network with ICCID: " << iccid;

  // The network has just been migrated.
  if (!managed_cellular_pref_handler_->ContainsApnMigratedIccid(iccid)) {
    CompleteMigrationAttempt(iccid, /*success=*/true);
  }
}

void ApnMigrator::OnSetShillCustomApnListFailure(
    const std::string iccid,
    const std::string guid,
    const std::string& error_name) {
  NET_LOG(ERROR) << "ApnMigrator: Failed to update the custom APN "
                 << "list in Shill for network: " << guid << ": [" << error_name
                 << ']';

  CompleteMigrationAttempt(iccid, /*success=*/false);
}

void ApnMigrator::MigrateNetwork(const NetworkState& network,
                                 const std::string& username_hash) {
  DCHECK(ash::features::IsApnRevampEnabled());
  DCHECK(!username_hash.empty());

  // Return early if the network is already in the process of being migrated.
  if (iccids_in_migration_.contains(network.iccid())) {
    NET_LOG(DEBUG) << "Attempting to migrate network that already has a "
                   << "migration in progress, returning early: "
                   << network.iccid();
    return;
  }

  DCHECK(!managed_cellular_pref_handler_->ContainsApnMigratedIccid(
      network.iccid()));

  // Get the pre-revamp APN list.
  const base::ListValue* custom_apn_list =
      GetNetworkMetadataStore()->GetPreRevampCustomApnList(network.guid());

  // If the pre-revamp APN list is empty, set the revamp list as empty and
  // finish the migration.
  if (!custom_apn_list || custom_apn_list->empty()) {
    NET_LOG(EVENT) << "Pre-revamp APN list is empty, sending empty list to "
                   << "Shill: " << network.iccid();
    base::ListValue empty_apn_list;
    SetShillCustomApnListForNetwork(network, &empty_apn_list);
    return;
  }

  // If the pre-revamp APN list is non-empty, get the network's managed
  // properties, to be used for the migration heuristic. This call is
  // asynchronous; mark the ICCID as migrating so that the network won't
  // be attempted to be migrated again while these properties are being
  // fetched.
  iccids_in_migration_.emplace(network.iccid());

  NET_LOG(EVENT) << "Fetching managed properties for network: "
                 << network.iccid();
  network_configuration_handler_->GetManagedProperties(
      username_hash, network.path(),
      base::BindOnce(&ApnMigrator::OnGetManagedProperties,
                     weak_factory_.GetWeakPtr(), network.iccid(),
                     network.guid()));
}

void ApnMigrator::OnGetManagedProperties(
    std::string iccid,
    std::string guid,
    const std::string& service_path,
    std::optional<base::DictValue> properties,
    std::optional<std::string> error) {
  if (error.has_value()) {
    NET_LOG(ERROR) << "Error fetching managed properties for " << iccid
                   << ", error: " << error.value();
    CompleteMigrationAttempt(iccid, /*success=*/false);
    return;
  }

  if (!properties) {
    NET_LOG(ERROR) << "Error fetching managed properties for " << iccid;
    CompleteMigrationAttempt(iccid, /*success=*/false);
    return;
  }

  const NetworkState* network =
      network_state_handler_->GetNetworkStateFromGuid(guid);
  if (!network) {
    NET_LOG(ERROR) << "Network no longer exists: " << guid;
    CompleteMigrationAttempt(iccid, /*success=*/false);
    return;
  }

  // Get the pre-revamp APN list.
  const base::ListValue* custom_apn_list =
      GetNetworkMetadataStore()->GetPreRevampCustomApnList(guid);

  // At this point, the pre-revamp APN list should not be empty. However, there
  // could be the case where the custom APN list was cleared during the
  // GetManagedProperties() call. If so, set the revamp list as empty and finish
  // the migration.
  if (!custom_apn_list || custom_apn_list->empty()) {
    NET_LOG(EVENT) << "Custom APN list cleared during GetManagedProperties() "
                   << "call, setting Shill with empty list for network: "
                   << guid;
    base::ListValue empty_apn_list;
    SetShillCustomApnListForNetwork(*network, &empty_apn_list);
    return;
  }

  ApnPropertiesPtr pre_revamp_custom_apn =
      chromeos::network_config::GetApnProperties(
          custom_apn_list->front().GetDict(),
          /*is_apn_revamp_enabled=*/false);

  NET_LOG(EVENT) << "pre_revamp_custom_apn: "
                 << pre_revamp_custom_apn->access_point_name;

  const base::DictValue* cellular_dict =
      chromeos::network_config::GetDictionary(&properties.value(),
                                              ::onc::network_config::kCellular);
  std::optional<ApnPropertiesPtr> last_connected_attach_apn =
      GetPreRevampApnFromDict(cellular_dict,
                              ::onc::cellular::kLastConnectedAttachApnProperty);
  NET_LOG(EVENT) << "last_connected_attach_apn: "
                 << (last_connected_attach_apn.has_value()
                         ? (*last_connected_attach_apn)->access_point_name
                         : "none");

  std::optional<ApnPropertiesPtr> last_connected_default_apn =
      GetPreRevampApnFromDict(
          cellular_dict, ::onc::cellular::kLastConnectedDefaultApnProperty);
  NET_LOG(EVENT) << "last_connected_default_apn: "
                 << (last_connected_default_apn.has_value()
                         ? (*last_connected_default_apn)->access_point_name
                         : "none");

  const bool is_network_managed = network->IsManagedByPolicy();
  if (is_network_managed && !last_connected_default_apn) {
    ManagedApnPropertiesPtr selected_apn =
        chromeos::network_config::GetManagedApnProperties(
            cellular_dict, ::onc::cellular::kAPN);
    if (selected_apn && pre_revamp_custom_apn->access_point_name ==
                            selected_apn->access_point_name->active_value) {
      NET_LOG(EVENT) << "Managed network's selected APN matches the saved "
                     << "custom APN, migrating APN: " << guid;
      // Ensure the APN is enabled when it's migrated so that it's attempted
      // to be used by the new UI.
      pre_revamp_custom_apn->state = ApnState::kEnabled;
      pre_revamp_custom_apn->apn_types =
          GetMigratedApnTypes(pre_revamp_custom_apn);
      CellularNetworkMetricsLogger::LogManagedCustomApnMigrationType(
          CellularNetworkMetricsLogger::ManagedApnMigrationType::
              kMatchesSelectedApn);
      CreateCustomApn(iccid, guid, std::move(pre_revamp_custom_apn));
    } else {
      NET_LOG(EVENT)
          << "Managed network's selected APN doesn't match the saved custom "
          << "APN, setting Shill with empty list for network: " << guid;
      base::ListValue empty_apn_list;
      CellularNetworkMetricsLogger::LogManagedCustomApnMigrationType(
          CellularNetworkMetricsLogger::ManagedApnMigrationType::
              kDoesNotMatchSelectedApn);
      SetShillCustomApnListForNetwork(*network, &empty_apn_list);
    }
    return;
  }

  NET_LOG(EVENT)
      << "Migrating network with non-managed flow, is network managed: "
      << is_network_managed;

  const bool has_last_connected_attach = last_connected_attach_apn.has_value();
  const bool is_last_connected_attach_custom =
      has_last_connected_attach &&
      pre_revamp_custom_apn->access_point_name ==
          (*last_connected_attach_apn)->access_point_name;

  const bool has_last_connected_default =
      last_connected_default_apn.has_value();
  const bool is_last_connected_default_custom =
      has_last_connected_default &&
      pre_revamp_custom_apn->access_point_name ==
          (*last_connected_default_apn)->access_point_name;

  if (is_last_connected_attach_custom && is_last_connected_default_custom) {
    NET_LOG(EVENT) << "Network's last_connected_default_apn and "
                      "last_connected_attach_apn match the "
                   << "saved custom APN, migrating APN: " << guid
                   << " in the Enabled state with Apn types Attach and Default";

    pre_revamp_custom_apn->state = ApnState::kEnabled;
    pre_revamp_custom_apn->apn_types = {ApnType::kAttach, ApnType::kDefault};
    CellularNetworkMetricsLogger::LogUnmanagedCustomApnMigrationType(
        CellularNetworkMetricsLogger::UnmanagedApnMigrationType::
            kMatchesLastConnectedAttachAndDefault);
    CreateCustomApn(iccid, guid, std::move(pre_revamp_custom_apn));
    return;
  }

  if (!has_last_connected_attach && !has_last_connected_default) {
    std::optional<ApnPropertiesPtr> last_good_apn =
        GetPreRevampApnFromDict(cellular_dict, ::onc::cellular::kLastGoodAPN);

    if (last_good_apn && pre_revamp_custom_apn->access_point_name ==
                             (*last_good_apn)->access_point_name) {
      NET_LOG(EVENT) << "Network's last good APN matches the saved "
                     << "custom APN, migrating APN: " << guid
                     << "in the Enabled state";
      // Ensure the APN is enabled when it's migrated so that it's
      // attempted to be used by the new UI.
      pre_revamp_custom_apn->state = ApnState::kEnabled;
      CellularNetworkMetricsLogger::LogUnmanagedCustomApnMigrationType(
          CellularNetworkMetricsLogger::UnmanagedApnMigrationType::
              kMatchesLastGoodApn);
    } else {
      NET_LOG(EVENT) << "Network's last good APN does not match the saved "
                     << "custom APN, migrating APN: " << guid
                     << "in the Disabled state";
      // The custom APN was last unsuccessful in connecting when the flag was
      // off. Preserve the details of the custom APN but with a state of
      // Disabled.
      pre_revamp_custom_apn->state = ApnState::kDisabled;
      CellularNetworkMetricsLogger::LogUnmanagedCustomApnMigrationType(
          CellularNetworkMetricsLogger::UnmanagedApnMigrationType::
              kDoesNotMatchLastGoodApn);
    }
    pre_revamp_custom_apn->apn_types =
        GetMigratedApnTypes(pre_revamp_custom_apn);
    CreateCustomApn(iccid, guid, std::move(pre_revamp_custom_apn));
    return;
  }

  if (!has_last_connected_attach && is_last_connected_default_custom) {
    NET_LOG(EVENT) << "Network has no last connected attach APN but has "
                   << "a last connected default APN that matches the "
                   << "saved custom APN, migrating APN: " << guid
                   << " in the Enabled state with Apn type Default";

    pre_revamp_custom_apn->state = ApnState::kEnabled;
    pre_revamp_custom_apn->apn_types = {ApnType::kDefault};

    CellularNetworkMetricsLogger::LogUnmanagedCustomApnMigrationType(
        CellularNetworkMetricsLogger::UnmanagedApnMigrationType::
            kMatchesLastConnectedDefaultNoLastConnectedAttach);
    CreateCustomApn(iccid, guid, std::move(pre_revamp_custom_apn));
    return;
  }

  if (has_last_connected_attach && is_last_connected_default_custom) {
    NET_LOG(EVENT)
        << "Only last_connected_default_apn matches "
           "pre_revamp_custom_apn, last_connected_attach_apn exists "
           "but does not match pre_revamp_custom_apn, migrating "
           "last_connected_default_apn APN in enabled state with "
           "Default and Attach type, and last_connected_attach_apn APN in "
           "enabled state with Attach type for network: "
        << guid;
    CellularNetworkMetricsLogger::LogUnmanagedCustomApnMigrationType(
        CellularNetworkMetricsLogger::UnmanagedApnMigrationType::
            kMatchesLastConnectedDefaultOnlyAndAttachExists);

    CreateDefaultThenAttachCustomApns(std::move(*last_connected_attach_apn),
                                      std::move(*last_connected_default_apn),
                                      /*can_use_default_apn_as_attach=*/true,
                                      guid, iccid);
    return;
  }

  if (has_last_connected_default && is_last_connected_attach_custom) {
    NET_LOG(EVENT) << "Only last_connected_attach_apn matches "
                      "pre_revamp_custom_apn, last_connected_default exists "
                      "but does not match pre_revamp_custom_apn";
    CellularNetworkMetricsLogger::LogUnmanagedCustomApnMigrationType(
        CellularNetworkMetricsLogger::UnmanagedApnMigrationType::
            kMatchesLastConnectedAttachOnlyAndDefaultExists);
    CreateDefaultThenAttachCustomApns(std::move(*last_connected_attach_apn),
                                      std::move(*last_connected_default_apn),
                                      /*can_use_default_apn_as_attach=*/false,
                                      guid, iccid);
    return;
  }

  NET_LOG(EVENT) << "Network's last connected default APN and attach APN "
                 << "do not match the saved custom APN, migrating APN: " << guid
                 << " in the Disabled state.";
  pre_revamp_custom_apn->state = ApnState::kDisabled;
  pre_revamp_custom_apn->apn_types = GetMigratedApnTypes(pre_revamp_custom_apn);

  CellularNetworkMetricsLogger::LogUnmanagedCustomApnMigrationType(
      CellularNetworkMetricsLogger::UnmanagedApnMigrationType::
          kNoMatchingConnectedApn);
  CreateCustomApn(iccid, guid, std::move(pre_revamp_custom_apn));
}

void ApnMigrator::CreateDefaultThenAttachCustomApns(
    chromeos::network_config::mojom::ApnPropertiesPtr attach_apn,
    chromeos::network_config::mojom::ApnPropertiesPtr default_apn,
    bool can_use_default_apn_as_attach,
    const std::string& guid,
    const std::string& iccid) {
  DCHECK(!attach_apn.is_null());
  DCHECK(!default_apn.is_null());
  NET_LOG(EVENT) << "Migrating default_apn: " << default_apn->access_point_name
                 << " in the enabled state; default_apn is also type attach: "
                 << can_use_default_apn_as_attach
                 << ". Then, migrate attach_apn: "
                 << attach_apn->access_point_name
                 << " in the enabled state for Network with guid: " << guid
                 << " and iccid: " << iccid;

  attach_apn->state = ApnState::kEnabled;
  attach_apn->apn_types = {ApnType::kAttach};

  default_apn->state = ApnState::kEnabled;
  if (can_use_default_apn_as_attach) {
    default_apn->apn_types = {ApnType::kAttach, ApnType::kDefault};
  } else {
    default_apn->apn_types = {ApnType::kDefault};
  }

  // Migrate default APN first, then attach APN in case both are to
  // be enabled; enabled default APN must be created before enabled attach APN.
  // See b/303565348.
  // Also, |CompleteMigrationAttempt()| should only be called
  // once, when both APNs have been migrated.
  auto create_custom_attach_apn_callback =
      base::BindOnce(&ApnMigrator::CreateCustomApn, weak_factory_.GetWeakPtr(),
                     iccid, guid, std::move(attach_apn), std::nullopt);

  auto on_create_default_apn_callback =
      base::BindOnce(
          [](base::OnceClosure success_callback, const std::string& guid,
             bool success) {
            if (success) {
              NET_LOG(EVENT)
                  << "ApnMigrator: Succeeded migrating custom default APN "
                     "for network guid in the enabled state: "
                  << guid
                  << ", now migrating different custom attach APN in enabled "
                     "state.";
              std::move(success_callback).Run();
              return;
            }
            NET_LOG(ERROR)
                << "ApnMigrator: Failed to create custom default APN for "
                   "network of guid: "
                << guid
                << ", so will not proceed to create associated custom attach "
                   "APN";
          },
          std::move(create_custom_attach_apn_callback), guid);

  CreateCustomApn(iccid, guid, std::move(default_apn),
                  std::move(on_create_default_apn_callback));
}

void ApnMigrator::CreateCustomApn(
    const std::string& iccid,
    const std::string& network_guid,
    chromeos::network_config::mojom::ApnPropertiesPtr apn,
    std::optional<base::OnceCallback<void(bool)>> success_callback) {
  remote_cros_network_config_->CreateCustomApn(
      network_guid, std::move(apn),
      success_callback ? std::move(*success_callback)
                       : base::BindOnce(&ApnMigrator::CompleteMigrationAttempt,
                                        weak_factory_.GetWeakPtr(), iccid));
}

void ApnMigrator::CompleteMigrationAttempt(const std::string& iccid,
                                           bool success) {
  iccids_in_migration_.erase(iccid);

  if (success) {
    NET_LOG(EVENT) << "ApnMigrator: Mark network with ICCID: " << iccid
                   << " as migrated";
    managed_cellular_pref_handler_->AddApnMigratedIccid(iccid);
  }
}

NetworkMetadataStore* ApnMigrator::GetNetworkMetadataStore() {
  if (network_metadata_store_for_testing_) {
    return network_metadata_store_for_testing_;
  }

  return NetworkHandler::Get()->network_metadata_store();
}

}  // namespace ash
