content: import 'dart:async';
  import 'dart:convert';
  
  import 'package:bot_toast/bot_toast.dart';
  import 'package:flutter/material.dart';
  import 'package:flutter_hbb/common/hbbs/hbbs.dart';
  import 'package:flutter_hbb/models/ab_model.dart';
  import 'package:get/get.dart';
  
  import '../common.dart';
  import '../utils/http_service.dart' as http;
  import 'model.dart';
  import 'platform_model.dart';
  
  bool refreshingUser = false;
  
  class UserModel {
    final RxString userName = ''.obs;
    final RxBool isAdmin = false.obs;
    final RxString networkError = ''.obs;
    bool get isLogin => userName.isNotEmpty;
    WeakReference<FFI> parent;
  
    UserModel(this.parent) {
      userName.listen((p0) {
        // When user name becomes empty, show login button
        // When user name becomes non-empty:
        //  For _updateLocalUserInfo, network error will be set later
        //  For login success, should clear network error
        networkError.value = '';
      });
    }
  
    void refreshCurrentUser() async {
      if (bind.isDisableAccount()) return;
      networkError.value = '';
      final token = bind.mainGetLocalOption(key: 'access_token');
      if (token == '') {
        await updateOtherModels();
        return;
      }
      _updateLocalUserInfo();
      final url = await bind.mainGetApiServer();
      final body = {
        'id': await bind.mainGetMyId(),
        'uuid': await bind.mainGetUuid()
      };
      if (refreshingUser) return;
      try {
        refreshingUser = true;
        final http.Response response;
        try {
          response = await http.post(Uri.parse('$url/api/currentUser'),
              headers: {
                'Content-Type': 'application/json',
                'Authorization': 'Bearer $token'
              },
              body: json.encode(body));
        } catch (e) {
          networkError.value = e.toString();
          rethrow;
        }
        refreshingUser = false;
        final status = response.statusCode;
        if (status == 401 || status == 400) {
          reset(resetOther: status == 401);
          return;
        }
        final data = json.decode(decode_http_response(response));
        final error = data['error'];
        if (error != null) {
          throw error;
        }
  
        final user = UserPayload.fromJson(data);
        _parseAndUpdateUser(user);
      } catch (e) {
        debugPrint('Failed to refreshCurrentUser: $e');
      } finally {
        refreshingUser = false;
        await updateOtherModels();
      }
    }
  
    static Map<String, dynamic>? getLocalUserInfo() {
      final userInfo = bind.mainGetLocalOption(key: 'user_info');
      if (userInfo == '') {
        return null;
      }
      try {
        return json.decode(userInfo);
      } catch (e) {
        debugPrint('Failed to get local user info "$userInfo": $e');
      }
      return null;
    }
  
    _updateLocalUserInfo() {
      final userInfo = getLocalUserInfo();
      if (userInfo != null) {
        userName.value = userInfo['name'];
      }
    }
  
    Future<void> reset({bool resetOther = false}) async {
      await bind.mainSetLocalOption(key: 'access_token', value: '');
      await bind.mainSetLocalOption(key: 'user_info', value: '');
      if (resetOther) {
        await gFFI.abModel.reset();
        await gFFI.groupModel.reset();
      }
      userName.value = '';
    }
  
    _parseAndUpdateUser(UserPayload user) {
      userName.value = user.name;
      isAdmin.value = user.isAdmin;
      bind.mainSetLocalOption(key: 'user_info', value: jsonEncode(user));
      if (isWeb) {
        // ugly here, tmp solution
        bind.mainSetLocalOption(key: 'verifier', value: user.verifier ?? '');
      }
    }
  
    // update ab and group status
    static Future<void> updateOtherModels() async {
      await Future.wait([
        gFFI.abModel.pullAb(force: ForcePullAb.listAndCurrent, quiet: false),
        gFFI.groupModel.pull()
      ]);
    }
  
    Future<void> logOut({String? apiServer}) async {
      final tag = gFFI.dialogManager.showLoading(translate('Waiting'));
      try {
        final url = apiServer ?? await bind.mainGetApiServer();
        final authHeaders = getHttpHeaders();
        authHeaders['Content-Type'] = "application/json";
        await http
            .post(Uri.parse('$url/api/logout'),
                body: jsonEncode({
                  'id': await bind.mainGetMyId(),
                  'uuid': await bind.mainGetUuid(),
                }),
                headers: authHeaders)
            .timeout(Duration(seconds: 2));
      } catch (e) {
        debugPrint("request /api/logout failed: err=$e");
      } finally {
        await reset(resetOther: true);
        gFFI.dialogManager.dismissByTag(tag);
      }
    }
  
    /// throw [RequestException]
    Future<LoginResponse> login(LoginRequest loginRequest) async {
      final url = await bind.mainGetApiServer();
      final resp = await http.post(Uri.parse('$url/api/login'),
          body: jsonEncode(loginRequest.toJson()));
  
      final Map<String, dynamic> body;
      try {
        body = jsonDecode(decode_http_response(resp));
      } catch (e) {
        debugPrint("login: jsonDecode resp body failed: ${e.toString()}");
        if (resp.statusCode != 200) {
          BotToast.showText(
              contentColor: Colors.red, text: 'HTTP ${resp.statusCode}');
        }
        rethrow;
      }
      if (resp.statusCode != 200) {
        throw RequestException(resp.statusCode, body['error'] ?? '');
      }
      if (body['error'] != null) {
        throw RequestException(0, body['error']);
      }
  
      return getLoginResponseFromAuthBody(body);
    }
  
    LoginResponse getLoginResponseFromAuthBody(Map<String, dynamic> body) {
      final LoginResponse loginResponse;
      try {
        loginResponse = LoginResponse.fromJson(body);
      } catch (e) {
        debugPrint("login: jsonDecode LoginResponse failed: ${e.toString()}");
        rethrow;
      }
  
      final isLogInDone = loginResponse.type == HttpType.kAuthResTypeToken &&
          loginResponse.access_token != null;
      if (isLogInDone && loginResponse.user != null) {
        _parseAndUpdateUser(loginResponse.user!);
      }
  
      return loginResponse;
    }
  
    static Future<List<dynamic>> queryOidcLoginOptions() async {
      try {
        final url = await bind.mainGetApiServer();
        if (url.trim().isEmpty) return [];
        final resp = await http.get(Uri.parse('$url/api/login-options'));
        final List<String> ops = [];
        for (final item in jsonDecode(resp.body)) {
          ops.add(item as String);
        }
        for (final item in ops) {
          if (item.startsWith('common-oidc/')) {
            return jsonDecode(item.substring('common-oidc/'.length));
          }
        }
        return ops
            .where((item) => item.startsWith('oidc/'))
            .map((item) => {'name': item.substring('oidc/'.length)})
            .toList();
      } catch (e) {
        debugPrint(
            "queryOidcLoginOptions: jsonDecode resp body failed: ${e.toString()}");
        return [];
      }
    }
  }
  