import 'dart:async';

import 'package:dio/dio.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:guru_app/account/account_data_store.dart';
import 'package:guru_app/account/model/account.dart';
import 'package:guru_app/account/model/account_profile.dart';
import 'package:guru_app/account/model/user.dart';
import 'package:guru_app/analytics/guru_analytics.dart';
import 'package:guru_app/api/guru_api.dart';
import 'package:guru_app/firebase/firebase.dart';
import 'package:guru_app/firebase/firestore/firestore_manager.dart';
import 'package:guru_app/guru_app.dart';
import 'package:guru_app/property/app_property.dart';
import 'package:guru_app/property/settings/guru_settings.dart';
import 'package:guru_utils/auth/auth_credential_manager.dart';
import 'package:guru_utils/collection/collectionutils.dart';
import 'package:guru_utils/core/ext.dart';
import 'package:guru_utils/datetime/datetime_utils.dart';
import 'package:guru_utils/device/device_info.dart';
import 'package:guru_utils/device/device_utils.dart';
import 'package:guru_utils/log/log.dart';
import 'package:guru_utils/network/network_utils.dart';

import 'model/credential.dart';

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

part "account_auth_extension.dart";

part "account_auth_invoker.dart";

class ModifyNicknameException implements Exception {
  final String? message;
  final dynamic cause;

  ModifyNicknameException(this.message, {this.cause});

  @override
  String toString() {
    return "ModifyNicknameException: $message cause:$cause";
  }
}

class ModifyLevelException implements Exception {
  final String? message;
  final dynamic cause;

  ModifyLevelException(this.message, {this.cause});

  @override
  String toString() {
    return "ModifyLevelException: $message cause:$cause";
  }
}

class AccountManager {
  final AccountDataStore accountDataStore;

  Timer? retryTimer;

  static final AccountManager instance = AccountManager();

  static const List<AuthCredentialDelegate> defaultSupportedAuthCredentialDelegates = [
    AnonymousCredentialDelegate()
  ];

  AccountManager() : accountDataStore = AccountDataStore.instance;

  Future init({Completer<dynamic>? completer}) async {
    try {
      final result = accountDataStore.transitionTo(AccountDataStatus.initializing);
      if (!result) {
        Log.w(
            "init account error, current initializing! please wait result! retry[${accountDataStore.initRetryCount}]",
            tag: "Account");
        return;
      }
      retryTimer?.cancel();
      final account = await AppProperty.getInstance().loadAccount();
      final restoreResult = await _restoreAccount(account);
      if (!restoreResult) {
        Log.v("init account error: restoreAccount error! retry[${accountDataStore.initRetryCount}]",
            tag: "Account");
        _retry();
      } else {
        accountDataStore.initRetryCount = 0;
        accountDataStore.transitionTo(AccountDataStatus.initialized);
        Log.v("init account success!", tag: "Account");
      }
    } catch (error, stacktrace) {
      completer?.complete(error);
      Log.v("init account error retry[${accountDataStore.initRetryCount}]:$error $stacktrace",
          tag: "Account");
      _retry();
    }
    completer?.complete(true);
  }

  void _retry() {
    final intervalSeconds = (accountDataStore.initRetryCount * 2 + 8).clamp(8, 30);
    retryTimer?.cancel();
    accountDataStore.transitionTo(AccountDataStatus.waiting);
    retryTimer = Timer(Duration(seconds: intervalSeconds), () {
      init();
      accountDataStore.initRetryCount++;
    });
  }

  Future updateLocalProfile(Map<String, dynamic> modifiedJson) async {
    modifiedJson[AccountProfile.dirtyField] = true;
    final dirtyAccountProfile = accountDataStore.accountProfile?.merge(modifiedJson) ??
        AccountProfile.fromJson(modifiedJson);
    AppProperty.getInstance().setAccountProfile(dirtyAccountProfile);
    accountDataStore.updateAccountProfile(dirtyAccountProfile);
  }

  /// 登录
  ///
  /// [authType] 登录类型
  /// [onConflict] 登录冲突处理
  /// [onLogin] 登录成功处理
  ///
  Future<bool> loginWith(AuthType authType) async {
    late final Credential? credential;
    try {
      final result = await AuthCredentialManager.instance.loginWith(authType);
      credential = result.credential;
      if (!result.isSuccess || credential == null) {
        Log.w("loginWith $authType error! credential: [$credential]", tag: "Account");
        return false;
      }
    } catch (error, stacktrace) {
      Log.e("loginWith $authType error:$error, $stacktrace");
      return false;
    }

    try {
      /// 如果冲突将会报 409 的错
      final guruUser = await _requestGuruUser(credential);
      await processLogin(guruUser, credential);
      return true;
    } catch (error, stacktrace) {
      Log.w("loginWith $authType error:$error, $stacktrace");
      if (error is DioError && error.response?.statusCode == 409) {
        return await _processConflict(credential);
      } else {
        return false;
      }
    }
  }

  Future<bool> processLogin(GuruUser user, Credential credential) async {
    await _updateGuruUser(user);
    await _bindCredential(credential);
    try {
      await _verifyOrReportAuthDevice(user);
      authenticateFirebase();
    } catch (error, stacktrace) {
      Log.e("_verifyOrReportAuthDevice error!$error $stacktrace");
    }
    if (credential.isAnonymous) {
      return await _invokeAnonymousLogin(user, credential);
    } else {
      return await _invokeLogin(user, credential);
    }
  }

  ///
  /// 登出操作，会把所有的第三方登陆都登出，如果当前有对应的匿名登陆的 credential 那么保留到匿名登陆的状态
  /// 如果当前没有匿名登录的 credential 那么这里将会重新创建一个匿名登陆，并且这里不会进行数据迁移
  ///
  /// 如果在调用 logout 时，明确指定了要登出哪些 AuthType，
  /// 那么 logout 方法将尝试用 unbind 的方法去处理用户信息。
  /// 当尝试解绑掉指定的 authTypes 时，只要满足下面两种情况的其中一种，
  /// 都不能以 unbind 形式进行处理，都会认定为是真正的 logout
  /// 1. 如果解绑掉所有指定的凭证后，当前凭证信息只保留了一个匿名凭证
  /// 2. 如果解绑掉所有指定的凭证后，当前没有任何凭证信息
  /// 如果上面两个条件都不满足，那么将以 unbind方法进行 logout
  /// 以 unbind 形式进行 logout时，将不会通知应用 onLogout方法
  ///
  /// 因此这里需要注意，就算明确指定了登出的 AuthType，依然存在调用 onLogout 的情况
  ///
  Future<GuruUser?> logout({bool switching = false, Set<AuthType>? authTypes}) async {
    bool isUnbind = false;

    if (authTypes != null && authTypes.isNotEmpty) {
      final currentCredentials = accountDataStore.credentials.keys.toSet();
      currentCredentials.removeAll(authTypes);
      currentCredentials.remove(AuthType.anonymous);
      isUnbind = currentCredentials.isNotEmpty;
    }

    final logoutUser = accountDataStore.user?.copyWith();
    try {
      if (!isUnbind && logoutUser != null) {
        final result = await _invokeLogout(logoutUser);
        if (!result) {
          Log.w("logout error! ignore!");
          return null;
        }
      }
    } catch (error, stacktrace) {
      Log.w("invokeLogout error! $error!");
      return null;
    }
    for (var authType in accountDataStore.credentials.keys) {
      /// 默认的登出只是 unbind 掉三方的 credentials ，不会真正的登出
      /// 如果当前没有匿名登陆，那么就会真正的登出，并会重新登陆匿名，但是数据不会清除
      /// 如果 authTypes传的是 null,这里会返回空，依然满足不等于 False,
      /// 这里只要是空或是真正的包含才会进行真正的解绑
      if (authTypes?.contains(authType) != false && authType != AuthType.anonymous) {
        await AuthCredentialManager.instance.logout(authType);
        _unbindCredential(authType);
      }
    }

    /// 如果当前连匿名登陆也没有了，那么就会重新登陆匿名帐号
    /// 如果是正在切换帐号的话，这里不需要登录一个新的匿名帐号
    if (!switching && accountDataStore.credentials.isEmpty) {
      final auth = await _retrieveAnonymous();
      if (auth != null) {
        await processLogin(auth.user, auth.credential!);
      }
    }
    return logoutUser;
  }

  Future<bool> modifyProfile(
      {String? nickname,
      String? avatar,
      String? countryCode,
      Map<String, dynamic> userData = const <String, dynamic>{}}) async {
    int retryCount = 2;
    Log.i("modifyProfile $nickname $avatar  $countryCode", syncFirebase: true, tag: "Account");
    if (nickname == null && avatar == null && countryCode == null && userData.isEmpty) {
      return false;
    }
    final now = DateTimeUtils.currentTimeInMillis();
    final modifiedJson = CollectionUtils.filterOutNulls(<String, dynamic>{
      AccountProfile.uidField: accountDataStore.uid,
      AccountProfile.nicknameField: nickname,
      AccountProfile.countryField: countryCode?.toLowerCase(),
      AccountProfile.avatarField: avatar,
      AccountProfile.updateAtField: now,
      AccountProfile.versionField: GuruSettings.instance.version.get(),
      AccountProfile.roleField:
          GuruSettings.instance.debugMode.get() == true ? UserAttr.tester : UserAttr.real,
      AccountProfile.dirtyField: true,
      ...userData
    });
    await updateLocalProfile(modifiedJson);

    /// 如果本地部署没有打开同步 AccountProfile 机制，这里直接返回 true
    if (!GuruApp.instance.appSpec.deployment.enabledSyncAccountProfile) {
      return true;
    }
    while ((await NetworkUtils.isNetworkConnected()) && retryCount-- > 0) {
      final accountProfile =
          await FirestoreManager.instance.modifyProfile(modifiedJson).onError((error, stackTrace) {
        Log.i("modifyProfile error!:$error");
        GuruAnalytics.instance.logException(ModifyLevelException("modifyProfile error!:$error"),
            stacktrace: stackTrace);
        return null;
      });
      if (accountProfile != null) {
        Log.i("modifyProfile success! $accountProfile", tag: "Account");
        AppProperty.getInstance().setAccountProfile(accountProfile);
        accountDataStore.updateAccountProfile(accountProfile);
        return true;
      } else {
        Log.i("[$retryCount] modify profile error!", tag: "Account");
        await authenticateFirebase()
            .timeout(const Duration(seconds: 15))
            .catchError((error, stackTrace) {
          Log.i("re-authenticate error:$error", stackTrace: stackTrace, tag: "Account");
        });
        await Future.delayed(const Duration(seconds: 1));
      }
    }

    return false;
  }
}
