// 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:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/web/chrome.dart';
import 'package:flutter_tools/src/web/web_device.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import 'package:platform/platform.dart';

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

void main() {
  MockChromeLauncher mockChromeLauncher;
  MockPlatform mockPlatform;
  MockProcessManager mockProcessManager;
  MockWebApplicationPackage mockWebApplicationPackage;

  setUp(() async {
    mockWebApplicationPackage = MockWebApplicationPackage();
    mockProcessManager = MockProcessManager();
    mockChromeLauncher = MockChromeLauncher();
    mockPlatform = MockPlatform();
    when(mockChromeLauncher.launch(any)).thenAnswer((Invocation invocation) async {
      return null;
    });
    when(mockWebApplicationPackage.name).thenReturn('test');
  });

  test('Chrome defaults', () async {
    final ChromeDevice chromeDevice = ChromeDevice();

    expect(chromeDevice.name, 'Chrome');
    expect(chromeDevice.id, 'chrome');
    expect(chromeDevice.supportsHotReload, true);
    expect(chromeDevice.supportsHotRestart, true);
    expect(chromeDevice.supportsStartPaused, true);
    expect(chromeDevice.supportsFlutterExit, true);
    expect(chromeDevice.supportsScreenshot, false);
    expect(await chromeDevice.isLocalEmulator, false);
    expect(chromeDevice.getLogReader(app: mockWebApplicationPackage), isA<NoOpDeviceLogReader>());
    expect(chromeDevice.getLogReader(), isA<NoOpDeviceLogReader>());
    expect(await chromeDevice.portForwarder.forward(1), 1);
  });

  test('Server defaults', () async {
    final WebServerDevice device = WebServerDevice();

    expect(device.name, 'Web Server');
    expect(device.id, 'web-server');
    expect(device.supportsHotReload, true);
    expect(device.supportsHotRestart, true);
    expect(device.supportsStartPaused, true);
    expect(device.supportsFlutterExit, true);
    expect(device.supportsScreenshot, false);
    expect(await device.isLocalEmulator, false);
    expect(device.getLogReader(app: mockWebApplicationPackage), isA<NoOpDeviceLogReader>());
    expect(device.getLogReader(), isA<NoOpDeviceLogReader>());
    expect(await device.portForwarder.forward(1), 1);
  });

  testUsingContext('Chrome device is listed when Chrome is available', () async {
    when(mockChromeLauncher.canFindChrome()).thenReturn(true);

    final WebDevices deviceDiscoverer = WebDevices();
    final List<Device> devices = await deviceDiscoverer.pollingGetDevices();
    expect(devices, contains(isA<ChromeDevice>()));
  }, overrides: <Type, Generator>{
    ChromeLauncher: () => mockChromeLauncher,
  });

  testUsingContext('Chrome device is not listed when Chrome is not available', () async {
    when(mockChromeLauncher.canFindChrome()).thenReturn(false);

    final WebDevices deviceDiscoverer = WebDevices();
    final List<Device> devices = await deviceDiscoverer.pollingGetDevices();
    expect(devices, isNot(contains(isA<ChromeDevice>())));
  }, overrides: <Type, Generator>{
    ChromeLauncher: () => mockChromeLauncher,
  });

  testUsingContext('Web Server device is listed even when Chrome is not available', () async {
    when(mockChromeLauncher.canFindChrome()).thenReturn(false);

    final WebDevices deviceDiscoverer = WebDevices();
    final List<Device> devices = await deviceDiscoverer.pollingGetDevices();
    expect(devices, contains(isA<WebServerDevice>()));
  }, overrides: <Type, Generator>{
    ChromeLauncher: () => mockChromeLauncher,
  });

  testUsingContext('Chrome invokes version command on non-Windows platforms', () async{
    when(mockPlatform.isWindows).thenReturn(false);
    when(mockProcessManager.canRun('chrome.foo')).thenReturn(true);
    when(mockProcessManager.run(<String>['chrome.foo', '--version'])).thenAnswer((Invocation invocation) async {
      return MockProcessResult(0, 'ABC');
    });
    final ChromeDevice chromeDevice = ChromeDevice();

    expect(chromeDevice.isSupported(), true);
    expect(await chromeDevice.sdkNameAndVersion, 'ABC');

    // Verify caching works correctly.
    expect(await chromeDevice.sdkNameAndVersion, 'ABC');
    verify(mockProcessManager.run(<String>['chrome.foo', '--version'])).called(1);
  }, overrides: <Type, Generator>{
    Platform: () => mockPlatform,
    ProcessManager: () => mockProcessManager,
  });

  testUsingContext('Chrome invokes different version command on windows.', () async {
    when(mockPlatform.isWindows).thenReturn(true);
    when(mockProcessManager.canRun('chrome.foo')).thenReturn(true);
    when(mockProcessManager.run(<String>[
      'reg',
      'query',
      r'HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon',
      '/v',
      'version',
    ])).thenAnswer((Invocation invocation) async {
      return MockProcessResult(0, r'HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon\ version REG_SZ 74.0.0 A');
    });
    final ChromeDevice chromeDevice = ChromeDevice();

    expect(chromeDevice.isSupported(), true);
    expect(await chromeDevice.sdkNameAndVersion, 'Google Chrome 74.0.0');
  }, overrides: <Type, Generator>{
    Platform: () => mockPlatform,
    ProcessManager: () => mockProcessManager,
  });
}

class MockChromeLauncher extends Mock implements ChromeLauncher {}
class MockPlatform extends Mock implements Platform {
  @override
  Map<String, String> environment = <String, String>{'FLUTTER_WEB': 'true', kChromeEnvironment: 'chrome.foo'};
}
class MockProcessManager extends Mock implements ProcessManager {}
class MockProcessResult extends Mock implements ProcessResult {
  MockProcessResult(this.exitCode, this.stdout);

  @override
  final int exitCode;

  @override
  final String stdout;
}
class MockWebApplicationPackage extends Mock implements WebApplicationPackage {}