// Copyright 2013 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:args/command_runner.dart';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_plugin_tools/src/common/core.dart';
import 'package:flutter_plugin_tools/src/common/plugin_utils.dart';
import 'package:flutter_plugin_tools/src/fetch_deps_command.dart';
import 'package:test/test.dart';

import 'mocks.dart';
import 'util.dart';

void main() {
  group('FetchDepsCommand', () {
    FileSystem fileSystem;
    late Directory packagesDir;
    late CommandRunner<void> runner;
    late MockPlatform mockPlatform;
    late RecordingProcessRunner processRunner;

    setUp(() {
      fileSystem = MemoryFileSystem();
      packagesDir = createPackagesDirectory(fileSystem: fileSystem);
      mockPlatform = MockPlatform();
      processRunner = RecordingProcessRunner();
      final FetchDepsCommand command = FetchDepsCommand(
        packagesDir,
        processRunner: processRunner,
        platform: mockPlatform,
      );

      runner =
          CommandRunner<void>('fetch_deps_test', 'Test for $FetchDepsCommand');
      runner.addCommand(command);
    });

    group('dart', () {
      test('runs pub get', () async {
        final RepositoryPackage plugin = createFakePlugin(
            'plugin1', packagesDir, platformSupport: <String, PlatformDetails>{
          platformIOS: const PlatformDetails(PlatformSupport.inline)
        });

        final List<String> output =
            await runCapturingPrint(runner, <String>['fetch-deps']);

        expect(
          processRunner.recordedCalls,
          orderedEquals(<ProcessCall>[
            ProcessCall(
              'flutter',
              const <String>['pub', 'get'],
              plugin.directory.path,
            ),
          ]),
        );

        expect(
            output,
            containsAllInOrder(<Matcher>[
              contains('Running for plugin1'),
              contains('No issues found!'),
            ]));
      });

      test('fails if pub get fails', () async {
        createFakePlugin('plugin1', packagesDir,
            platformSupport: <String, PlatformDetails>{
              platformIOS: const PlatformDetails(PlatformSupport.inline)
            });

        processRunner
                .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] =
            <FakeProcessInfo>[
          FakeProcessInfo(MockProcess(exitCode: 1)),
        ];

        Error? commandError;
        final List<String> output = await runCapturingPrint(
            runner, <String>['fetch-deps'], errorHandler: (Error e) {
          commandError = e;
        });

        expect(commandError, isA<ToolExit>());
        expect(
            output,
            containsAllInOrder(
              <Matcher>[
                contains('Failed to "pub get"'),
              ],
            ));
      });

      test('skips unsupported packages when any platforms are passed',
          () async {
        final RepositoryPackage packageWithBoth = createFakePackage(
            'supports_both', packagesDir, extraFiles: <String>[
          'example/linux/placeholder',
          'example/windows/placeholder'
        ]);
        final RepositoryPackage packageWithOne = createFakePackage(
            'supports_one', packagesDir,
            extraFiles: <String>['example/linux/placeholder']);
        createFakePackage('supports_neither', packagesDir);

        await runCapturingPrint(runner, <String>[
          'fetch-deps',
          '--linux',
          '--windows',
          '--supporting-target-platforms-only'
        ]);

        expect(
          processRunner.recordedCalls,
          orderedEquals(<ProcessCall>[
            ProcessCall(
              'dart',
              const <String>['pub', 'get'],
              packageWithBoth.path,
            ),
            ProcessCall(
              'dart',
              const <String>['pub', 'get'],
              packageWithOne.path,
            ),
          ]),
        );
      });
    });

    group('android', () {
      test('runs pub get before gradlew dependencies', () async {
        final RepositoryPackage plugin =
            createFakePlugin('plugin1', packagesDir, extraFiles: <String>[
          'example/android/gradlew',
        ], platformSupport: <String, PlatformDetails>{
          platformAndroid: const PlatformDetails(PlatformSupport.inline)
        });

        final Directory androidDir = plugin
            .getExamples()
            .first
            .platformDirectory(FlutterPlatform.android);

        final List<String> output = await runCapturingPrint(
            runner, <String>['fetch-deps', '--android']);

        expect(
          processRunner.recordedCalls,
          orderedEquals(<ProcessCall>[
            ProcessCall(
              'flutter',
              const <String>['pub', 'get'],
              plugin.directory.path,
            ),
            ProcessCall(
              androidDir.childFile('gradlew').path,
              const <String>['plugin1:dependencies'],
              androidDir.path,
            ),
          ]),
        );

        expect(
            output,
            containsAllInOrder(<Matcher>[
              contains('Running for plugin1'),
              contains('No issues found!'),
            ]));
      });

      test('runs gradlew dependencies', () async {
        final RepositoryPackage plugin =
            createFakePlugin('plugin1', packagesDir, extraFiles: <String>[
          'example/android/gradlew',
        ], platformSupport: <String, PlatformDetails>{
          platformAndroid: const PlatformDetails(PlatformSupport.inline)
        });

        final Directory androidDir = plugin
            .getExamples()
            .first
            .platformDirectory(FlutterPlatform.android);

        final List<String> output = await runCapturingPrint(
            runner, <String>['fetch-deps', '--no-dart', '--android']);

        expect(
          processRunner.recordedCalls,
          orderedEquals(<ProcessCall>[
            ProcessCall(
              androidDir.childFile('gradlew').path,
              const <String>['plugin1:dependencies'],
              androidDir.path,
            ),
          ]),
        );

        expect(
            output,
            containsAllInOrder(<Matcher>[
              contains('Running for plugin1'),
              contains('No issues found!'),
            ]));
      });

      test('runs on all examples', () async {
        final List<String> examples = <String>['example1', 'example2'];
        final RepositoryPackage plugin = createFakePlugin(
            'plugin1', packagesDir,
            examples: examples,
            extraFiles: <String>[
              'example/example1/android/gradlew',
              'example/example2/android/gradlew',
            ],
            platformSupport: <String, PlatformDetails>{
              platformAndroid: const PlatformDetails(PlatformSupport.inline)
            });

        final Iterable<Directory> exampleAndroidDirs = plugin.getExamples().map(
            (RepositoryPackage example) =>
                example.platformDirectory(FlutterPlatform.android));

        final List<String> output = await runCapturingPrint(
            runner, <String>['fetch-deps', '--no-dart', '--android']);

        expect(
          processRunner.recordedCalls,
          orderedEquals(<ProcessCall>[
            for (final Directory directory in exampleAndroidDirs)
              ProcessCall(
                directory.childFile('gradlew').path,
                const <String>['plugin1:dependencies'],
                directory.path,
              ),
          ]),
        );

        expect(
            output,
            containsAllInOrder(<Matcher>[
              contains('Running for plugin1'),
              contains('No issues found!'),
            ]));
      });

      test('runs --config-only build if gradlew is missing', () async {
        final RepositoryPackage plugin = createFakePlugin(
            'plugin1', packagesDir, platformSupport: <String, PlatformDetails>{
          platformAndroid: const PlatformDetails(PlatformSupport.inline)
        });

        final Directory androidDir = plugin
            .getExamples()
            .first
            .platformDirectory(FlutterPlatform.android);

        final List<String> output = await runCapturingPrint(
            runner, <String>['fetch-deps', '--no-dart', '--android']);

        expect(
          processRunner.recordedCalls,
          orderedEquals(<ProcessCall>[
            ProcessCall(
              getFlutterCommand(mockPlatform),
              const <String>['build', 'apk', '--config-only'],
              plugin.getExamples().first.directory.path,
            ),
            ProcessCall(
              androidDir.childFile('gradlew').path,
              const <String>['plugin1:dependencies'],
              androidDir.path,
            ),
          ]),
        );

        expect(
            output,
            containsAllInOrder(<Matcher>[
              contains('Running for plugin1'),
              contains('No issues found!'),
            ]));
      });

      test('fails if gradlew generation fails', () async {
        createFakePlugin('plugin1', packagesDir,
            platformSupport: <String, PlatformDetails>{
              platformAndroid: const PlatformDetails(PlatformSupport.inline)
            });

        processRunner
                .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] =
            <FakeProcessInfo>[
          FakeProcessInfo(MockProcess(exitCode: 1)),
        ];

        Error? commandError;
        final List<String> output = await runCapturingPrint(
            runner, <String>['fetch-deps', '--no-dart', '--android'],
            errorHandler: (Error e) {
          commandError = e;
        });

        expect(commandError, isA<ToolExit>());
        expect(
            output,
            containsAllInOrder(
              <Matcher>[
                contains('Unable to configure Gradle project'),
              ],
            ));
      });

      test('fails if dependency download finds issues', () async {
        final RepositoryPackage plugin =
            createFakePlugin('plugin1', packagesDir, extraFiles: <String>[
          'example/android/gradlew',
        ], platformSupport: <String, PlatformDetails>{
          platformAndroid: const PlatformDetails(PlatformSupport.inline)
        });

        final String gradlewPath = plugin
            .getExamples()
            .first
            .platformDirectory(FlutterPlatform.android)
            .childFile('gradlew')
            .path;
        processRunner.mockProcessesForExecutable[gradlewPath] =
            <FakeProcessInfo>[
          FakeProcessInfo(MockProcess(exitCode: 1)),
        ];

        Error? commandError;
        final List<String> output = await runCapturingPrint(
            runner, <String>['fetch-deps', '--no-dart', '--android'],
            errorHandler: (Error e) {
          commandError = e;
        });

        expect(commandError, isA<ToolExit>());
        expect(
            output,
            containsAllInOrder(
              <Matcher>[
                contains('The following packages had errors:'),
              ],
            ));
      });

      test('skips non-Android plugins', () async {
        createFakePlugin('plugin1', packagesDir);

        final List<String> output = await runCapturingPrint(
            runner, <String>['fetch-deps', '--no-dart', '--android']);

        expect(
            output,
            containsAllInOrder(
              <Matcher>[
                contains('Package does not have native Android dependencies.')
              ],
            ));
      });

      test('skips non-inline plugins', () async {
        createFakePlugin('plugin1', packagesDir,
            platformSupport: <String, PlatformDetails>{
              platformAndroid: const PlatformDetails(PlatformSupport.federated)
            });

        final List<String> output = await runCapturingPrint(
            runner, <String>['fetch-deps', '--no-dart', '--android']);

        expect(
            output,
            containsAllInOrder(
              <Matcher>[
                contains('Package does not have native Android dependencies.')
              ],
            ));
      });
    });

    group('ios', () {
      test('runs on all examples', () async {
        final List<String> examples = <String>['example1', 'example2'];
        final RepositoryPackage plugin = createFakePlugin(
            'plugin1', packagesDir,
            examples: examples,
            platformSupport: <String, PlatformDetails>{
              platformIOS: const PlatformDetails(PlatformSupport.inline)
            });

        final Iterable<Directory> exampleDirs = plugin
            .getExamples()
            .map((RepositoryPackage example) => example.directory);

        final List<String> output = await runCapturingPrint(
            runner, <String>['fetch-deps', '--no-dart', '--ios']);

        expect(
          processRunner.recordedCalls,
          orderedEquals(<ProcessCall>[
            const ProcessCall(
              'flutter',
              <String>['precache', '--ios'],
              null,
            ),
            for (final Directory directory in exampleDirs)
              ProcessCall(
                'flutter',
                const <String>['build', 'ios', '--config-only'],
                directory.path,
              ),
          ]),
        );

        expect(
            output,
            containsAllInOrder(<Matcher>[
              contains('Running for plugin1'),
              contains('No issues found!'),
            ]));
      });

      test('fails if flutter build --config-only fails', () async {
        createFakePlugin('plugin1', packagesDir,
            platformSupport: <String, PlatformDetails>{
              platformIOS: const PlatformDetails(PlatformSupport.inline)
            });

        processRunner
                .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] =
            <FakeProcessInfo>[
          FakeProcessInfo(MockProcess(), <String>['precache']),
          FakeProcessInfo(MockProcess(exitCode: 1),
              <String>['build', 'ios', '--config-only']),
        ];

        Error? commandError;
        final List<String> output = await runCapturingPrint(
            runner, <String>['fetch-deps', '--no-dart', '--ios'],
            errorHandler: (Error e) {
          commandError = e;
        });

        expect(commandError, isA<ToolExit>());
        expect(
            output,
            containsAllInOrder(
              <Matcher>[
                contains('The following packages had errors:'),
              ],
            ));
      });

      test('skips non-iOS plugins', () async {
        createFakePlugin('plugin1', packagesDir);

        final List<String> output = await runCapturingPrint(
            runner, <String>['fetch-deps', '--no-dart', '--ios']);

        expect(
            output,
            containsAllInOrder(
              <Matcher>[
                contains('Package does not have native iOS dependencies.')
              ],
            ));
      });

      test('skips non-inline plugins', () async {
        createFakePlugin('plugin1', packagesDir,
            platformSupport: <String, PlatformDetails>{
              platformIOS: const PlatformDetails(PlatformSupport.federated)
            });

        final List<String> output = await runCapturingPrint(
            runner, <String>['fetch-deps', '--no-dart', '--ios']);

        expect(
            output,
            containsAllInOrder(
              <Matcher>[
                contains('Package does not have native iOS dependencies.')
              ],
            ));
      });
    });

    group('macos', () {
      test('runs on all examples', () async {
        final List<String> examples = <String>['example1', 'example2'];
        final RepositoryPackage plugin = createFakePlugin(
            'plugin1', packagesDir,
            examples: examples,
            platformSupport: <String, PlatformDetails>{
              platformMacOS: const PlatformDetails(PlatformSupport.inline)
            });

        final Iterable<Directory> exampleDirs = plugin
            .getExamples()
            .map((RepositoryPackage example) => example.directory);

        final List<String> output = await runCapturingPrint(
            runner, <String>['fetch-deps', '--no-dart', '--macos']);

        expect(
          processRunner.recordedCalls,
          orderedEquals(<ProcessCall>[
            const ProcessCall(
              'flutter',
              <String>['precache', '--macos'],
              null,
            ),
            for (final Directory directory in exampleDirs)
              ProcessCall(
                'flutter',
                const <String>['build', 'macos', '--config-only'],
                directory.path,
              ),
          ]),
        );

        expect(
            output,
            containsAllInOrder(<Matcher>[
              contains('Running for plugin1'),
              contains('No issues found!'),
            ]));
      });

      test('fails if flutter build --config-only fails', () async {
        createFakePlugin('plugin1', packagesDir,
            platformSupport: <String, PlatformDetails>{
              platformMacOS: const PlatformDetails(PlatformSupport.inline)
            });

        processRunner
                .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] =
            <FakeProcessInfo>[
          FakeProcessInfo(MockProcess(), <String>['precache']),
          FakeProcessInfo(MockProcess(exitCode: 1),
              <String>['build', 'macos', '--config-only']),
        ];

        Error? commandError;
        final List<String> output = await runCapturingPrint(
            runner, <String>['fetch-deps', '--no-dart', '--macos'],
            errorHandler: (Error e) {
          commandError = e;
        });

        expect(commandError, isA<ToolExit>());
        expect(
            output,
            containsAllInOrder(
              <Matcher>[
                contains('The following packages had errors:'),
              ],
            ));
      });

      test('skips non-macOS plugins', () async {
        createFakePlugin('plugin1', packagesDir);

        final List<String> output = await runCapturingPrint(
            runner, <String>['fetch-deps', '--no-dart', '--macos']);

        expect(
            output,
            containsAllInOrder(
              <Matcher>[
                contains('Package does not have native macOS dependencies.')
              ],
            ));
      });

      test('skips non-inline plugins', () async {
        createFakePlugin('plugin1', packagesDir,
            platformSupport: <String, PlatformDetails>{
              platformMacOS: const PlatformDetails(PlatformSupport.federated)
            });

        final List<String> output = await runCapturingPrint(
            runner, <String>['fetch-deps', '--no-dart', '--macos']);

        expect(
            output,
            containsAllInOrder(
              <Matcher>[
                contains('Package does not have native macOS dependencies.')
              ],
            ));
      });
    });
  });
}