// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

@TestOn('windows')
library;

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

import 'package:file/file.dart';
import 'package:flutter_tools/src/base/io.dart';

import '../src/common.dart';
import 'test_data/tools_entrypoint_env.dart';
import 'test_utils.dart';

final String flutterRootPath = getFlutterRoot();
final Directory flutterRoot = fileSystem.directory(flutterRootPath);

Future<void> main() async {
  // Regression test for https://github.com/flutter/flutter/issues/132592
  test('flutter/bin/dart updates the Dart SDK without hanging', () async {
    // Run the Dart entrypoint once to ensure the Dart SDK is downloaded.
    await runDartBatch();

    expect(dartSdkStamp.existsSync(), true);

    // Remove the Dart SDK stamp and run the Dart entrypoint again to trigger
    // the Dart SDK update.
    dartSdkStamp.deleteSync();
    final Future<String> runFuture = runDartBatch();
    final timer = Timer(const Duration(minutes: 5), () {
      // This print is useful for people debugging this test. Normally we would
      // avoid printing in a test but this is an exception because it's useful
      // ambient information.
      // ignore: avoid_print
      print(
        'The Dart batch entrypoint did not complete after 5 minutes. '
        'Historically this is a sign that 7-Zip zip extraction is waiting for '
        'the user to confirm they would like to overwrite files. '
        "This likely means the test isn't a flake and will fail. "
        'See: https://github.com/flutter/flutter/issues/132592',
      );
    });

    final String output = await runFuture;
    timer.cancel();

    // Check the Dart SDK was re-downloaded and extracted.
    // If 7-Zip is installed, unexpected overwrites causes this to hang.
    // If 7-Zip is not installed, unexpected overwrites results in error messages.
    // See: https://github.com/flutter/flutter/issues/132592
    expect(dartSdkStamp.existsSync(), true);
    expect(output, contains('Downloading Dart SDK from Flutter engine ...'));
    // Do not assert on the exact unzipping method, as this could change on CI
    expect(output, contains(RegExp(r'Expanding downloaded archive with (.*)...')));
    expect(output, isNot(contains('Use the -Force parameter' /* Luke */)));
  });

  // Regresion test for https://github.com/flutter/flutter/issues/171024.
  test('shared.bat does not rebuild the flutter tool on a no-op pub upgrade', () async {
    setupToolsEntrypointNewerPubpsec();

    // Run flutter --version, observe we rebuild the tool.
    ProcessResult result = await processManager.run(<String>[flutterBatch.path, '--version']);
    expect(result.stderr, contains('Building flutter tool'));

    // Now do it again.
    result = await processManager.run(<String>[flutterBatch.path, '--version']);
    expect(result.stderr, isNot(contains('Building flutter tool...')));
  });
}

Future<String> runDartBatch() async {
  var output = '';
  final Process process = await processManager.start(<String>[dartBatch.path]);
  final Future<Object?> stdoutFuture = process.stdout.transform<String>(utf8.decoder).forEach((
    String str,
  ) {
    output += str;
  });
  final Future<Object?> stderrFuture = process.stderr.transform<String>(utf8.decoder).forEach((
    String str,
  ) {
    output += str;
  });

  // Wait for the output to complete
  await Future.wait(<Future<Object?>>[stdoutFuture, stderrFuture]);
  // Ensure child exited successfully
  expect(
    await process.exitCode,
    0,
    reason:
        'child process exited with code ${await process.exitCode}, and '
        'output:\n$output',
  );

  // Check the Dart tool prints the expected output.
  expect(output, contains('A command-line utility for Dart development.'));
  expect(output, contains('Usage: dart <command|dart-file> [arguments]'));

  return output;
}

// The executable batch entrypoint for the Dart binary.
File get dartBatch {
  return flutterRoot.childDirectory('bin').childFile('dart.bat').absolute;
}

// The executable batch entrypoint for the Flutter binary.
File get flutterBatch {
  return flutterRoot.childDirectory('bin').childFile('flutter.bat').absolute;
}

// The Dart SDK's stamp file.
File get dartSdkStamp {
  return flutterRoot
      .childDirectory('bin')
      .childDirectory('cache')
      .childFile('engine-dart-sdk.stamp')
      .absolute;
}
