import 'dart:convert';
import 'package:firebase_auth/firebase_auth.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/guru_app.dart';
import 'package:guru_app/property/app_property.dart';
import 'package:guru_utils/auth/auth_credential_manager.dart';
import 'package:guru_utils/device/device_info.dart';
import 'package:guru_utils/extensions/extensions.dart';

import 'package:http/http.dart' as http;

/// Created by Haoyi on 6/3/21
///
enum AccountDataStatus { idle, initializing, initialized, waiting, error }

class AccountDataStore {
  static final AccountDataStore instance = AccountDataStore._();

  final BehaviorSubject<DeviceInfo?> _deviceInfoSubject = BehaviorSubject.seeded(null);
  final BehaviorSubject<GuruUser?> _guruUserSubject = BehaviorSubject.seeded(null);
  final BehaviorSubject<User?> _firebaseUser = BehaviorSubject.seeded(null);
  final BehaviorSubject<AccountProfile?> _accountProfile = BehaviorSubject.seeded(null);
  final BehaviorSubject<AccountDataStatus> _accountDataStatus =
      BehaviorSubject<AccountDataStatus>.seeded(AccountDataStatus.idle);

  final BehaviorSubject<Map<AuthType, Credential>> _credentials =
      BehaviorSubject.seeded(<AuthType, Credential>{});

  int initRetryCount = 0;

  Stream<AccountProfile?> get observableAccountProfile => _accountProfile.stream;

  // final EnvConfig envConfig;

  AccountDataStore._();

  @Deprecated("use guruToken instead")
  String? get saasToken => _guruUserSubject.value?.token;

  String? get guruToken => _guruUserSubject.value?.token;

  String? get uid => _guruUserSubject.value?.uid;

  AccountProfile? get accountProfile => _accountProfile.value;

  String? get nickname => _accountProfile.value?.nickname;

  String? get countryCode => _accountProfile.value?.countryCode;

  GuruUser? get user => _guruUserSubject.value;

  String? get avatar => _accountProfile.value?.avatar;

  DeviceInfo? get currentDevice => _deviceInfoSubject.value;

  AccountDataStatus get accountDataStatus => _accountDataStatus.value;

  bool get initialized => _accountDataStatus.value == AccountDataStatus.initialized;

  Stream<bool> get observableInitialized =>
      _accountDataStatus.stream.map((status) => status == AccountDataStatus.initialized);

  bool get hasUid => uid?.isNotEmpty == true;

  bool get isAnonymous =>
      (uid?.isNotEmpty != true) ||
      (_credentials.value.containsKey(AuthType.anonymous) && _credentials.value.length == 1);

  Stream<GuruUser?> get observableSaasUser => _guruUserSubject.stream;

  Map<AuthType, Credential> get credentials => _credentials.value;

  Account get account => Account.restore(
      guruUser: user,
      device: currentDevice,
      accountProfile: accountProfile,
      firebaseUser: _firebaseUser.value,
      credentials: credentials);

  Stream<Account> get observableAccount => Rx.combineLatestList([
        _guruUserSubject.stream,
        _deviceInfoSubject.stream,
        _accountProfile.stream,
        _firebaseUser.stream,
        _credentials.stream
      ]).debounceTime(const Duration(milliseconds: 100)).map((_) => account);

  bool get isSocialLogged => (uid?.isNotEmpty == true) && credentials.isNotEmpty;

  void dispose() {
    _deviceInfoSubject.close();
    _guruUserSubject.close();
    _firebaseUser.close();
    _accountProfile.close();
    _credentials.close();
  }

  Future<GuruUser?> signInAnonymousInLocked() async {
    // 这里需要使用原始的http post请求。否则这里将会死锁DIO所有请求
    final secret = await AppProperty.getInstance().getAnonymousSecretKey();
    final headers = {
      "X-APP-ID": GuruApp.instance.details.saasAppId,
      "content-type": "application/json"
    };
    try {
      final uri = Uri.parse("${GuruApi.saasApiHost}/auth/api/v1/tokens/provider/secret");
      final response = await http
          .post(uri,
              headers: headers,
              body: jsonEncode(AnonymousLoginReqBody(secret: secret)),
              encoding: utf8)
          .timeout(const Duration(seconds: 30));
      final data = const Utf8Decoder().convert(response.bodyBytes);
      if (data.isNotEmpty) {
        final result = json.decode(data);
        return GuruUser.fromJson(result["data"]);
      }
    } catch (error, stacktrace) {
      Log.v("signInAnonymousInLocked error:$error", tag: "Account");
    }
    return null;
  }

  Future refreshAuth() async {
    final guruUser = await signInAnonymousInLocked();
    if (guruUser != null) {
      updateGuruUser(guruUser);
    }
  }

  void updateDeviceInfo(DeviceInfo deviceInfo) {
    _deviceInfoSubject.addEx(deviceInfo);
  }

  @Deprecated("use updateGuruUser instead")
  void updateSaasUser(GuruUser saasUser) {
    updateGuruUser(saasUser);
  }

  void updateGuruUser(GuruUser guruUser) {
    _guruUserSubject.addEx(guruUser);
    if (guruUser.createAtTimestamp > 0) {
      GuruAnalytics.instance
          .setUserProperty("user_created_timestamp", guruUser.createAtTimestamp.toString());
    }
  }

  void updateFirebaseUser(User user) {
    _firebaseUser.addEx(user);
  }

  void updateAccountProfile(AccountProfile profile) {
    _accountProfile.addEx(profile);
  }

  void bindCredential(Credential credential) {
    final newCredentials = Map.of(_credentials.value);
    newCredentials[credential.authType] = credential;
    _credentials.addEx(newCredentials);
  }

  void unbindCredential(AuthType authType) {
    final newCredentials = Map.of(_credentials.value);
    newCredentials.remove(authType);
    _credentials.addEx(newCredentials);
  }

  void updateCredentials(Map<AuthType, Credential> credentials) {
    _credentials.addEx(Map.of(credentials));
  }

  bool transitionTo(AccountDataStatus status, {AccountDataStatus? expectStatus}) {
    return _accountDataStatus.addIfChanged(status);
  }

  logout() {
    _guruUserSubject.addEx(null);
    _firebaseUser.addEx(null);
    _deviceInfoSubject.addEx(null);
    _accountProfile.addEx(null);
    _accountDataStatus.addIfChanged(AccountDataStatus.idle);
  }
}
