// ignore_for_file: avoid_init_to_null

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

import 'package:dart_framework/log/log.dart';
import 'package:dart_framework/utils/system_util.dart';

typedef LogHandler = Function(String, LogType);

class ProcessData {
  int? exitCode;
  late Process _process;
  late File file;
  late StringBuffer stdout;
  late StringBuffer stderr;
  final bool _enableStdout;
  final bool _enableStderr;
  final Completer _completer = Completer();
  LogHandler? _logHandler;
  bool _completeStdout = false;
  bool _completeStderr = false;
  bool _completeExitCode = false;
  late Converter<List<int>, String> _decoder;

  // ignore: no_leading_underscores_for_local_identifiers
  ProcessData(this._enableStdout, this._enableStderr,
      {LogHandler? logHandler, Converter<List<int>, String>? decoder}) {
    _logHandler = logHandler;
    _decoder =
        Platform.isWindows ? SystemEncoding().decoder : decoder ?? utf8.decoder;
  }

  bool get isSuccess {
    if (Platform.isWindows) return exitCode == 1;
    return exitCode == 0;
  }

  List<String> get stdoutArr {
    return getBufferToArray(stdout);
  }

  List<String> get stderrArr {
    return getBufferToArray(stderr);
  }

  List<String> getBufferToArray(StringBuffer buffer) {
    var list = <String>[];
    var arr = buffer.toString().split('\n');
    var length = arr.length;
    for (var i = 0; i < length; ++i) {
      if (arr[i].isNotEmpty) {
        list.add(arr[i]);
      }
    }
    return list;
  }

  Process get process {
    return _process;
  }

  set process(Process value) {
    _process = value;
    stdout = StringBuffer();
    stderr = StringBuffer();
    _process.stdout.transform(_decoder).transform(LineSplitter()).listen(
        (event) {
      stdout.writeln(event);
      if (_enableStdout) {
        log(event, LogType.verbose);
      }
    }, onDone: () {
      _completeStdout = true;
      _checkComplete();
    });
    _process.stderr.transform(_decoder).transform(LineSplitter()).listen(
        (event) {
      stderr.writeln(event);
      if (_enableStderr) {
        log('[Error] $event', LogType.error);
      }
    }, onDone: () {
      _completeStderr = true;
      _checkComplete();
    });
    _checkExitCode();
  }

  void log(String message, LogType logType) {
    if (_logHandler == null) {
      print(message);
    } else {
      _logHandler!(message, logType);
    }
  }

  void terminate() {
    _process.kill();
    _deleteFile();
  }

  void _deleteFile() async {
    if (file.existsSync()) {
      file.deleteSync();
    }
  }

  void _checkExitCode() async {
    exitCode = await _process.exitCode;
    _completeExitCode = true;
    _checkComplete();
  }

  void _checkComplete() {
    if (_completeExitCode && _completeStdout && _completeStderr) {
      _deleteFile();
      _completer.complete();
    }
  }

  Future waitForExit() async {
    return _completer.future;
  }
}

class ProcessExecutor {
  static bool _initedPowershell = false;
  static Future initPowershell() async {
    if (!_initedPowershell) {
      var result = await Process.run(
        'powershell',
        [
          '-Command',
          'Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned'
        ],
        runInShell: true,
      );

      _initedPowershell = true;

      if (result.stderr.toString().trim().isNotEmpty) {
        print('[Powershell Grant] Error: ${result.stderr}');
      }
    }
    return simpleFuture;
  }

  static Future<ProcessData> startExe(String exe, List<String> args,
      {String? workspace,
      bool? printStdout,
      bool? printStderr,
      LogHandler? logHandler}) async {
    var arg = '$exe ${args.join(' ')}';
    var printStdout_ = printStdout ?? true;
    var printStderr_ = printStderr ?? true;
    var processData =
        ProcessData(printStdout_, printStderr_, logHandler: logHandler);
    if (Platform.isMacOS || Platform.isWindows) {
      workspace = workspace ?? Directory.systemTemp.path;
      if (printStdout_) {
        logHandler?.call('Execute Shell: $arg', LogType.verbose);
      }
      try {
        processData.process =
            await Process.start(exe, args, workingDirectory: workspace);
      } on Exception catch (e) {
        logHandler?.call(e.toString(), LogType.error);
      }
    } else {
      logHandler ?? ('This platform not support process executor.');
    }
    return dataFutrue(processData);
  }

  static Future<ProcessData> start(StringBuffer shell,
      {Converter<List<int>, String>? decoder,
      String? workspace,
      bool? printStdout,
      bool? printStderr,
      LogHandler? logHandler}) async {
    var c = Completer<ProcessData>();
    try {
      var printStdout_ = printStdout ?? true;
      var printStderr_ = printStderr ?? true;
      var processData = ProcessData(printStdout_, printStderr_,
          logHandler: logHandler, decoder: decoder);
      if (Platform.isMacOS || Platform.isLinux) {
        workspace = workspace ?? Directory.systemTemp.path;
        var file = File('$workspace/$tempFileName');
        processData.file = file;
        if (printStdout_) {
          logHandler?.call
              (
                'Execute Shell: ${file.path} \n ${shell.toString()}',
                LogType.verbose
              );
        }
        if (file.existsSync()) {
          file.deleteSync();
        }
        file.createSync();
        file.writeAsStringSync(shell.toString(), flush: true);
        try {
          var shellExe = Platform.isMacOS ? 'zsh' : 'bash';
          processData.process = await Process.start(shellExe, [file.path],
              workingDirectory: workspace);
        } on Exception catch (e) {
          logHandler?.call(e.toString(), LogType.error);
        }
      } else if (Platform.isWindows) {
        workspace = workspace ?? Directory.systemTemp.path;
        var file = File('$workspace/$tempFileName');
        processData.file = file;
        if (printStdout_) {
          logHandler?.call
              (
                'Execute Shell: ${file.path} \n ${shell.toString()}',
                LogType.verbose
              );
        }
        if (await file.exists()) {
          await file.delete();
        }
        file.createSync();
        file.writeAsStringSync(shell.toString(), flush: true);

        await initPowershell();
        try {
          processData.process = await Process.start(
              'powershell', ['-NoProfile', '-File', file.absolute.path]);
        } on Exception catch (e) {
          logHandler?.call(e.toString(), LogType.error);
        }
      } else {
        logHandler?.call
            ('This platform not support process executor.', LogType.error);
      }
      c.complete(processData);
      await processData.waitForExit();
    } on Exception catch (e) {
      logHandler?.call
          ('General Exception in ProcessExecutor.start: $e', LogType.error);
    }
    return c.future;
  }

  static Future<ProcessResult> run(StringBuffer shell,
      {String? workspace,
      bool printStdout = true,
      bool printStderr = true,
      Converter<List<int>, String>? decoder,
      LogHandler? logHandler}) async {
    var c = Completer<ProcessResult>();
    workspace = workspace ?? Directory.systemTemp.path;
    var file = File('$workspace/$tempFileName');
    if (printStdout) {
      logHandler?.call
          (
            '---------------------------------------------------',
            LogType.verbose
          );
      logHandler?.call
          (
            'Shell Path: ${file.path} \nExecute Shell: ${shell.toString()}',
            LogType.verbose
          );
    }
    ProcessResult? processResult;
    if (Platform.isMacOS || Platform.isLinux) {
      file.createSync();
      file.writeAsStringSync(shell.toString());
      try {
        var shellExe = Platform.isMacOS ? 'zsh' : 'bash';
        processResult = await Process.run(shellExe, [file.path],
            workingDirectory: workspace);
      } on Exception catch (e) {
        logHandler?.call(e.toString(), LogType.error);
      } finally {
        file.deleteSync();
      }
      c.complete(processResult);
    } else if (Platform.isWindows) {
      file.createSync();
      file.writeAsStringSync(shell.toString());

      print(file.absolute.path);

      await initPowershell();

      processResult = await Process.run(
          'powershell', // PowerShell 실행
          ['-NoProfile', '-File', file.absolute.path],
          runInShell: true);

      file.deleteSync();
      c.complete(processResult);
    } else {
      logHandler?.call
          ('This platform not support process executor.', LogType.error);
      c.complete(null);
    }
    if (processResult != null) {
      if (printStdout) {
        var stdout = processResult.stdout.toString();
        if (decoder != null) {
          stdout = decoder.convert(stdout.codeUnits);
        }
        logHandler?.call
            (
              // ignore: prefer_interpolation_to_compose_strings
              '[ProcessExecutor] Result: $stdout',
              LogType.verbose
            );
      }
      if (printStderr) {
        var stderr = processResult.stderr.toString();
        if (decoder != null) {
          stderr = decoder.convert(stderr.codeUnits);
        }
        logHandler?.call
            (
              // ignore: prefer_interpolation_to_compose_strings
              '[ProcessExecutor] Exit Code: ${processResult.exitCode}\nError: $stderr',
              LogType.verbose
            );
      }
    }
    return c.future;
  }

  static String get tempFileName {
    if (Platform.isMacOS || Platform.isLinux) {
      return 'temp_${DateTime.now().microsecondsSinceEpoch}.sh';
    } else {
      return 'temp_${DateTime.now().microsecondsSinceEpoch}.ps1';
    }
  }
}

class NetStatData {
  late String type;
  late String from;
  late String to;
  late String state;
  late int pid;

  NetStatData(List<String> categories) {
    var count = 0;
    var length = categories.length;
    for (var i = 0; i < length; ++i) {
      var category = categories[i].replaceAll(' ', '');
      if (category.isNotEmpty) {
        switch (count) {
          case 0:
            type = category;
            break;
          case 1:
            from = category;
            break;
          case 2:
            to = category;
            break;
          case 3:
            state = category;
            break;
          case 4:
            var value = int.tryParse(category);
            value ??= -1;
            pid = value;
            break;
        }
        count++;
      }
    }
  }
}

bool _removedBat = false;
Future removeTempBat() async {
  var c = Completer();
  if (!_removedBat) {
    _removedBat = true;
    var reg = RegExp(r'(temp_bat_)([0-9_]+)(.bat)');
    var list = Directory.current.listSync();
    for (var item in list) {
      var match = reg.stringMatch(item.path);
      if (match != null) {
        await item.delete();
      }
    }
    await Future.delayed(Duration(milliseconds: 500));
  }
  c.complete();
  return c.future;
}

Future<String> runGetStdout(StringBuffer shell) async {
  var c = Completer<String>();
  var shellStr = shell.toString();
  var batch =
      File('${Directory.systemTemp.path}/temp_${getRnadomPostfix()}.bat');
  batch.writeAsStringSync(shellStr);
  var result = await ProcessExecutor.run(shell);
  if (result.exitCode == 0 || result.exitCode == 1) {
    c.complete(result.stdout);
    return c.future;
  } else {
    throw Exception(result.stderr);
  }
}

Future<NetStatData?> findUsePortPid(int port) async {
  await removeTempBat();
  var c = Completer<NetStatData?>();
  var shell = StringBuffer('netstat -aon | find "$port"');
  var stdout = "";
  try {
    stdout = await runGetStdout(shell);
  } on Exception {}
  if (stdout == "") {
    c.complete(null);
  } else {
    var netList = <NetStatData?>[];
    var returnPattern = RegExp('\n|\r');
    var list = stdout.split(returnPattern);
    for (var item in list) {
      item = item.replaceAll(returnPattern, '');
      if (item.isNotEmpty) {
        var categories = item.split(' ');
        if (categories.isNotEmpty) {
          netList.add(NetStatData(categories));
        }
      }
    }
    NetStatData? data = null;
    if (netList.length > 1) {
      for (var net in netList) {
        if (net!.pid != -1) {
          data = net;
          break;
        }
      }
    }
    c.complete(data);
  }
  return c.future;
}

Future<List<ProcessInfo>> findProcess(String searchWord) async {
  await removeTempBat();
  var c = Completer<List<ProcessInfo>>();
  var pList = <ProcessInfo>[];
  if (Platform.isWindows) {
    var csvPath = File(
        '${Directory.systemTemp.absolute.path}\\temp_${Random().nextInt(100000)}.csv');
    var shell = StringBuffer(
        'Get-CimInstance -ClassName Win32_Process | Where-Object { \$_.Name.Contains("$searchWord") } | Select-Object CommandLine, ProcessId | Export-Csv -Path ${csvPath.absolute.path} -NoTypeInformation');
    var result = await ProcessExecutor.run(shell);
    if (result.exitCode == 0 && csvPath.existsSync()) {
      pList = parseProcessStd(csvPath.readAsStringSync());
      csvPath.deleteSync();
    }
  } else if (Platform.isLinux) {
    var shell = StringBuffer('ps -ef | grep \'$searchWord\' | grep -v grep');
    var result = await ProcessExecutor.run(shell, printStdout: false);
    if (result.exitCode == 0) {
      var procList = result.stdout.toString().split('\n');
      for (var proc in procList) {
        if (proc.isNotEmpty) {
          var info = ProcessInfo();
          var index = proc.indexOf(' ');
          //deck       61218     920  0 08:24 ?        00:00:11 /home/deck/SDK/flutter/bin/cache/dart-sdk/bin/dart /home/deck/work/oto_cli/bin/main.dart scheduler -s
          proc = proc.substring(index, proc.length).trimLeft();
          index = proc.indexOf(' ');
          info.pid = int.parse(proc.substring(0, index));
          proc = proc.substring(index).trimLeft();

          index = proc.indexOf(' ');
          info.ppid = int.parse(proc.substring(0, index));
          proc = proc.substring(index).trimLeft();

          index = proc.indexOf(' ');
          info.cpuUsage = int.parse(proc.substring(0, index));
          proc = proc.substring(index).trimLeft();

          index = proc.indexOf(' ');
          info.startTime = proc.substring(0, index);
          proc = proc.substring(index).trimLeft();

          index = proc.indexOf(' ');
          info.sessionName = proc.substring(0, index);
          proc = proc.substring(index).trimLeft();

          index = proc.indexOf(' ');
          info.duration = proc.substring(0, index);

          info.name = proc.substring(index).trimLeft();
          pList.add(info);
        }
      }
    }
  } else {
    var shell = StringBuffer(
        'ps -A | grep \'$searchWord\' | grep -v grep | awk \'{\$2=\$3=""; print \$0}\'');
    var result = await ProcessExecutor.run(shell, printStdout: false);
    if (result.exitCode == 0) {
      var procList = result.stdout.toString().split('\n');
      for (var proc in procList) {
        if (proc.isNotEmpty) {
          var info = ProcessInfo();
          var index = proc.indexOf(' ');
          info.pid = int.parse(proc.substring(0, index));
          info.name = proc.substring(index).trimLeft();
          pList.add(info);
        }
      }
    }
  }
  c.complete(pList);
  return c.future;
}

List<ProcessInfo> parseProcessStd(String stdout) {
  var pList = <ProcessInfo>[];
  var returnPattern = RegExp('\n|\r');
  var list = stdout.split(returnPattern);
  for (var item in list) {
    item = item.replaceAll(returnPattern, '');
    if (item.isNotEmpty && !item.contains('CommandLine')) {
      var categories = item.split(',');
      if (categories.length > 1) {
        pList.add(ProcessInfo.category(categories));
      }
    }
  }
  return pList;
}

Future<ProcessInfo?> findProcessByPid(int pid) async {
  // await removeTempBat();
  var c = Completer<ProcessInfo?>();
  var shell =
      StringBuffer('tasklist /fi "pid eq ${pid.toString()}" /nh /fo "csv"');
  var stdout = await runGetStdout(shell);
  var pList = parseProcessStd(stdout);
  if (pList.isNotEmpty) {
    c.complete(pList.first);
  } else {
    c.complete(null);
  }
  return c.future;
}

class ProcessSilentExecutor {
  static bool _initialized = false;
  static Future initialize() async {
    if (!_initialized) {
      _initialized = true;
      //remove before files
      var regBat = RegExp(r'(silent_bat_)([0-9_]+)(.bat)');
      var regLog = RegExp(r'(silent_log_)([0-9_]+)(.txt)');
      var list = Directory.current.listSync();
      for (var item in list) {
        var matchBat = regBat.stringMatch(item.path);
        var matchLog = regLog.stringMatch(item.path);
        if (matchBat != null || matchLog != null) {
          item.deleteSync();
        }
      }
    }
  }
}

String getRnadomPostfix() {
  return '${DateTime.now().microsecondsSinceEpoch}_${Random().nextInt(100)}';
}

File getTempBat({String? fixedName}) {
  fixedName ??= getRnadomPostfix();
  File batch = File('${Directory.systemTemp.path}/temp_bat_$fixedName.bat');
  return batch;
}

class ProcessInfoListener {
  late ProcessInfo? _process;
  bool isRunning = false;
  late Timer? _timer;
  late final int _checkInterval;
  late Function(ProcessInfoListener, bool)? _connectionListener;
  set _isRunning(bool value) {
    if (isRunning != value) {
      isRunning = value;
      changedConnection();
    }
  }

  ProcessInfoListener(this._process, this._checkInterval) {
    checkLive();
  }

  void checkLive() async {
    stopChecker();
    _isRunning = true;
    _timer = Timer.periodic(Duration(seconds: _checkInterval), (time) async {
      var enable = false;
      if (_process != null) {
        var proc = await findProcessByPid(_process!.pid);
        if (proc != null) {
          print('process live');
          enable = true;
          _isRunning = true;
        }
      }

      if (!enable) {
        _isRunning = false;
        stopChecker();
      }
    });
  }

  void stopChecker() {
    if (_timer != null) {
      _timer!.cancel();
      _timer = null;
    }
  }

  void changedConnection() {
    if (_connectionListener != null) {
      _connectionListener!(this, isRunning);
    }
  }

  Function(ProcessInfoListener, bool)? get endListener {
    return _connectionListener;
  }

  void addEndListener(Function(ProcessInfoListener, bool)? listener) {
    _connectionListener = listener;
  }

  void removeConnectionListener() {
    _connectionListener = null;
  }

  void dispose() {
    stopChecker();
    removeConnectionListener();
    _isRunning = false;
    _process = null;
  }

  void terminate() {
    if (isRunning && _process != null) {
      Process.killPid(_process?.pid as int);
    }
  }
}

class ProcessInfo {
  String? startTime;
  String? duration;
  int? ppid;
  int? cpuUsage;
  late String name;
  late int pid;
  late String sessionName;
  late int session;
  late String memory;

  ProcessInfo();
  ProcessInfo.category(List<String> categories) {
    var count = 0;
    var length = categories.length;
    for (var i = 0; i < length; ++i) {
      var category = categories[i].replaceAll('"', '');
      switch (count) {
        case 0:
          name = category;
          break;
        case 1:
          var value = int.tryParse(category);
          value ??= -1;
          pid = value;
          break;
      }
      count++;
    }
  }
}
