content: import 'dart:io';
  
  import 'package:args/command_runner.dart';
  import 'package:ed25519_edwards/ed25519_edwards.dart';
  import 'package:github/github.dart';
  import 'package:hex/hex.dart';
  import 'package:logging/logging.dart';
  
  import 'android_environment.dart';
  import 'build_cmake.dart';
  import 'build_gradle.dart';
  import 'build_pod.dart';
  import 'logging.dart';
  import 'options.dart';
  import 'precompile_binaries.dart';
  import 'target.dart';
  import 'util.dart';
  import 'verify_binaries.dart';
  
  final log = Logger('build_tool');
  
  abstract class BuildCommand extends Command {
    Future<void> runBuildCommand(CargokitUserOptions options);
  
    @override
    Future<void> run() async {
      final options = CargokitUserOptions.load();
  
      if (options.verboseLogging ||
          Platform.environment['CARGOKIT_VERBOSE'] == '1') {
        enableVerboseLogging();
      }
  
      await runBuildCommand(options);
    }
  }
  
  class BuildPodCommand extends BuildCommand {
    @override
    final name = 'build-pod';
  
    @override
    final description = 'Build cocoa pod library';
  
    @override
    Future<void> runBuildCommand(CargokitUserOptions options) async {
      final build = BuildPod(userOptions: options);
      await build.build();
    }
  }
  
  class BuildGradleCommand extends BuildCommand {
    @override
    final name = 'build-gradle';
  
    @override
    final description = 'Build android library';
  
    @override
    Future<void> runBuildCommand(CargokitUserOptions options) async {
      final build = BuildGradle(userOptions: options);
      await build.build();
    }
  }
  
  class BuildCMakeCommand extends BuildCommand {
    @override
    final name = 'build-cmake';
  
    @override
    final description = 'Build CMake library';
  
    @override
    Future<void> runBuildCommand(CargokitUserOptions options) async {
      final build = BuildCMake(userOptions: options);
      await build.build();
    }
  }
  
  class GenKeyCommand extends Command {
    @override
    final name = 'gen-key';
  
    @override
    final description = 'Generate key pair for signing precompiled binaries';
  
    @override
    void run() {
      final kp = generateKey();
      final private = HEX.encode(kp.privateKey.bytes);
      final public = HEX.encode(kp.publicKey.bytes);
      print("Private Key: $private");
      print("Public Key: $public");
    }
  }
  
  class PrecompileBinariesCommand extends Command {
    PrecompileBinariesCommand() {
      argParser
        ..addOption(
          'repository',
          mandatory: true,
          help: 'Github repository slug in format owner/name',
        )
        ..addOption(
          'manifest-dir',
          mandatory: true,
          help: 'Directory containing Cargo.toml',
        )
        ..addMultiOption('target',
            help: 'Rust target triple of artifact to build.\n'
                'Can be specified multiple times or omitted in which case\n'
                'all targets for current platform will be built.')
        ..addOption(
          'android-sdk-location',
          help: 'Location of Android SDK (if available)',
        )
        ..addOption(
          'android-ndk-version',
          help: 'Android NDK version (if available)',
        )
        ..addOption(
          'android-min-sdk-version',
          help: 'Android minimum rquired version (if available)',
        )
        ..addOption(
          'temp-dir',
          help: 'Directory to store temporary build artifacts',
        )
        ..addFlag(
          "verbose",
          abbr: "v",
          defaultsTo: false,
          help: "Enable verbose logging",
        );
    }
  
    @override
    final name = 'precompile-binaries';
  
    @override
    final description = 'Prebuild and upload binaries\n'
        'Private key must be passed through PRIVATE_KEY environment variable. '
        'Use gen_key through generate priave key.\n'
        'Github token must be passed as GITHUB_TOKEN environment variable.\n';
  
    @override
    Future<void> run() async {
      final verbose = argResults!['verbose'] as bool;
      if (verbose) {
        enableVerboseLogging();
      }
  
      final privateKeyString = Platform.environment['PRIVATE_KEY'];
      if (privateKeyString == null) {
        throw ArgumentError('Missing PRIVATE_KEY environment variable');
      }
      final githubToken = Platform.environment['GITHUB_TOKEN'];
      if (githubToken == null) {
        throw ArgumentError('Missing GITHUB_TOKEN environment variable');
      }
      final privateKey = HEX.decode(privateKeyString);
      if (privateKey.length != 64) {
        throw ArgumentError('Private key must be 64 bytes long');
      }
      final manifestDir = argResults!['manifest-dir'] as String;
      if (!Directory(manifestDir).existsSync()) {
        throw ArgumentError('Manifest directory does not exist: $manifestDir');
      }
      String? androidMinSdkVersionString =
          argResults!['android-min-sdk-version'] as String?;
      int? androidMinSdkVersion;
      if (androidMinSdkVersionString != null) {
        androidMinSdkVersion = int.tryParse(androidMinSdkVersionString);
        if (androidMinSdkVersion == null) {
          throw ArgumentError(
              'Invalid android-min-sdk-version: $androidMinSdkVersionString');
        }
      }
      final targetStrigns = argResults!['target'] as List<String>;
      final targets = targetStrigns.map((target) {
        final res = Target.forRustTriple(target);
        if (res == null) {
          throw ArgumentError('Invalid target: $target');
        }
        return res;
      }).toList(growable: false);
      final precompileBinaries = PrecompileBinaries(
        privateKey: PrivateKey(privateKey),
        githubToken: githubToken,
        manifestDir: manifestDir,
        repositorySlug: RepositorySlug.full(argResults!['repository'] as String),
        targets: targets,
        androidSdkLocation: argResults!['android-sdk-location'] as String?,
        androidNdkVersion: argResults!['android-ndk-version'] as String?,
        androidMinSdkVersion: androidMinSdkVersion,
        tempDir: argResults!['temp-dir'] as String?,
      );
  
      await precompileBinaries.run();
    }
  }
  
  class VerifyBinariesCommand extends Command {
    VerifyBinariesCommand() {
      argParser.addOption(
        'manifest-dir',
        mandatory: true,
        help: 'Directory containing Cargo.toml',
      );
    }
  
    @override
    final name = "verify-binaries";
  
    @override
    final description = 'Verifies published binaries\n'
        'Checks whether there is a binary published for each targets\n'
        'and checks the signature.';
  
    @override
    Future<void> run() async {
      final manifestDir = argResults!['manifest-dir'] as String;
      final verifyBinaries = VerifyBinaries(
        manifestDir: manifestDir,
      );
      await verifyBinaries.run();
    }
  }
  
  Future<void> runMain(List<String> args) async {
    try {
      // Init logging before options are loaded
      initLogging();
  
      if (Platform.environment['_CARGOKIT_NDK_LINK_TARGET'] != null) {
        return AndroidEnvironment.clangLinkerWrapper(args);
      }
  
      final runner = CommandRunner('build_tool', 'Cargokit built_tool')
        ..addCommand(BuildPodCommand())
        ..addCommand(BuildGradleCommand())
        ..addCommand(BuildCMakeCommand())
        ..addCommand(GenKeyCommand())
        ..addCommand(PrecompileBinariesCommand())
        ..addCommand(VerifyBinariesCommand());
  
      await runner.run(args);
    } on ArgumentError catch (e) {
      stderr.writeln(e.toString());
      exit(1);
    } catch (e, s) {
      log.severe(kDoubleSeparator);
      log.severe('Cargokit BuildTool failed with error:');
      log.severe(kSeparator);
      log.severe(e);
      // This tells user to install Rust, there's no need to pollute the log with
      // stack trace.
      if (e is! RustupNotFoundException) {
        log.severe(kSeparator);
        log.severe(s);
        log.severe(kSeparator);
        log.severe('BuildTool arguments: $args');
      }
      log.severe(kDoubleSeparator);
      exit(1);
    }
  }
  