import 'package:flutter/widgets.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/common/hbbs/hbbs.dart';
import 'package:flutter_hbb/common/widgets/peers_view.dart';
import 'package:flutter_hbb/models/model.dart';
import 'package:flutter_hbb/models/peer_model.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart';
import 'dart:convert';
import '../utils/http_service.dart' as http;

class GroupModel {
  final RxBool groupLoading = false.obs;
  final RxString groupLoadError = "".obs;
  final RxList<DeviceGroupPayload> deviceGroups = RxList.empty(growable: true);
  final RxList<UserPayload> users = RxList.empty(growable: true);
  final RxList<Peer> peers = RxList.empty(growable: true);
  final RxBool isSelectedDeviceGroup = false.obs;
  final RxString selectedAccessibleItemName = ''.obs;
  final RxString searchAccessibleItemNameText = ''.obs;
  WeakReference<FFI> parent;
  var initialized = false;
  var _cacheLoadOnceFlag = false;
  var _statusCode = 200;

  final Map<String, VoidCallback> _peerIdUpdateListeners = {};

  bool get emtpy => deviceGroups.isEmpty && users.isEmpty && peers.isEmpty;

  late final Peers peersModel;

  GroupModel(this.parent) {
    peersModel = Peers(
        name: PeersModelName.group,
        getInitPeers: () => peers,
        loadEvent: LoadEvent.group);
  }

  Future<void> pull({force = true, quiet = false}) async {
    if (bind.isDisableGroupPanel()) return;
    if (!gFFI.userModel.isLogin || groupLoading.value) return;
    if (gFFI.userModel.networkError.isNotEmpty) return;
    if (!force && initialized) return;
    if (!quiet) {
      groupLoading.value = true;
      groupLoadError.value = "";
    }
    try {
      await _pull();
      _tryHandlePullError();
    } catch (e) {
      print("pull accessibles error: $e");
    }
    groupLoading.value = false;
    initialized = true;
    platformFFI.tryHandle({'name': LoadEvent.group});
    if (_statusCode == 401) {
      gFFI.userModel.reset(resetOther: true);
    } else {
      _saveCache();
    }
  }

  Future<void> _pull() async {
    List<DeviceGroupPayload> tmpDeviceGroups = List.empty(growable: true);
    if (!await _getDeviceGroups(tmpDeviceGroups)) {
      // old hbbs doesn't support this api
      // return;
    }
    tmpDeviceGroups.sort((a, b) => a.name.compareTo(b.name));
    List<UserPayload> tmpUsers = List.empty(growable: true);
    if (!await _getUsers(tmpUsers)) {
      return;
    }
    List<Peer> tmpPeers = List.empty(growable: true);
    if (!await _getPeers(tmpPeers)) {
      return;
    }
    deviceGroups.value = tmpDeviceGroups;
    // me first
    var index = tmpUsers
        .indexWhere((user) => user.name == gFFI.userModel.userName.value);
    if (index != -1) {
      var user = tmpUsers.removeAt(index);
      tmpUsers.insert(0, user);
    }
    users.value = tmpUsers;
    if (!users.any((u) => u.name == selectedAccessibleItemName.value) &&
        !deviceGroups.any((d) => d.name == selectedAccessibleItemName.value)) {
      selectedAccessibleItemName.value = '';
    }
    // recover online
    final oldOnlineIDs = peers.where((e) => e.online).map((e) => e.id).toList();
    peers.value = tmpPeers;
    peers
        .where((e) => oldOnlineIDs.contains(e.id))
        .map((e) => e.online = true)
        .toList();
    groupLoadError.value = '';
    _callbackPeerUpdate();
  }

  Future<bool> _getDeviceGroups(
      List<DeviceGroupPayload> tmpDeviceGroups) async {
    final api = "${await bind.mainGetApiServer()}/api/device-group/accessible";
    try {
      var uri0 = Uri.parse(api);
      final pageSize = 100;
      var total = 0;
      int current = 0;
      do {
        current += 1;
        var uri = Uri(
            scheme: uri0.scheme,
            host: uri0.host,
            path: uri0.path,
            port: uri0.port,
            queryParameters: {
              'current': current.toString(),
              'pageSize': pageSize.toString(),
            });
        final resp = await http.get(uri, headers: getHttpHeaders());
        _statusCode = resp.statusCode;
        Map<String, dynamic> json =
            _jsonDecodeResp(decode_http_response(resp), resp.statusCode);
        if (json.containsKey('error')) {
          throw json['error'];
        }
        if (resp.statusCode != 200) {
          throw 'HTTP ${resp.statusCode}';
        }
        if (json.containsKey('total')) {
          if (total == 0) total = json['total'];
          if (json.containsKey('data')) {
            final data = json['data'];
            if (data is List) {
              for (final user in data) {
                final u = DeviceGroupPayload.fromJson(user);
                int index = tmpDeviceGroups.indexWhere((e) => e.name == u.name);
                if (index < 0) {
                  tmpDeviceGroups.add(u);
                } else {
                  tmpDeviceGroups[index] = u;
                }
              }
            }
          }
        }
      } while (current * pageSize < total);
      return true;
    } catch (err) {
      debugPrint('get accessible device groups: $err');
      // old hbbs doesn't support this api
      // groupLoadError.value =
      //     '${translate('pull_group_failed_tip')}: ${translate(err.toString())}';
    }
    return false;
  }

  Future<bool> _getUsers(List<UserPayload> tmpUsers) async {
    final api = "${await bind.mainGetApiServer()}/api/users";
    try {
      var uri0 = Uri.parse(api);
      final pageSize = 100;
      var total = 0;
      int current = 0;
      do {
        current += 1;
        var uri = Uri(
            scheme: uri0.scheme,
            host: uri0.host,
            path: uri0.path,
            port: uri0.port,
            queryParameters: {
              'current': current.toString(),
              'pageSize': pageSize.toString(),
              'accessible': '',
              'status': '1',
            });
        final resp = await http.get(uri, headers: getHttpHeaders());
        _statusCode = resp.statusCode;
        Map<String, dynamic> json =
            _jsonDecodeResp(decode_http_response(resp), resp.statusCode);
        if (json.containsKey('error')) {
          if (json['error'] == 'Admin required!' ||
              json['error']
                  .toString()
                  .contains('ambiguous column name: status')) {
            throw translate('upgrade_rustdesk_server_pro_to_{1.1.10}_tip');
          } else {
            throw json['error'];
          }
        }
        if (resp.statusCode != 200) {
          throw 'HTTP ${resp.statusCode}';
        }
        if (json.containsKey('total')) {
          if (total == 0) total = json['total'];
          if (json.containsKey('data')) {
            final data = json['data'];
            if (data is List) {
              for (final user in data) {
                final u = UserPayload.fromJson(user);
                int index = tmpUsers.indexWhere((e) => e.name == u.name);
                if (index < 0) {
                  tmpUsers.add(u);
                } else {
                  tmpUsers[index] = u;
                }
              }
            }
          }
        }
      } while (current * pageSize < total);
      return true;
    } catch (err) {
      debugPrint('get accessible users: $err');
      groupLoadError.value =
          '${translate('pull_group_failed_tip')}: ${translate(err.toString())}';
    }
    return false;
  }

  Future<bool> _getPeers(List<Peer> tmpPeers) async {
    try {
      final api = "${await bind.mainGetApiServer()}/api/peers";
      var uri0 = Uri.parse(api);
      final pageSize = 100;
      var total = 0;
      int current = 0;
      do {
        current += 1;
        var queryParameters = {
          'current': current.toString(),
          'pageSize': pageSize.toString(),
          'accessible': '',
          'status': '1',
        };
        var uri = Uri(
            scheme: uri0.scheme,
            host: uri0.host,
            path: uri0.path,
            port: uri0.port,
            queryParameters: queryParameters);
        final resp = await http.get(uri, headers: getHttpHeaders());
        _statusCode = resp.statusCode;

        Map<String, dynamic> json =
            _jsonDecodeResp(decode_http_response(resp), resp.statusCode);
        if (json.containsKey('error')) {
          throw json['error'];
        }
        if (resp.statusCode != 200) {
          throw 'HTTP ${resp.statusCode}';
        }
        if (json.containsKey('total')) {
          if (total == 0) total = json['total'];
          if (json.containsKey('data')) {
            final data = json['data'];
            if (data is List) {
              for (final p in data) {
                final peerPayload = PeerPayload.fromJson(p);
                final peer = PeerPayload.toPeer(peerPayload);
                int index = tmpPeers.indexWhere((e) => e.id == peer.id);
                if (index < 0) {
                  tmpPeers.add(peer);
                } else {
                  tmpPeers[index] = peer;
                }
              }
            }
          }
        }
      } while (current * pageSize < total);
      return true;
    } catch (err) {
      debugPrint('get accessible peers: $err');
      groupLoadError.value =
          '${translate('pull_group_failed_tip')}: ${translate(err.toString())}';
    }
    return false;
  }

  Map<String, dynamic> _jsonDecodeResp(String body, int statusCode) {
    try {
      Map<String, dynamic> json = jsonDecode(body);
      return json;
    } catch (e) {
      final err = body.isNotEmpty && body.length < 128 ? body : e.toString();
      if (statusCode != 200) {
        throw 'HTTP $statusCode, $err';
      }
      throw err;
    }
  }

  void _saveCache() {
    try {
      final map = (<String, dynamic>{
        "access_token": bind.mainGetLocalOption(key: 'access_token'),
        "device_groups": deviceGroups.map((e) => e.toGroupCacheJson()).toList(),
        "users": users.map((e) => e.toGroupCacheJson()).toList(),
        'peers': peers.map((e) => e.toGroupCacheJson()).toList()
      });
      bind.mainSaveGroup(json: jsonEncode(map));
    } catch (e) {
      debugPrint('group save:$e');
    }
  }

  Future<void> loadCache() async {
    try {
      if (_cacheLoadOnceFlag || groupLoading.value || initialized) return;
      _cacheLoadOnceFlag = true;
      final access_token = bind.mainGetLocalOption(key: 'access_token');
      if (access_token.isEmpty) return;
      final cache = await bind.mainLoadGroup();
      if (groupLoading.value) return;
      final data = jsonDecode(cache);
      if (data == null || data['access_token'] != access_token) return;
      deviceGroups.clear();
      users.clear();
      peers.clear();
      if (data['device_groups'] is List) {
        for (var u in data['device_groups']) {
          deviceGroups.add(DeviceGroupPayload.fromJson(u));
        }
      }
      if (data['users'] is List) {
        for (var u in data['users']) {
          users.add(UserPayload.fromJson(u));
        }
      }
      if (data['peers'] is List) {
        for (final peer in data['peers']) {
          peers.add(Peer.fromJson(peer));
        }
        _callbackPeerUpdate();
      }
    } catch (e) {
      debugPrint("load group cache: $e");
    }
  }

  reset() async {
    initialized = false;
    groupLoadError.value = '';
    deviceGroups.clear();
    users.clear();
    peers.clear();
    selectedAccessibleItemName.value = '';
    await bind.mainClearGroup();
  }

  void _callbackPeerUpdate() {
    for (var listener in _peerIdUpdateListeners.values) {
      listener();
    }
  }

  void addPeerUpdateListener(String key, VoidCallback listener) {
    _peerIdUpdateListeners[key] = listener;
  }

  void removePeerUpdateListener(String key) {
    _peerIdUpdateListeners.remove(key);
  }

  void _tryHandlePullError() {
    String errorMessage = groupLoadError.value;
    // The error message is "Retrieving accessible devices is disabled."
    if (errorMessage.toLowerCase().contains('disabled')) {
      users.clear();
      peers.clear();
      deviceGroups.clear();
    }
  }
}
