// ignore_for_file: depend_on_referenced_packages

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

import 'package:dart_framework/utils/system_util.dart';
import 'package:file_hasher/file_hasher.dart';
import 'package:oto/oto/application.dart';
import 'package:oto/oto/commands/command.dart';
import 'package:oto/oto/data/command_data.dart';
import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart' as crypto;

class FileDiffCheck extends Command {
  @override
  Future execute(DataCommand command) async {
    var data = DataFileDiffCheck.fromJson(getParam(command));
    var exitCode = 0;
    var diff = false;

    var currentDir = Directory(workspace);
    var fileName = getUniqueFileName(data.paths.join(';'));

    var tempDir = Directory.systemTemp;
    var cacheFolder =
        Directory('${tempDir.path}/oto/${getUniqueFileName(currentDir.path)}');
    if (!cacheFolder.existsSync()) {
      cacheFolder.createSync(recursive: true);
    }
    var cacheFile = File('${cacheFolder.path}/$fileName');
    var paths = data.paths;
    Application.log('Cache Path: ${cacheFile.path}, Exist: ${cacheFile.existsSync()}');

    if (paths.isEmpty) {
      throw Exception('The paths is Empty.');
    } else {
      if (cacheFile.existsSync()) {
        var cachedMap =
            jsonDecode(cacheFile.readAsStringSync()) as Map<String, dynamic>;
        var filesHashMap = await getFilesHashMap(paths);
        if (cachedMap.length == filesHashMap.length) {
          for (var pair in filesHashMap.entries) {
            if (cachedMap.containsKey(pair.key)) {
              var cachedHash = cachedMap[pair.key];
              if (cachedHash != pair.value) {
                await saveDiffData(cacheFile, paths);
                diff = true;
                break;
              }
            } else {
              await saveDiffData(cacheFile, paths);
              diff = true;
              break;
            }
          }
        } else {
          await saveDiffData(cacheFile, paths);
          diff = true;
        }
      } else {
        await saveDiffData(cacheFile, paths);
        diff = true;
      }
    }

    await setProperty(data.setDiff, diff);
    return await complete(exitCode, command);
  }

  Future saveDiffData(File cacheFile, List<String> paths) async {
    var cachedMap = await getFilesHashMap(paths);
    var jsonString = jsonEncode(cachedMap);
    await cacheFile.writeAsString(jsonString);
    return simpleFuture;
  }

  String getUniqueFileName(String seed) {
    var fileName = generateMd5(seed);
    if (fileName.length > 150) {
      fileName = fileName.substring(0, 150);
    }
    return fileName;
  }

  Future<Map<String, dynamic>> getFilesHashMap(List<String> paths) async {
    var c = Completer<Map<String, dynamic>>();
    var cachedMap = <String, dynamic>{};
    for (var path in paths) {
      var file = File(path);
      var type = file.statSync().type;
      switch (type) {
        case FileSystemEntityType.directory:
          var dir = Directory(path);
          var files = dir.listSync();
          for (var child in files) {
            if (child is File) {
              await setCachedMapHash(cachedMap, File(child.path));
            }
          }
          break;
        case FileSystemEntityType.file:
          await setCachedMapHash(cachedMap, file);
          break;
        default:
          throw Exception('File or Directory are not exists: $path');
      }
    }
    c.complete(cachedMap);
    return c.future;
  }

  Future setCachedMapHash(Map<String, dynamic> cachedMap, File file) async {
    cachedMap[file.path] = await FileHasher.hash(file);
    return simpleFuture;
  }

  ///Generate MD5 hash
  String generateMd5(String data) {
    var content = Utf8Encoder().convert(data);
    var md5 = crypto.md5;
    var digest = md5.convert(content);
    return hex.encode(digest.bytes);
  }
}

void registerFileDiffCheckCommand() {
  Command.register(
    CommandType.FileDiffCheck,
    () => FileDiffCheck(),
    spec: const CommandSpec(
      type: CommandType.FileDiffCheck,
      category: 'file',
      dataModel: 'DataFileDiffCheck',
      samplePath: 'assets/yaml/sample/22_file_diff.yaml',
    ),
  );
}
