import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'package:cw_core/node.dart';
import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart';
import 'package:cw_monero/api/convert_utf8_to_string.dart';
import 'package:cw_monero/api/signatures.dart';
import 'package:cw_monero/api/types.dart';
import 'package:cw_monero/api/monero_api.dart';
import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart';
import 'package:cw_monero/api/exceptions/wallet_creation_exception.dart';
import 'package:cw_monero/api/exceptions/wallet_restore_from_keys_exception.dart';
import 'package:cw_monero/api/exceptions/wallet_restore_from_seed_exception.dart';
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart' as ioc;

final createWalletNative =
    moneroApi.lookup<NativeFunction<create_wallet>>('create_wallet').asFunction<CreateWallet>();

final restoreWalletFromSeedNative = moneroApi
    .lookup<NativeFunction<restore_wallet_from_seed>>('restore_wallet_from_seed')
    .asFunction<RestoreWalletFromSeed>();

final restoreWalletFromKeysNative = moneroApi
    .lookup<NativeFunction<restore_wallet_from_keys>>('restore_wallet_from_keys')
    .asFunction<RestoreWalletFromKeys>();

final isWalletExistNative = moneroApi
    .lookup<NativeFunction<is_wallet_exist>>('is_wallet_exist')
    .asFunction<IsWalletExist>();

final loadWalletNative =
    moneroApi.lookup<NativeFunction<load_wallet>>('load_wallet').asFunction<LoadWallet>();

final errorStringNative =
    moneroApi.lookup<NativeFunction<error_string>>('error_string').asFunction<ErrorString>();

void createWalletSync(
    {required String path, required String password, required String language, int nettype = 0}) {
  final pathPointer = path.toNativeUtf8();
  final passwordPointer = password.toNativeUtf8();
  final languagePointer = language.toNativeUtf8();
  final errorMessagePointer = ''.toNativeUtf8();
  final isWalletCreated = createWalletNative(
          pathPointer, passwordPointer, languagePointer, nettype, errorMessagePointer) !=
      0;

  calloc.free(pathPointer);
  calloc.free(passwordPointer);
  calloc.free(languagePointer);

  if (!isWalletCreated) {
    throw WalletCreationException(message: convertUTF8ToString(pointer: errorMessagePointer));
  }

  // setupNodeSync(address: "node.moneroworld.com:18089");
}

bool isWalletExistSync({required String path}) {
  final pathPointer = path.toNativeUtf8();
  final isExist = isWalletExistNative(pathPointer) != 0;

  calloc.free(pathPointer);

  return isExist;
}

void restoreWalletFromSeedSync(
    {required String path,
    required String password,
    required String seed,
    int nettype = 0,
    int restoreHeight = 0}) {
  final pathPointer = path.toNativeUtf8();
  final passwordPointer = password.toNativeUtf8();
  final seedPointer = seed.toNativeUtf8();
  final errorMessagePointer = ''.toNativeUtf8();
  final isWalletRestored = restoreWalletFromSeedNative(
          pathPointer, passwordPointer, seedPointer, nettype, restoreHeight, errorMessagePointer) !=
      0;

  calloc.free(pathPointer);
  calloc.free(passwordPointer);
  calloc.free(seedPointer);

  if (!isWalletRestored) {
    throw WalletRestoreFromSeedException(
        message: convertUTF8ToString(pointer: errorMessagePointer));
  }
}

void restoreWalletFromKeysSync(
    {required String path,
    required String password,
    required String language,
    required String address,
    required String viewKey,
    required String spendKey,
    int nettype = 0,
    int restoreHeight = 0}) {
  final pathPointer = path.toNativeUtf8();
  final passwordPointer = password.toNativeUtf8();
  final languagePointer = language.toNativeUtf8();
  final addressPointer = address.toNativeUtf8();
  final viewKeyPointer = viewKey.toNativeUtf8();
  final spendKeyPointer = spendKey.toNativeUtf8();
  final errorMessagePointer = ''.toNativeUtf8();
  final isWalletRestored = restoreWalletFromKeysNative(
          pathPointer,
          passwordPointer,
          languagePointer,
          addressPointer,
          viewKeyPointer,
          spendKeyPointer,
          nettype,
          restoreHeight,
          errorMessagePointer) !=
      0;

  calloc.free(pathPointer);
  calloc.free(passwordPointer);
  calloc.free(languagePointer);
  calloc.free(addressPointer);
  calloc.free(viewKeyPointer);
  calloc.free(spendKeyPointer);

  if (!isWalletRestored) {
    throw WalletRestoreFromKeysException(
        message: convertUTF8ToString(pointer: errorMessagePointer));
  }
}

void loadWallet({required String path, required String password, int nettype = 0}) {
  final pathPointer = path.toNativeUtf8();
  final passwordPointer = password.toNativeUtf8();
  final loaded = loadWalletNative(pathPointer, passwordPointer, nettype) != 0;
  calloc.free(pathPointer);
  calloc.free(passwordPointer);

  if (!loaded) {
    throw WalletOpeningException(message: convertUTF8ToString(pointer: errorStringNative()));
  }
}

void _createWallet(Map<String, dynamic> args) {
  final path = args['path'] as String;
  final password = args['password'] as String;
  final language = args['language'] as String;

  createWalletSync(path: path, password: password, language: language);
}

void _restoreFromSeed(Map<String, dynamic> args) {
  final path = args['path'] as String;
  final password = args['password'] as String;
  final seed = args['seed'] as String;
  final restoreHeight = args['restoreHeight'] as int;

  restoreWalletFromSeedSync(
      path: path, password: password, seed: seed, restoreHeight: restoreHeight);
}

void _restoreFromKeys(Map<String, dynamic> args) {
  final path = args['path'] as String;
  final password = args['password'] as String;
  final language = args['language'] as String;
  final restoreHeight = args['restoreHeight'] as int;
  final address = args['address'] as String;
  final viewKey = args['viewKey'] as String;
  final spendKey = args['spendKey'] as String;

  restoreWalletFromKeysSync(
      path: path,
      password: password,
      language: language,
      restoreHeight: restoreHeight,
      address: address,
      viewKey: viewKey,
      spendKey: spendKey);
}

Future<void> _openWallet(Map<String, String> args) async =>
    loadWallet(path: args['path'] as String, password: args['password'] as String);

bool _isWalletExist(String path) => isWalletExistSync(path: path);

void openWallet({required String path, required String password, int nettype = 0}) async =>
    loadWallet(path: path, password: password, nettype: nettype);

Future<void> openWalletAsync(Map<String, String> args) async => compute(_openWallet, args);

Future<void> createWallet(
        {required String path,
        required String password,
        required String language,
        int nettype = 0}) async =>
    compute(_createWallet,
        {'path': path, 'password': password, 'language': language, 'nettype': nettype});

Future<void> restoreFromSeed(
        {required String path,
        required String password,
        required String seed,
        int nettype = 0,
        int restoreHeight = 0}) async =>
    compute<Map<String, Object>, void>(_restoreFromSeed, {
      'path': path,
      'password': password,
      'seed': seed,
      'nettype': nettype,
      'restoreHeight': restoreHeight
    });

Future<void> restoreFromKeys(
        {required String path,
        required String password,
        required String language,
        required String address,
        required String viewKey,
        required String spendKey,
        int nettype = 0,
        int restoreHeight = 0}) async =>
    compute<Map<String, Object>, void>(_restoreFromKeys, {
      'path': path,
      'password': password,
      'language': language,
      'address': address,
      'viewKey': viewKey,
      'spendKey': spendKey,
      'nettype': nettype,
      'restoreHeight': restoreHeight
    });

Future<Map<String, dynamic>> sweepFundsToNewWallet({
  required Node node,
  required String address,
  required String paymentId,
  List subaddressIndices = const [],
  int accountIndex = 0,
  int priority = 0,
  int ringSize = 0,
  int outputs = 0,
  int unlockTime = 1,
  bool getTxKeys = false,
  bool doNotRelay = false,
  bool getTxHex = false,
  bool getTxMetadata = false,
  int belowAmount = 0,
}) async {
  final uri = Uri.http(node.uriRaw, '');
  final path = '/json_rpc';
  final rpcUri = node.isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path);
  final realm = 'monero-rpc';
  final body = {
    "method": "sweep_all",
    "jsonrpc": "2.0",
    "id": "0",
    "params": {
      "address": address,
      "account_index": accountIndex,
      "subaddr_indices": subaddressIndices,
      "priority": priority,
      "ring_size": ringSize,
      "outputs": outputs,
      "unlock_time": unlockTime,
      "payment_id": paymentId,
      "get_tx_keys": getTxKeys,
      "below_amount": belowAmount,
      "do_not_relay": doNotRelay,
      "get_tx_hex": getTxHex,
      "get_tx_metadata": getTxMetadata,
    },
  };

  try {
    final authenticatingClient = HttpClient();

    authenticatingClient.addCredentials(
      rpcUri,
      realm,
      HttpClientDigestCredentials(node.login ?? '', node.password ?? ''),
    );

    final http.Client client = ioc.IOClient(authenticatingClient);

    final response = await client.post(
      rpcUri,
      headers: {'Content-Type': 'application/json'},
      body: json.encode(body),
    );

    client.close();
    final resBody = json.decode(response.body) as Map<String, dynamic>;

    return resBody;
  } catch (e) {
    print(e);
    throw Exception(e);
  }
}

Future<bool> isWalletExist({required String path}) => compute(_isWalletExist, path);