// 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 'dart:async';

import 'package:file/memory.dart';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/bundle.dart';
import 'package:flutter_tools/src/bundle_builder.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/preview_device.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:meta/meta.dart';
import 'package:test/fake.dart';

import '../src/common.dart';
import '../src/context.dart';
import '../src/fakes.dart';

void main() {
  String? flutterRootBackup;
  late MemoryFileSystem fs;
  late File previewBinary;

  setUp(() {
    fs = MemoryFileSystem.test(style: FileSystemStyle.windows);
    Cache.flutterRoot = r'C:\path\to\flutter';
    previewBinary = fs.file('${Cache.flutterRoot}\\bin\\cache\\artifacts\\flutter_preview\\flutter_preview.exe');
    previewBinary.createSync(recursive: true);
    flutterRootBackup = Cache.flutterRoot;
  });

  tearDown(() {
    Cache.flutterRoot = flutterRootBackup;
  });

  testWithoutContext('PreviewDevice defaults', () async {
    final PreviewDevice device = PreviewDevice(
      artifacts: Artifacts.test(),
      fileSystem: fs,
      processManager: FakeProcessManager.any(),
      previewBinary: previewBinary,
      logger: BufferLogger.test(),
    );

    expect(await device.isLocalEmulator, false);
    expect(device.name, 'preview');
    expect(await device.sdkNameAndVersion, 'preview');
    expect(await device.targetPlatform, TargetPlatform.windows_x64);
    expect(device.category, Category.desktop);
    expect(device.ephemeral, false);
    expect(device.id, 'preview');

    expect(device.isSupported(), true);
    expect(device.isSupportedForProject(FakeFlutterProject()), true);
    expect(await device.isLatestBuildInstalled(FakeApplicationPackage()), false);
    expect(await device.isAppInstalled(FakeApplicationPackage()), false);
    expect(await device.uninstallApp(FakeApplicationPackage()), true);
  });

  testUsingContext('Can build a simulator app', () async {
    final Completer<void> completer = Completer<void>();
    final BufferLogger logger = BufferLogger.test();
    final PreviewDevice device = PreviewDevice(
      artifacts: Artifacts.test(),
      fileSystem: fs,
      previewBinary: previewBinary,
      processManager: FakeProcessManager.list(<FakeCommand>[
        FakeCommand(
          command: const <String>[
            r'C:\.tmp_rand0\flutter_preview.rand0\flutter_preview.exe',
          ],
          stdout: 'The Dart VM service is listening on http://127.0.0.1:64494/fZ_B2N6JRwY=/\n',
          completer: completer,
        ),
      ]),
      logger: logger,
      builderFactory: () => FakeBundleBuilder(fs),
    );
    final Directory previewDeviceCacheDir = fs
      .directory('Artifact.windowsDesktopPath.TargetPlatform.windows_x64.debug')
      ..createSync(recursive: true);
    previewDeviceCacheDir.childFile('flutter_windows.dll').writeAsStringSync('1010101');
    previewDeviceCacheDir.childFile('icudtl.dat').writeAsStringSync('1010101');

    final LaunchResult result = await device.startApp(
      FakeApplicationPackage(),
      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
    );

    expect(result.started, true);
    expect(result.vmServiceUri, Uri.parse('http://127.0.0.1:64494/fZ_B2N6JRwY=/'));
  });

  group('PreviewDeviceDiscovery', () {
    late Artifacts artifacts;
    late ProcessManager processManager;
    final FakePlatform windowsPlatform = FakePlatform(operatingSystem: 'windows');
    final FakePlatform macPlatform = FakePlatform(operatingSystem: 'macos');
    final FakePlatform linuxPlatform = FakePlatform();
    final TestFeatureFlags featureFlags = TestFeatureFlags(isPreviewDeviceEnabled: true);

    setUp(() {
      artifacts = Artifacts.test(fileSystem: fs);
      processManager = FakeProcessManager.empty();
    });

    testWithoutContext('PreviewDeviceDiscovery on linux', () async {
      final PreviewDeviceDiscovery discovery = PreviewDeviceDiscovery(
        artifacts: artifacts,
        fileSystem: fs,
        logger: BufferLogger.test(),
        processManager: processManager,
        platform: linuxPlatform,
        featureFlags: featureFlags,
      );

      final List<Device> devices = await discovery.devices();

      expect(devices, isEmpty);
    });

    testWithoutContext('PreviewDeviceDiscovery on macOS', () async {
      final PreviewDeviceDiscovery discovery = PreviewDeviceDiscovery(
        artifacts: artifacts,
        fileSystem: fs,
        logger: BufferLogger.test(),
        processManager: processManager,
        platform: macPlatform,
        featureFlags: featureFlags,
      );

      final List<Device> devices = await discovery.devices();

      expect(devices, isEmpty);
    });

    testWithoutContext('PreviewDeviceDiscovery on Windows returns preview when binary exists', () async {
      // ensure Flutter preview binary exists in cache.
      fs.file(artifacts.getArtifactPath(Artifact.flutterPreviewDevice)).writeAsBytesSync(<int>[1, 0, 0, 1]);
      final PreviewDeviceDiscovery discovery = PreviewDeviceDiscovery(
        artifacts: artifacts,
        fileSystem: fs,
        logger: BufferLogger.test(),
        processManager: processManager,
        platform: windowsPlatform,
        featureFlags: featureFlags,
      );

      final List<Device> devices = await discovery.devices();

      expect(devices, hasLength(1));
      final Device previewDevice = devices.first;
      expect(previewDevice, isA<PreviewDevice>());
    });

    testWithoutContext('PreviewDeviceDiscovery on Windows returns nothing when binary does not exist', () async {
      final PreviewDeviceDiscovery discovery = PreviewDeviceDiscovery(
        artifacts: artifacts,
        fileSystem: fs,
        logger: BufferLogger.test(),
        processManager: processManager,
        platform: windowsPlatform,
        featureFlags: featureFlags,
      );

      final List<Device> devices = await discovery.devices();

      expect(devices, isEmpty);
    });
  });
}

class FakeFlutterProject extends Fake implements FlutterProject { }
class FakeApplicationPackage extends Fake implements ApplicationPackage { }
class FakeBundleBuilder extends Fake implements BundleBuilder {
  FakeBundleBuilder(this.fileSystem);

  final FileSystem fileSystem;

  @override
  Future<void> build({
    required TargetPlatform platform,
    required BuildInfo buildInfo,
    FlutterProject? project,
    String? mainPath,
    String manifestPath = defaultManifestPath,
    String? applicationKernelFilePath,
    String? depfilePath,
    String? assetDirPath,
    bool buildNativeAssets = true,
    @visibleForTesting BuildSystem? buildSystem
  }) async {
    final Directory assetDirectory = fileSystem
      .directory(assetDirPath)
      .childDirectory('flutter_assets')
      ..createSync(recursive: true);
    assetDirectory.childFile('kernel_blob.bin').createSync();
  }
}
