// ignore_for_file: avoid_init_to_null

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

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

class GitHub extends Command {
  @override
  Future execute(DataCommand command) async {
    var data = DataGitHub.fromJson(getParam(command));

    var shell = StringBuffer(getCDPath(workspace));
    var commands = data.commands;
    for (var item in commands) {
      shell.write(' $and gh ${Command.getPathNormal(item)}');
    }

    var process =
        await runtime.start(shell, logHandler: Application.logWithType);

    return await completeProcess(process, command);
  }
}

class GitHubPullRequestCreate extends Command {
  @override
  Future execute(DataCommand command) async {
    var data = DataGitHubPullRequestCreate.fromJson(getParam(command));

    var shell = StringBuffer(getCDPath(workspace));
    shell.write(
        ' $and gh pr create --title "${data.title}" --body "${data.body}" -B ${data.branch}');

    var process =
        await runtime.start(shell, logHandler: Application.logWithType);
    var exitCode = process.exitCode ?? await process.process.exitCode;

    if (exitCode == 0) {
      var resultMessage = process.stdout.toString().replaceAll('\n', '');
      String? url = null;
      String? number = null;
      if (!resultMessage.contains('GraphQL: No commits')) {
        var num = resultMessage.substring(resultMessage.lastIndexOf('/') + 1);
        url = resultMessage;
        number = num.toString();
      }
      await setProperty(data.setURL, url);
      await setProperty(data.setNumber, number);
      return await completeProcess(process, command);
    }

    if (exitCode == 1) {
      var existingUrl = _extractPullRequestUrl(process.stderr.toString());
      if (existingUrl != null) {
        var num = existingUrl.substring(existingUrl.lastIndexOf('/') + 1);
        await setProperty(data.setURL, existingUrl);
        await setProperty(data.setNumber, num);
        return await completeProcess(process, command, passExitCodes: [1]);
      }
    }

    return await completeProcess(process, command);
  }

  String? _extractPullRequestUrl(String stderr) {
    final match =
        RegExp(r'https://github\.com/\S+/pull/\d+').firstMatch(stderr);
    return match?.group(0);
  }
}

class GitHubPullRequestList extends Command {
  @override
  Future execute(DataCommand command) async {
    var data = DataGitHubPullRequestList.fromJson(getParam(command));

    if (data.keys.isEmpty) throw Exception('keys not exist');
    var keysString = data.keys.join(',');

    var shell = StringBuffer(getCDPath(workspace));
    shell.write(' $and gh pr list --json $keysString');

    var decorder = Platform.isWindows ? SystemEncoding().decoder : utf8.decoder;
    var process = await runtime.start(shell,
        decoder: decorder, logHandler: Application.logWithType);
    var exitCode = process.exitCode ?? await process.process.exitCode;

    if (exitCode == 0) {
      Map<String, dynamic>? target = null;
      var jsonRaw = process.stdout.toString();
      jsonRaw = jsonRaw.substring(jsonRaw.indexOf('['));
      List<dynamic> list = jsonDecode(jsonRaw) as List<dynamic>;
      for (Map<String, dynamic> item in list) {
        if (item.containsKey(data.targetKey)) {
          if (item[data.targetKey] == data.targetValue) {
            target = item;
            break;
          }
        }
      }

      if (target == null) {
        await setProperty(data.setValue, null);
      } else {
        await setProperty(data.setValue, jsonEncode(target));
      }
    } else {
      await setProperty(data.setValue, null);
    }
    return await completeProcess(process, command);
  }
}

class GitHubPullRequestClose extends Command {
  @override
  Future execute(DataCommand command) async {
    var data = DataGitHubPullRequestClose.fromJson(getParam(command));

    var shell = StringBuffer(getCDPath(workspace));
    shell.write(' $and gh pr close ${data.number}');

    var decorder = Platform.isWindows ? SystemEncoding().decoder : utf8.decoder;
    var process = await runtime.start(shell,
        decoder: decorder, logHandler: Application.logWithType);
    return await completeProcess(process, command);
  }
}

void registerGitHubCommands() {
  Command.register(
    CommandType.GitHub,
    () => GitHub(),
    spec: const CommandSpec(
      type: CommandType.GitHub,
      category: 'github',
      dataModel: 'DataGitHub',
      samplePath: 'assets/yaml/sample/09_network.yaml',
    ),
  );
  Command.register(
    CommandType.GitHubPullRequestCreate,
    () => GitHubPullRequestCreate(),
    spec: const CommandSpec(
      type: CommandType.GitHubPullRequestCreate,
      category: 'github',
      dataModel: 'DataGitHubPullRequestCreate',
      samplePath: 'assets/yaml/sample/09_network.yaml',
    ),
  );
  Command.register(
    CommandType.GitHubPullRequestList,
    () => GitHubPullRequestList(),
    spec: const CommandSpec(
      type: CommandType.GitHubPullRequestList,
      category: 'github',
      dataModel: 'DataGitHubPullRequestList',
      samplePath: 'assets/yaml/sample/09_network.yaml',
    ),
  );
  Command.register(
    CommandType.GitHubPullRequestClose,
    () => GitHubPullRequestClose(),
    spec: const CommandSpec(
      type: CommandType.GitHubPullRequestClose,
      category: 'github',
      dataModel: 'DataGitHubPullRequestClose',
      samplePath: 'assets/yaml/sample/09_network.yaml',
    ),
  );
}
