/// Created by Haoyi on 6/3/21
part of "account_manager.dart";

extension AccountServiceExtension on AccountManager {
  Future<bool> _restoreAccount(Account account) async {
    AccountAuth? anonymousAuth;
    GuruUser? guruUser = account.guruUser;
    Log.d("restoreAccount $guruUser", tag: "Account");
    try {
      if (guruUser == null) {
        anonymousAuth = await _retrieveAnonymous();
        guruUser = anonymousAuth?.user;
      }
    } catch (error, stacktrace) {
      Log.w("loginWith Anonymous error:$error, $stacktrace");
    }

    Log.v("_restoreAccount saasUser:$guruUser", tag: "Account");
    final device = account.device;
    if (device != null) {
      _updateDevice(device);
    }

    final accountProfile = account.accountProfile;
    if (accountProfile != null) {
      _updateAccountProfile(accountProfile);
    }

    final credentials = account.credentials;
    if (credentials.isNotEmpty) {
      _restoreCredentials(credentials);
    }

    if (guruUser != null) {
      await _updateGuruUser(guruUser);
      await _verifyOrReportAuthDevice(guruUser);
      await authenticateFirebase();
      if (accountProfile != null) {
        await _checkOrUploadAccountProfile(accountProfile);
      }
      if (anonymousAuth != null) {
        final anonymousCredential = anonymousAuth.credential;
        if (anonymousCredential != null) {
          _bindCredential(anonymousCredential);
          return await _invokeAnonymousLogin(anonymousAuth.user, anonymousCredential);
        }
      }
      return true;
    } else {
      return false;
    }
  }

  Future switchUser(GuruUser newUser) async {
    /// 更新 login 的用户信息
    _updateGuruUser(newUser);
    try {
      await _verifyOrReportAuthDevice(newUser);
      // 登陆 firebase 不需要同步等待
      authenticateFirebase();
    } catch (error, stacktrace) {
      Log.w("loginWithCredential error:$error, $stacktrace");
    }
  }

  Future<bool> _switchAccount(Credential credential) async {
    GuruUser? loginUser;
    GuruUser? logoutUser;

    /// 这里只调用接口获取对应的新用户信息,还没有做对应的绑定操作
    try {
      loginUser = await _loginGuruWithCredential(credential);
    } catch (error, stacktrace) {
      Log.w("loginWithCredential[${credential.authType}] error:$error, $stacktrace");
      return false;
    }
    if (loginUser.isSame(accountDataStore.user)) {
      Log.w("loginWithCredential same user!", tag: "Account");
      _bindCredential(credential);
      return false;
    }
    bool result = false;

    /// logout 内部进行了拦截，因此这里总是会返回一个 logoutUser
    /// logout传入 switch参数，表示是一个切换帐号，不需要真正的登出，
    /// 因为在下面的 SwitchAccount方法中会完成后续的过程
    logoutUser = await logout(switching: true);

    /// 如果这里没有返回出对应的退出用户，将认为退出失败
    /// 因为进到 switchAccount 里肯定是非匿名登陆的帐号做登出操作
    if (logoutUser != null) {
      result = await GuruApp.instance.switchAccount(loginUser, credential, oldUser: logoutUser);
    }
    return result;
  }

  Future<bool> _processConflict(Credential credential) async {
    final historicalSocialAuths = await AppProperty.getInstance().getHistoricalSocialAuths();

    /// 如果是匿名登录，并且在这个设备上同样的用户没有绑定过其它的三方登陆凭证
    /// 这种情况下，认定为新用户，中台会静默解决冲突，并且对应的数据库不会发生迁移
    if (accountDataStore.isAnonymous && historicalSocialAuths.isEmpty) {
      Log.d("associate conflict: _loginGuruWithCredential!");
      final user = accountDataStore.user;
      final oldUid = user?.uid ?? "";
      if (user != null) {
        await _invokeAnonymousLogout(user);
      }

      /// 因为这里是匿名登陆，因此在冲突的时候通过静默的方法切换账户
      final guruUser = await _loginGuruWithCredential(credential);

      /// 由于是冲突处理，此时的匿名帐号已经和当前新登陆的用户不能配对
      /// 因此在新用户登陆成功后，这里需要将匿名帐户的凭证解绑，并清除匿名的密钥
      /// 这样做的目的是为了在该帐号退出时，判断匿名帐号是否存在，
      /// 如果不存在会创建一个新的匿名帐号，确保数据不被污染
      await _unbindCredential(AuthType.anonymous);

      /// 将新的用户进行关联，此时当前设备上只有一个登陆凭证
      await processLogin(guruUser, credential);

      GuruAnalytics.instance.logGuruEvent("switch_account", {
        "auth": getAuthName(credential.authType),
        "old_uid": oldUid,
        "new_uid": guruUser.uid,
        "silent": true
      });
      return true;
    } else {
      final canSwitch = await _invokeConflict();
      if (canSwitch) {
        return await _switchAccount(credential);
      }
    }
    return false;
  }

  Future<DeviceTrack> _buildDevice(GuruUser saasUser) async {
    final DeviceInfo? deviceInfo = await AppProperty.getInstance().getAccountDevice();
    final firebasePushToken = await RemoteMessagingManager.instance.getToken();

    if (firebasePushToken != null) {
      final deviceId = await AppProperty.getInstance().getDeviceId();
      final newDeviceInfo = await DeviceUtils.buildDeviceInfo(
          deviceId: deviceId, firebasePushToken: firebasePushToken, uid: saasUser.uid);
      return DeviceTrack(newDeviceInfo, deviceInfo);
    }
    return DeviceTrack(null, deviceInfo);
  }

  Future<AccountAuth?> _retrieveAnonymous() async {
    final result = await AuthCredentialManager.instance.loginWith(AuthType.anonymous);
    final credential = result.credential;
    if (!result.isSuccess || credential == null) {
      Log.w("_retrieveAnonymous error!", tag: "Account");
      return null;
    }
    final user = await _requestGuruUser(credential);
    return AccountAuth(user, credential: credential);
  }

  Future<GuruUser> _loginGuruWithCredential(Credential credential) async {
    return await GuruApi.instance.loginGuruWithCredential(credential: credential);
  }

  Future<GuruUser> _associateCredential(Credential credential) async {
    return await GuruApi.instance.associateCredential(credential: credential);
  }

  Future<GuruUser> _requestGuruUser(Credential credential) async {
    //MetaData是匿名请求，或者当前没有任何 GuruUser Id，走signIn接口
    if (!accountDataStore.hasUid || credential.isAnonymous) {
      Log.d("_loginGuruWithCredential!", tag: "Account");
      return await _loginGuruWithCredential(credential);
    } else {
      Log.d("_associateCredential!");
      //当前有 GuruUser id，并且MetaData是三方登录Token，走associate接口（不管已有的SaasUser是不是三方登录）
      return await _associateCredential(credential);
    }
  }

  Future _verifyOrReportAuthDevice(GuruUser guruUser) async {
    final deviceTrack = await _buildDevice(guruUser);
    final latestReportDeviceTimestamp =
        await AppProperty.getInstance().getLatestReportDeviceTimestamp();
    final elapsedInterval = DateTimeUtils.currentTimeInMillis() - latestReportDeviceTimestamp;
    final isChanged = (elapsedInterval > DateTimeUtils.sixHourInMillis) || deviceTrack.isChanged;
    final reportDevice = deviceTrack.device;
    final deviceId = deviceTrack.device?.deviceId ?? "";
    if (deviceId.isNotEmpty) {
      GuruAnalytics.instance.setDeviceId(deviceId);
    }
    if (isChanged && reportDevice?.isValid == true && guruUser.isValid == true) {
      final result = await GuruApi.instance.reportDevice(reportDevice!).then((_) {
        return true;
      }).catchError((error) {
        Log.i("reportDevice error:$error", tag: "Account");
        return false;
      });
      if (result) {
        reportDevice.dumpDevice(msg: "REPORT DEVICE SUCCESS");
        _updateDevice(reportDevice);
        AppProperty.getInstance()
            .setLatestReportDeviceTimestamp(DateTimeUtils.currentTimeInMillis());
        AppProperty.getInstance().setAccountDevice(reportDevice);
      }
    }
  }

  Future _checkOrUploadAccountProfile(AccountProfile accountProfile) async {
    bool upload = accountProfile.dirty;

    String? changedCountryCode = DeviceUtils.buildLocaleInfo().countryCode.toLowerCase();
    if (DartExt.isBlank(changedCountryCode) || accountProfile.countryCode == changedCountryCode) {
      changedCountryCode = null;
    } else {
      upload = true;
    }

    Log.d(
        "_checkOrUploadAccountProfile dirty:${accountProfile.dirty} upload:$upload  $changedCountryCode",
        tag: "Account");
    if (upload) {
      await modifyProfile(countryCode: changedCountryCode);
    }
  }

  void refreshFcmToken() {
    final saasUser = accountDataStore.user;
    if (saasUser != null) {
      _verifyOrReportAuthDevice(saasUser);
    }
  }

  void _updateDevice(DeviceInfo device) {
    accountDataStore.updateDeviceInfo(device);
  }

  Future _bindCredential(Credential credential) async {
    accountDataStore.bindCredential(credential);

    /// 这里匿名帐号是不会保存凭证的，因为匿名帐号的登陆凭证是自生成的
    if (credential.authType != AuthType.anonymous) {
      await AppProperty.getInstance().saveCredential(credential);
    }
  }

  Future _unbindCredential(AuthType authType) async {
    accountDataStore.unbindCredential(authType);
    if (authType != AuthType.anonymous) {
      await AppProperty.getInstance().deleteCredential(authType);
    } else {
      await AppProperty.getInstance().clearAnonymousSecretKey();
    }
  }

  void _restoreCredentials(Map<AuthType, Credential> credentials) {
    accountDataStore.updateCredentials(credentials);
  }

  Future _updateGuruUser(GuruUser guruUser) async {
    accountDataStore.updateGuruUser(guruUser);
    await AppProperty.getInstance().setAccountGuruUser(guruUser);
    await GuruAnalytics.instance.setUserId(guruUser.uid);
  }

  void _updateFirebaseUser(User user) {
    accountDataStore.updateFirebaseUser(user);
  }

  void _updateAccountProfile(AccountProfile accountProfile) {
    accountDataStore.updateAccountProfile(accountProfile);
  }
}
