// 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.

import 'package:dds/src/dap/protocol_generated.dart';
import 'package:file/file.dart';
import 'package:flutter_tools/src/cache.dart';

import '../../src/common.dart';
import '../test_data/tests_project.dart';
import '../test_utils.dart';
import 'test_client.dart';
import 'test_support.dart';

void main() {
  late Directory tempDir;
  late DapTestSession dap;

  setUpAll(() {
    Cache.flutterRoot = getFlutterRoot();
  });

  setUp(() async {
    tempDir = createResolvedTempDirectorySync('flutter_test_adapter_test.');
    dap = await DapTestSession.setUp(additionalArgs: <String>['--test']);
  });

  tearDown(() async {
    await dap.tearDown();
    tryToDelete(tempDir);
  });

  test('can run in debug mode', () async {
    final DapTestClient client = dap.client;
    final TestsProject project = TestsProject();
    await project.setUpIn(tempDir);

    // Collect output and test events while running the script.
    final TestEvents outputEvents = await client.collectTestOutput(
      launch: () => client.launch(
        program: project.testFilePath,
        cwd: project.dir.path,
      ),
    );

    // Check the printed output shows that the run finished, and it's exit
    // code (which is 1 due to the failing test).
    final String output = outputEvents.output.map((OutputEventBody e) => e.output).join();
    expectLines(
      output,
      <Object>[
        startsWith('Connecting to VM Service at'),
        ..._testsProjectExpectedOutput
      ],
      allowExtras: true, // Allow for printed call stack etc.
    );

    _expectStandardTestsProjectResults(outputEvents);
  });

  test('can run in noDebug mode', () async {
    final DapTestClient client = dap.client;
    final TestsProject project = TestsProject();
    await project.setUpIn(tempDir);

    // Collect output and test events while running the script.
    final TestEvents outputEvents = await client.collectTestOutput(
      launch: () => client.launch(
        program: project.testFilePath,
        noDebug: true,
        cwd: project.dir.path,
      ),
    );

    // Check the printed output shows that the run finished, and it's exit
    // code (which is 1 due to the failing test).
    final String output = outputEvents.output.map((OutputEventBody e) => e.output).join();
    expectLines(
      output,
      _testsProjectExpectedOutput,
      allowExtras: true, // Allow for printed call stack etc.
    );

    _expectStandardTestsProjectResults(outputEvents);
  });

  test('can run a single test', () async {
    final DapTestClient client = dap.client;
    final TestsProject project = TestsProject();
    await project.setUpIn(tempDir);

    // Collect output and test events while running the script.
    final TestEvents outputEvents = await client.collectTestOutput(
      launch: () => client.launch(
        program: project.testFilePath,
        noDebug: true,
        cwd: project.dir.path,
        // It's up to the calling IDE to pass the correct args for 'dart test'
        // if it wants to run a subset of tests.
        args: <String>[
          '--plain-name',
          'can pass',
        ],
      ),
    );

    final List<Object> testsNames = outputEvents.testNotifications
        .where((Map<String, Object?> e) => e['type'] == 'testStart')
        .map((Map<String, Object?> e) => (e['test']! as Map<String, Object?>)['name']!)
        .toList();

    expect(testsNames, contains('Flutter tests can pass'));
    expect(testsNames, isNot(contains('Flutter tests can fail')));
  });
}

/// Matchers for the expected console output of [TestsProject].
final List<Object> _testsProjectExpectedOutput = <Object>[
  // First test
  '✓ Flutter tests can pass',
  // Second test
  '══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════',
  'The following TestFailure was thrown running a test:',
  'Expected: false',
  '  Actual: <true>',
  '',
  'The test description was: can fail',
  '',
  '✖ Flutter tests can fail',
  // Exit
  '',
  'Exited (1).',
];

/// A helper that verifies a full set of expected test results for the
/// [TestsProject] script.
void _expectStandardTestsProjectResults(TestEvents events) {
  // Check we received all expected test events passed through from
  // package:test.
  final List<Object> eventNames =
      events.testNotifications.map((Map<String, Object?> e) => e['type']!).toList();

  // start/done should always be first/last.
  expect(eventNames.first, equals('start'));
  expect(eventNames.last, equals('done'));

  // allSuites should have occurred after start.
  expect(
    eventNames,
    containsAllInOrder(<String>['start', 'allSuites']),
  );

  // Expect two tests, with the failing one emitting an error.
  expect(
    eventNames,
    containsAllInOrder(<String>[
      'testStart',
      'testDone',
      'testStart',
      'error',
      'testDone',
    ]),
  );
}