// 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:file_testing/file_testing.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.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/build_system/targets/android.dart';
import 'package:flutter_tools/src/build_system/targets/assets.dart';
import 'package:flutter_tools/src/build_system/targets/common.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:mockito/mockito.dart';

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

void main() {
  FakeProcessManager fakeProcessManager;
  final Testbed testbed = Testbed(overrides: <Type, Generator>{
    Cache: () => FakeCache(),
    Platform: () => FakePlatform(operatingSystem: 'linux', environment: const <String, String>{}),
  });

  test('Android AOT targets has analyicsName', () {
    expect(androidArmProfile.analyticsName, 'android_aot');
  });

  testbed.test('debug bundle contains expected resources', () async {
    final Environment environment = Environment.test(
      globals.fs.currentDirectory,
      outputDir: globals.fs.directory('out')..createSync(),
      defines: <String, String>{
        kBuildMode: 'debug',
      },
      processManager: fakeProcessManager,
      artifacts: MockArtifacts(),
      fileSystem: globals.fs,
      logger: globals.logger,
    );
    environment.buildDir.createSync(recursive: true);

    // create pre-requisites.
    environment.buildDir.childFile('app.dill')
      .writeAsStringSync('abcd');
    final Directory hostDirectory = globals.fs.currentDirectory
      .childDirectory(getNameForHostPlatform(getCurrentHostPlatform()))
      ..createSync(recursive: true);
    hostDirectory.childFile('vm_isolate_snapshot.bin').createSync();
    hostDirectory.childFile('isolate_snapshot.bin').createSync();

    await const DebugAndroidApplication().build(environment);

    expect(globals.fs.file(globals.fs.path.join('out', 'flutter_assets', 'isolate_snapshot_data')).existsSync(), true);
    expect(globals.fs.file(globals.fs.path.join('out', 'flutter_assets', 'vm_snapshot_data')).existsSync(), true);
    expect(globals.fs.file(globals.fs.path.join('out', 'flutter_assets', 'kernel_blob.bin')).existsSync(), true);
  });

  testbed.test('debug bundle contains expected resources with bundle SkSL', () async {
    final Environment environment = Environment.test(
      globals.fs.currentDirectory,
      outputDir: globals.fs.directory('out')..createSync(),
      defines: <String, String>{
        kBuildMode: 'debug',
      },
      inputs: <String, String>{
        kBundleSkSLPath: 'bundle.sksl'
      },
      processManager: fakeProcessManager,
      artifacts: MockArtifacts(),
      fileSystem: globals.fs,
      logger: globals.logger,
      engineVersion: '2',
    );
    environment.buildDir.createSync(recursive: true);
    globals.fs.file('bundle.sksl').writeAsStringSync(json.encode(
      <String, Object>{
        'engineRevision': '2',
        'platform': 'android',
        'data': <String, Object>{
          'A': 'B',
        }
      }
    ));

    // create pre-requisites.
    environment.buildDir.childFile('app.dill')
      .writeAsStringSync('abcd');
    final Directory hostDirectory = globals.fs.currentDirectory
      .childDirectory(getNameForHostPlatform(getCurrentHostPlatform()))
      ..createSync(recursive: true);
    hostDirectory.childFile('vm_isolate_snapshot.bin').createSync();
    hostDirectory.childFile('isolate_snapshot.bin').createSync();

    await const DebugAndroidApplication().build(environment);

    expect(globals.fs.file(globals.fs.path.join('out', 'flutter_assets', 'isolate_snapshot_data')), exists);
    expect(globals.fs.file(globals.fs.path.join('out', 'flutter_assets', 'vm_snapshot_data')), exists);
    expect(globals.fs.file(globals.fs.path.join('out', 'flutter_assets', 'kernel_blob.bin')), exists);
    expect(globals.fs.file(globals.fs.path.join('out', 'flutter_assets', 'io.flutter.shaders.json')), exists);
  });

  testbed.test('profile bundle contains expected resources', () async {
    final Environment environment = Environment.test(
      globals.fs.currentDirectory,
      outputDir: globals.fs.directory('out')..createSync(),
      defines: <String, String>{
        kBuildMode: 'profile',
      },
      artifacts: MockArtifacts(),
      processManager: fakeProcessManager,
      fileSystem: globals.fs,
      logger: globals.logger,
    );
    environment.buildDir.createSync(recursive: true);

    // create pre-requisites.
    environment.buildDir.childFile('app.so')
      .writeAsStringSync('abcd');

    await const ProfileAndroidApplication().build(environment);

    expect(globals.fs.file(globals.fs.path.join('out', 'app.so')).existsSync(), true);
  });

  testbed.test('release bundle contains expected resources', () async {
    final Environment environment = Environment.test(
      globals.fs.currentDirectory,
      outputDir: globals.fs.directory('out')..createSync(),
      defines: <String, String>{
        kBuildMode: 'release',
      },
      artifacts: MockArtifacts(),
      processManager: fakeProcessManager,
      fileSystem: globals.fs,
      logger: globals.logger,
    );
    environment.buildDir.createSync(recursive: true);

    // create pre-requisites.
    environment.buildDir.childFile('app.so')
      .writeAsStringSync('abcd');

    await const ReleaseAndroidApplication().build(environment);

    expect(globals.fs.file(globals.fs.path.join('out', 'app.so')).existsSync(), true);
  });

  testbed.test('AndroidAot can build provided target platform', () async {
    fakeProcessManager = FakeProcessManager.list(<FakeCommand>[]);
    final Environment environment = Environment.test(
      globals.fs.currentDirectory,
      outputDir: globals.fs.directory('out')..createSync(),
      defines: <String, String>{
        kBuildMode: 'release',
      },
      artifacts: MockArtifacts(),
      processManager: FakeProcessManager.list(<FakeCommand>[]),
      fileSystem: globals.fs,
      logger: globals.logger,
    );
    fakeProcessManager.addCommand(FakeCommand(command: <String>[
        globals.fs.path.absolute(globals.fs.path.join('android-arm64-release', 'linux-x64', 'gen_snapshot')),
        '--deterministic',
        '--snapshot_kind=app-aot-elf',
        '--elf=${environment.buildDir.childDirectory('arm64-v8a').childFile('app.so').path}',
        '--strip',
        '--no-causal-async-stacks',
        '--lazy-async-stacks',
        environment.buildDir.childFile('app.dill').path,
        ],
      )
    );
    environment.buildDir.createSync(recursive: true);
    environment.buildDir.childFile('app.dill').createSync();
    environment.projectDir.childFile('.packages').writeAsStringSync('\n');
    const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release);

    await androidAot.build(environment);

    expect(fakeProcessManager.hasRemainingExpectations, false);
  }, overrides: <Type, Generator>{
    ProcessManager: () => fakeProcessManager,
  });

  testbed.test('kExtraGenSnapshotOptions passes values to gen_snapshot', () async {
    fakeProcessManager = FakeProcessManager.list(<FakeCommand>[]);
    final Environment environment = Environment.test(
      globals.fs.currentDirectory,
      outputDir: globals.fs.directory('out')..createSync(),
      defines: <String, String>{
        kBuildMode: 'release',
        kExtraGenSnapshotOptions: 'foo,bar,baz=2',
        kTargetPlatform: 'android-arm',
      },
      processManager: fakeProcessManager,
      artifacts: MockArtifacts(),
      fileSystem: globals.fs,
      logger: globals.logger,
    );
    fakeProcessManager.addCommand(
      FakeCommand(command: <String>[
         globals.fs.path.absolute(globals.fs.path.join('android-arm64-release', 'linux-x64', 'gen_snapshot')),
        '--deterministic',
        'foo',
        'bar',
        'baz=2',
        '--snapshot_kind=app-aot-elf',
        '--elf=${environment.buildDir.childDirectory('arm64-v8a').childFile('app.so').path}',
        '--strip',
        '--no-causal-async-stacks',
        '--lazy-async-stacks',
        environment.buildDir.childFile('app.dill').path
      ]));
    environment.buildDir.createSync(recursive: true);
    environment.buildDir.childFile('app.dill').createSync();
    environment.projectDir.childFile('.packages').writeAsStringSync('\n');

    await const AndroidAot(TargetPlatform.android_arm64, BuildMode.release)
      .build(environment);
  }, overrides: <Type, Generator>{
    ProcessManager: () => fakeProcessManager,
  });

  testbed.test('android aot bundle copies so from abi directory', () async {
    final Environment environment = Environment.test(
      globals.fs.currentDirectory,
      outputDir: globals.fs.directory('out')..createSync(),
      defines: <String, String>{
        kBuildMode: 'release',
      },
      processManager: fakeProcessManager,
      artifacts: MockArtifacts(),
      fileSystem: globals.fs,
      logger: globals.logger,
    );
    environment.buildDir.createSync(recursive: true);
    const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release);
    const AndroidAotBundle androidAotBundle = AndroidAotBundle(androidAot);
    // Create required files.
    environment.buildDir
      .childDirectory('arm64-v8a')
      .childFile('app.so')
      .createSync(recursive: true);

    await androidAotBundle.build(environment);

    expect(environment.outputDir
      .childDirectory('arm64-v8a')
      .childFile('app.so').existsSync(), true);
  });
}

class MockArtifacts extends Mock implements Artifacts {}
