import 'dart:convert';
import 'dart:io';

import 'package:archive/archive_io.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
import 'package:intl/intl.dart';
import 'package:path_provider/path_provider.dart';
import 'package:prasule/api/wallet.dart';
import 'package:prasule/main.dart';

/// Used for [Wallet]-managing operations
class WalletManager {
  /// Returns a list of all [Wallet]s
  static Future<List<Wallet>> listWallets() async {
    final path =
        Directory("${(await getApplicationDocumentsDirectory()).path}/wallets");
    if (!path.existsSync()) {
      path.createSync();
    }
    final wallets = <Wallet>[];
    for (final w
        in path.listSync().map((e) => e.path.split("/").last).toList()) {
      try {
        wallets.add(await loadWallet(w));
      } catch (e) {
        logger.e(e);
        // TODO: do something with unreadable wallets
      }
    }
    return wallets;
  }

  /// Deletes all [Wallet]s
  static Future<void> deleteAllData() async {
    final path =
        Directory("${(await getApplicationDocumentsDirectory()).path}/wallets");
    if (!path.existsSync()) {
      return;
    }

    for (final entry in path.listSync()) {
      logger.d("Deleting ${entry.path}");
      entry.deleteSync();
    }
  }

  /// Creates a ZIP archive from all wallets
  static Future<void> exportAllWallets() async {
    if (kIsWeb) {
      // TODO
      return;
    }

    final archive = Archive();
    for (final w in Directory(
      "${(await getApplicationDocumentsDirectory()).path}/wallets",
    ).listSync()) {
      if (w is! File) continue;
      logger.i("Zipping ${w.path.split("/").last}");
      final wf = w;
      archive.addFile(
        ArchiveFile.stream(
          wf.path.split("/").last,
          wf.lengthSync(),
          InputFileStream(wf.path),
        ),
      );
    }
    if (!await FlutterFileDialog.isPickDirectorySupported()) {
      File(
        "${(await getApplicationDocumentsDirectory()).path}/export_${DateFormat("dd_MM_yyyy").format(DateTime.now())}.zip",
      ).writeAsBytesSync(ZipEncoder().encode(archive) ?? []);
      return;
    }
    final dir = await FlutterFileDialog.pickDirectory();
    if (dir == null) return;
    await FlutterFileDialog.saveFileToDirectory(
      directory: dir,
      data: Uint8List.fromList(ZipEncoder().encode(archive) ?? []),
      fileName: "export_${DateFormat("dd_MM_yyyy").format(DateTime.now())}.zip",
      mimeType: "application/zip",
    );
  }

  /// Exports a single [Wallet]
  static Future<void> exportWallet({Wallet? wallet, String? name}) async {
    if (wallet == null && name == null) {
      throw Exception("You need to specify either a wallet or a name");
    }
    final n = name ?? wallet!.name;

    if (!await FlutterFileDialog.isPickDirectorySupported()) {
      File("${(await getApplicationDocumentsDirectory()).path}/wallets/$n")
          .copySync(
        "${await getApplicationDocumentsDirectory()}/export_${n.replaceAll(RegExp('[|\\?*<":>+[]/\' ]+'), '_')}_${DateFormat("dd_MM_yyyy").format(DateTime.now())}.json",
      );
      return;
    }
    final dir = await FlutterFileDialog.pickDirectory();
    if (dir == null) return;
    await FlutterFileDialog.saveFileToDirectory(
      directory: dir,
      data:
          File("${(await getApplicationDocumentsDirectory()).path}/wallets/$n")
              .readAsBytesSync(),
      fileName:
          "export_${n.replaceAll(RegExp('[|\\?*<":>+[]/\' ]+'), '_')}_${DateFormat("dd_MM_yyyy").format(DateTime.now())}.json",
      mimeType: "application/json",
    );
  }

  /// Import a single wallet
  static Future<void> importWallet({String? data}) async {
    var d = data ?? "";
    if (data == null) {
      final filePath = await FlutterFileDialog.pickFile(
        params: const OpenFileDialogParams(
          mimeTypesFilter: ["application/json"],
          fileExtensionsFilter: ["json"],
        ),
      );
      if (filePath == null) return;
      d = File(filePath).readAsStringSync();
    }
    final w = Wallet.fromJson(jsonDecode(d) as Map<String, dynamic>);
    if (await WalletManager.exists(w.name)) {
      throw Exception("Wallet already exists!");
    }
    await WalletManager.saveWallet(
      w,
    );
  }

  /// Imports wallets from a ZIP archive
  static Future<void> importArchive() async {
    final filePath = await FlutterFileDialog.pickFile(
      params: const OpenFileDialogParams(
        mimeTypesFilter: ["application/zip"],
        fileExtensionsFilter: ["zip"],
      ),
    );
    if (filePath == null) return;
    if (kIsWeb) {
      // TODO
      return;
    }
    final temp = Directory("${(await getTemporaryDirectory()).path}/data");
    if (temp.existsSync()) {
      temp.deleteSync();
    }
    temp.createSync(recursive: true);
    final archive = ZipDecoder().decodeBuffer(InputFileStream(filePath));
    for (final file in archive.files) {
      if (!file.isFile) {
        logger.d(file.name);
        continue;
      }
      file.writeContent(OutputFileStream("${temp.path}/${file.name}"));
    }
    for (final e in temp.listSync()) {
      logger.d(e.path);
      if (e is! File) continue;
      await importWallet(data: e.readAsStringSync());
    }
  }

  /// Loads and returns a single [Wallet] by name
  static Future<Wallet> loadWallet(String name) async {
    final path =
        Directory("${(await getApplicationDocumentsDirectory()).path}/wallets");
    final wallet = File("${path.path}/$name");
    if (!path.existsSync()) {
      path.createSync();
    }
    if (!wallet.existsSync()) {
      return Future.error("Wallet does not exist");
    }
    return Wallet.fromJson(
      jsonDecode(wallet.readAsStringSync()) as Map<String, dynamic>,
    );
  }

  /// Converts [Wallet] to JSON and saves it to AppData
  static Future<bool> saveWallet(Wallet w) async {
    final path =
        Directory("${(await getApplicationDocumentsDirectory()).path}/wallets");
    final wallet = File("${path.path}/${w.name}");
    if (!path.existsSync()) {
      path.createSync();
    }
    // if (!wallet.existsSync()) return false;
    wallet.writeAsStringSync(jsonEncode(w.toJson()));
    return true;
  }

  /// Deletes the corresponding [Wallet] file
  static Future<void> deleteWallet(Wallet w) async {
    final path =
        Directory("${(await getApplicationDocumentsDirectory()).path}/wallets");
    File("${path.path}/${w.name}").deleteSync();
  }

  /// Checks if the wallet exists
  static Future<bool> exists(String name) async {
    return File(
      "${(await getApplicationDocumentsDirectory()).path}/wallets/$name",
    ).existsSync();
  }
}
