// ignore_for_file: avoid_init_to_null

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

import 'package:dart_framework/data/isolate_data.dart';
import 'package:dart_framework/platform/isolate_manager.dart';
import 'package:oto/cli/cli.dart';
import 'package:oto/cli/commands/command_base.dart';
import 'package:dart_framework/utils/system_util.dart';
import 'package:oto/oto/application.dart';
import 'package:oto/oto/core/build_result.dart';
import 'package:oto/oto/data/command_data.dart';

class CommandExe extends CommandBase {
  IsolateHandler? _handler;
  bool _complete = false;
  bool _ready = false;
  int _exitCode = 0;
  Map<String, dynamic>? _resultJson;
  final Future<void> Function(String) _printString;

  CommandExe({Future<void> Function(String)? printString})
      : _printString = printString ??
            ((value) async {
              stdout.writeln(value);
            });

  @override
  Map<String, List<String>> get arguments => {
        '-j': ["Used when it's a Jenkins build."],
        '-t': ["This is the setup for testing."],
        '-f {file path}': ["Run the yaml file to process."],
        '--file {file path}': ["Run the yaml file to process."],
        '--json': ["Output execution result as structured JSON format."]
      };
  @override
  String get name => 'exe';

  @override
  bool shouldPrintExecuteLog(List<String> parameters) {
    return !parameters.contains('--json');
  }

  @override
  Future execute(List<String> parameters) async {
    BuildType? buildType;
    String? filePath;
    bool isJson = false;
    bool hasUnknownOption = false;

    if (parameters.isEmpty) {
      printHelp();
      return simpleFuture;
    }

    for (int i = 0; i < parameters.length; i++) {
      final param = parameters[i];
      if (param == '--json') {
        isJson = true;
      } else if (param == '-j') {
        buildType = BuildType.jenkins;
      } else if (param == '-t') {
        buildType = BuildType.test;
      } else if (param.startsWith('-f')) {
        buildType = BuildType.file;
        if (param == '-f') {
          if (i + 1 < parameters.length) {
            filePath = parameters[++i];
          } else {
            hasUnknownOption = true;
          }
        } else {
          filePath = param.substring(2);
        }
      } else if (param.startsWith('--file=')) {
        buildType = BuildType.file;
        filePath = param.substring('--file='.length);
      } else if (param == '--file') {
        buildType = BuildType.file;
        if (i + 1 < parameters.length) {
          filePath = parameters[++i];
        } else {
          hasUnknownOption = true;
        }
      } else {
        hasUnknownOption = true;
      }
    }

    if (hasUnknownOption || buildType == null) {
      printHelp();
      return simpleFuture;
    }

    final logEnable = !isJson;

    if (buildType == BuildType.file) {
      if (filePath == null || filePath.isEmpty) {
        printHelp();
        return simpleFuture;
      }
      await startYaml(filePath, logEnable: logEnable);
    } else {
      _handler = IsolateManager.create(IsolateExe(buildType, logEnable: logEnable));
      await isComplete();
    }

    if (isJson && _resultJson != null) {
      await _printString(const JsonEncoder.withIndent('  ').convert(_resultJson));
    }

    exitCode = _exitCode;
    _handler?.exit();
    return simpleFuture;
  }

  Future executeScheduler(
      DataBuild build, bool logEnable, String logPath) async {
    _handler =
        IsolateManager.create(IsolateExe.scheduler(build, logEnable, logPath));
    await isComplete();
    _handler?.exit();
    return simpleFuture;
  }

  Future isComplete() async {
    _ready = false;
    _complete = false;
    _exitCode = 0;
    _resultJson = null;
    _handler!.addEventListener(IsolateEvent.ready, (data) => _ready = true);
    _handler!.addListener('complete', (data) {
      if (data is Map) {
        _resultJson = Map<String, dynamic>.from(data);
        _exitCode = (_resultJson!['exitCode'] as num?)?.toInt() ?? 10;
      } else if (data is int) {
        _exitCode = data;
      }
      _complete = true;
    });
    while (!(_complete && _ready)) {
      await Future.delayed(const Duration(milliseconds: 200));
    }
    return simpleFuture;
  }

  Future startYaml(String target, {bool logEnable = true}) async {
    var file = File(target);
    if (file.existsSync()) {
      _handler = IsolateManager.create(IsolateExe(BuildType.file,
          logEnable: logEnable,
          yamlContent: file.readAsStringSync(
              /*encoding: Platform.isWindows ? eucKr : systemEncoding*/)));
      await isComplete();
    } else {
      _exitCode = 10;
      if (!logEnable) {
        final failureResult = BuildResult.failure(
          'There are no files in path "$target"',
          null,
          exitCode: 10,
        );
        _resultJson = failureResult.toJson();
      } else {
        await CLI.print([
          CLI.style('There are no files in path "$target"', color: Color.red)
        ]);
      }
    }
    return simpleFuture;
  }

  @override
  String getDescription() {
    return ':::: Read documents written in yaml to do process task.';
  }
}

class IsolateExe extends IsolateBase {
  late BuildType _buildType;
  bool _logEnable = true;
  DataBuild? _build = null;
  String? _yamleContent = null;
  String? _logPath = null;

  IsolateExe(this._buildType,
      {String? yamlContent,
      DataBuild? build,
      bool logEnable = true,
      String? logPath}) {
    _yamleContent = yamlContent;
    _build = build;
    _logEnable = logEnable;
    _logPath = logPath;
  }

  factory IsolateExe.scheduler(
      DataBuild build, bool logEnable, String logPath) {
    return IsolateExe(BuildType.scheduler,
        build: build, logEnable: logEnable, logPath: logPath);
  }

  @override
  void onReady(Object? initData) async {
    final result = await Application.instance.build(_buildType,
        yamlContent: _yamleContent,
        buildData: _build,
        logEnable: _logEnable,
        logPath: _logPath);
    send(IsoMessege('complete', data: result.toJson()));
  }
}

