// 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 'dart:convert';
import 'dart:typed_data';

import 'package:flutter_tools/src/test/flutter_web_platform.dart';
import 'package:flutter_tools/src/test/test_compiler.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';

import '../../src/common.dart';
import '../../src/mocks.dart';
import '../../src/testbed.dart';

void main() {

  group('Test that TestGoldenComparator', () {
    Testbed testbed;
    Uri goldenKey;
    Uri goldenKey2;
    Uri testUri;
    Uri testUri2;
    Uint8List imageBytes;
    MockProcessManager mockProcessManager;
    MockTestCompiler mockCompiler;

    setUp(() {
      goldenKey = Uri.parse('file://golden_key');
      goldenKey2 = Uri.parse('file://second_golden_key');
      testUri = Uri.parse('file://test_uri');
      testUri2 = Uri.parse('file://second_test_uri');
      imageBytes = Uint8List.fromList(<int>[1,2,3,4,5]);
      mockProcessManager = MockProcessManager();
      mockCompiler = MockTestCompiler();
      when(mockCompiler.compile(any)).thenAnswer((_) => Future<String>.value('compiler_output'));

      testbed = Testbed(overrides: <Type, Generator>{
        ProcessManager: () {
          return mockProcessManager;
        }
      });
    });

    test('succeed when golden comparison succeed', () => testbed.run(() async {
      final Map<String, dynamic> expectedResponse = <String, dynamic>{
        'success': true,
        'message': 'some message',
      };

      when(mockProcessManager.start(any, environment: anyNamed('environment')))
        .thenAnswer((Invocation invocation) async {
          return FakeProcess(
            exitCode: Future<int>.value(0),
            stdout: stdoutFromString(jsonEncode(expectedResponse) + '\n'),
          );
      });

      final TestGoldenComparator comparator = TestGoldenComparator(
        'shell',
        () => mockCompiler,
      );

      final String result = await comparator.compareGoldens(testUri, imageBytes, goldenKey, false);
      expect(result, null);
    }));

    test('fail with error message when golden comparison failed', () => testbed.run(() async {
      final Map<String, dynamic> expectedResponse = <String, dynamic>{
        'success': false,
        'message': 'some message',
      };

      when(mockProcessManager.start(any, environment: anyNamed('environment')))
        .thenAnswer((Invocation invocation) async {
          return FakeProcess(
            exitCode: Future<int>.value(0),
            stdout: stdoutFromString(jsonEncode(expectedResponse) + '\n'),
          );
      });

      final TestGoldenComparator comparator = TestGoldenComparator(
        'shell',
        () => mockCompiler,
      );

      final String result = await comparator.compareGoldens(testUri, imageBytes, goldenKey, false);
      expect(result, 'some message');
    }));

    test('reuse the process for the same test file', () => testbed.run(() async {
      final Map<String, dynamic> expectedResponse1 = <String, dynamic>{
        'success': false,
        'message': 'some message',
      };
      final Map<String, dynamic> expectedResponse2 = <String, dynamic>{
        'success': false,
        'message': 'some other message',
      };

      when(mockProcessManager.start(any, environment: anyNamed('environment')))
        .thenAnswer((Invocation invocation) async {
          return FakeProcess(
            exitCode: Future<int>.value(0),
            stdout: stdoutFromString(jsonEncode(expectedResponse1) + '\n' + jsonEncode(expectedResponse2) + '\n'),
          );
      });

      final TestGoldenComparator comparator = TestGoldenComparator(
        'shell',
        () => mockCompiler,
      );

      final String result1 = await comparator.compareGoldens(testUri, imageBytes, goldenKey, false);
      expect(result1, 'some message');
      final String result2 = await comparator.compareGoldens(testUri, imageBytes, goldenKey2, false);
      expect(result2, 'some other message');
      verify(mockProcessManager.start(any, environment: anyNamed('environment'))).called(1);
    }));

    test('does not reuse the process for different test file', () => testbed.run(() async {
      final Map<String, dynamic> expectedResponse1 = <String, dynamic>{
        'success': false,
        'message': 'some message',
      };
      final Map<String, dynamic> expectedResponse2 = <String, dynamic>{
        'success': false,
        'message': 'some other message',
      };

      when(mockProcessManager.start(any, environment: anyNamed('environment')))
        .thenAnswer((Invocation invocation) async {
          return FakeProcess(
            exitCode: Future<int>.value(0),
            stdout: stdoutFromString(jsonEncode(expectedResponse1) + '\n' + jsonEncode(expectedResponse2) + '\n'),
          );
      });

      final TestGoldenComparator comparator = TestGoldenComparator(
        'shell',
        () => mockCompiler,
      );

      final String result1 = await comparator.compareGoldens(testUri, imageBytes, goldenKey, false);
      expect(result1, 'some message');
      final String result2 = await comparator.compareGoldens(testUri2, imageBytes, goldenKey2, false);
      expect(result2, 'some message');
      verify(mockProcessManager.start(any, environment: anyNamed('environment'))).called(2);
    }));

    test('removes all temporary files when closed', () => testbed.run(() async {
      final Map<String, dynamic> expectedResponse = <String, dynamic>{
        'success': true,
        'message': 'some message',
      };

      when(mockProcessManager.start(any, environment: anyNamed('environment')))
        .thenAnswer((Invocation invocation) async {
          return FakeProcess(
            exitCode: Future<int>.value(0),
            stdout: stdoutFromString(jsonEncode(expectedResponse) + '\n'),
          );
      });

      final TestGoldenComparator comparator = TestGoldenComparator(
        'shell',
        () => mockCompiler,
      );

      final String result = await comparator.compareGoldens(testUri, imageBytes, goldenKey, false);
      expect(result, null);

      await comparator.close();
      expect(globals.fs.systemTempDirectory.listSync(recursive: true), isEmpty);
    }));
  });
}

Stream<List<int>> stdoutFromString(String string) => Stream<List<int>>.fromIterable(<List<int>>[
  utf8.encode(string),
]);

class MockProcessManager extends Mock implements ProcessManager {}
class MockTestCompiler extends Mock implements TestCompiler {}