/* 
 * This file is part of Stack Wallet.
 * 
 * Copyright (c) 2023 Cypher Stack
 * All Rights Reserved.
 * The code is distributed under GPLv3 license, see LICENSE file for details.
 * Generated by Cypher Stack on 2023-05-26
 *
 */

import 'dart:async';

import 'package:event_bus/event_bus.dart';
import 'package:flutter/material.dart';
import 'package:stackwallet/db/hive/db.dart';
import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
import 'package:stackwallet/models/models.dart';
import 'package:stackwallet/services/coins/coin_service.dart';
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/services/mixins/coin_control_interface.dart';
import 'package:stackwallet/services/mixins/ordinals_interface.dart';
import 'package:stackwallet/services/mixins/fusion_interface.dart';
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
import 'package:stackwallet/services/mixins/xpubable.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/logger.dart';

class Manager with ChangeNotifier {
  final CoinServiceAPI _currentWallet;
  StreamSubscription<dynamic>? _backgroundRefreshListener;
  StreamSubscription<dynamic>? _nodeStatusListener;

  /// optional eventbus parameter for testing only
  Manager(this._currentWallet, [EventBus? globalEventBusForTesting]) {
    final bus = globalEventBusForTesting ?? GlobalEventBus.instance;
    _backgroundRefreshListener = bus.on<UpdatedInBackgroundEvent>().listen(
      (event) async {
        if (event.walletId == walletId) {
          notifyListeners();
          Logging.instance.log(
              "UpdatedInBackgroundEvent activated notifyListeners() in Manager instance $hashCode $walletName with: ${event.message}",
              level: LogLevel.Info);
        }
      },
    );

    _nodeStatusListener = bus.on<NodeConnectionStatusChangedEvent>().listen(
      (event) async {
        if (event.walletId == walletId) {
          notifyListeners();
          Logging.instance.log(
              "NodeConnectionStatusChangedEvent activated notifyListeners() in Manager instance $hashCode $walletName with: ${event.newStatus}",
              level: LogLevel.Info);
        }
      },
    );
  }

  bool _isActiveWallet = false;
  bool get isActiveWallet => _isActiveWallet;
  set isActiveWallet(bool isActive) {
    if (_isActiveWallet != isActive) {
      _isActiveWallet = isActive;
      _currentWallet.onIsActiveWalletChanged?.call(isActive);
      //todo: check if print needed
      debugPrint(
          "wallet ID: ${_currentWallet.walletId} is active set to: $isActive");
    } else {
      debugPrint("wallet ID: ${_currentWallet.walletId} is still: $isActive");
    }
  }

  Future<void> updateNode(bool shouldRefresh) async {
    await _currentWallet.updateNode(shouldRefresh);
  }

  CoinServiceAPI get wallet => _currentWallet;

  bool get hasBackgroundRefreshListener => _backgroundRefreshListener != null;

  Coin get coin => _currentWallet.coin;

  bool get isRefreshing => _currentWallet.isRefreshing;

  bool get shouldAutoSync => _currentWallet.shouldAutoSync;
  set shouldAutoSync(bool shouldAutoSync) =>
      _currentWallet.shouldAutoSync = shouldAutoSync;

  bool get isFavorite => _currentWallet.isFavorite;

  set isFavorite(bool markFavorite) {
    _currentWallet.isFavorite = markFavorite;
    notifyListeners();
  }

  @override
  dispose() async {
    await exitCurrentWallet();
    super.dispose();
  }

  Future<Map<String, dynamic>> prepareSend({
    required String address,
    required Amount amount,
    Map<String, dynamic>? args,
  }) async {
    try {
      final txInfo = await _currentWallet.prepareSend(
        address: address,
        amount: amount,
        args: args,
      );
      // notifyListeners();
      return txInfo;
    } catch (e) {
      // rethrow to pass error in alert
      rethrow;
    }
  }

  Future<String> confirmSend({required Map<String, dynamic> txData}) async {
    try {
      final txid = await _currentWallet.confirmSend(txData: txData);

      try {
        txData["txid"] = txid;
        await _currentWallet.updateSentCachedTxData(txData);
      } catch (e, s) {
        // do not rethrow as that would get handled as a send failure further up
        // also this is not critical code and transaction should show up on \
        // refresh regardless
        Logging.instance.log("$e\n$s", level: LogLevel.Warning);
      }

      notifyListeners();
      return txid;
    } catch (e) {
      // rethrow to pass error in alert
      rethrow;
    }
  }

  Future<FeeObject> get fees => _currentWallet.fees;
  Future<int> get maxFee => _currentWallet.maxFee;

  Future<String> get currentReceivingAddress =>
      _currentWallet.currentReceivingAddress;

  Balance get balance => _currentWallet.balance;

  Future<List<isar_models.Transaction>> get transactions =>
      _currentWallet.transactions;
  Future<List<isar_models.UTXO>> get utxos => _currentWallet.utxos;

  Future<void> refresh() async {
    await _currentWallet.refresh();
    notifyListeners();
  }

  // setter for updating on rename
  set walletName(String newName) {
    if (newName != _currentWallet.walletName) {
      _currentWallet.walletName = newName;
      notifyListeners();
    }
  }

  String get walletName => _currentWallet.walletName;
  String get walletId => _currentWallet.walletId;

  bool validateAddress(String address) =>
      _currentWallet.validateAddress(address);

  Future<List<String>> get mnemonic => _currentWallet.mnemonic;
  Future<String?> get mnemonicPassphrase => _currentWallet.mnemonicPassphrase;

  Future<bool> testNetworkConnection() =>
      _currentWallet.testNetworkConnection();

  Future<void> initializeNew() => _currentWallet.initializeNew();
  Future<void> initializeExisting() => _currentWallet.initializeExisting();
  Future<void> recoverFromMnemonic({
    required String mnemonic,
    String? mnemonicPassphrase,
    required int maxUnusedAddressGap,
    required int maxNumberOfIndexesToCheck,
    required int height,
  }) async {
    try {
      await _currentWallet.recoverFromMnemonic(
        mnemonic: mnemonic,
        mnemonicPassphrase: mnemonicPassphrase,
        maxUnusedAddressGap: maxUnusedAddressGap,
        maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck,
        height: height,
      );
    } catch (e, s) {
      Logging.instance.log("e: $e, S: $s", level: LogLevel.Error);
      rethrow;
    }
  }

  Future<void> exitCurrentWallet() async {
    final name = _currentWallet.walletName;
    final id = _currentWallet.walletId;
    await _backgroundRefreshListener?.cancel();
    _backgroundRefreshListener = null;
    await _nodeStatusListener?.cancel();
    _nodeStatusListener = null;
    await _currentWallet.exit();
    Logging.instance.log("manager.exitCurrentWallet completed for $id $name",
        level: LogLevel.Info);
  }

  Future<void> fullRescan(
      int maxUnusedAddressGap, int maxNumberOfIndexesToCheck) async {
    try {
      await _currentWallet.fullRescan(
          maxUnusedAddressGap, maxNumberOfIndexesToCheck);
    } catch (e) {
      rethrow;
    }
  }

  bool get isConnected => _currentWallet.isConnected;

  Future<Amount> estimateFeeFor(Amount amount, int feeRate) async {
    return _currentWallet.estimateFeeFor(amount, feeRate);
  }

  Future<bool> generateNewAddress() async {
    final success = await _currentWallet.generateNewAddress();
    if (success) {
      notifyListeners();
    }
    return success;
  }

  int get currentHeight => _currentWallet.storedChainHeight;

  bool get hasPaynymSupport => _currentWallet is PaynymWalletInterface;

  bool get hasCoinControlSupport => _currentWallet is CoinControlInterface;

  bool get hasOrdinalsSupport => _currentWallet is OrdinalsInterface;

  bool get hasTokenSupport => _currentWallet.coin == Coin.ethereum;

  bool get hasWhirlpoolSupport => false;

  bool get hasFusionSupport => _currentWallet is FusionInterface;

  int get rescanOnOpenVersion =>
      DB.instance.get<dynamic>(
        boxName: DB.boxNameDBInfo,
        key: "rescan_on_open_$walletId",
      ) as int? ??
      0;

  Future<void> resetRescanOnOpen() async {
    await DB.instance.delete<dynamic>(
      key: "rescan_on_open_$walletId",
      boxName: DB.boxNameDBInfo,
    );
  }

  // TODO: re enable once xpubs have been redone
  bool get hasXPub => false; //_currentWallet is XPubAble;

  Future<String> get xpub async {
    if (!hasXPub) {
      throw Exception(
          "Tried to read xpub from wallet that does not support it");
    }
    return (_currentWallet as XPubAble).xpub;
  }
}