// 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/memory.dart';
import 'package:flutter_tools/src/android/deferred_components_prebuild_validator.dart';
import 'package:flutter_tools/src/android/deferred_components_validator.dart';
import 'package:flutter_tools/src/base/deferred_component.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 '../../src/common.dart';
import '../../src/context.dart';

void main() {
  late FileSystem fileSystem;
  late BufferLogger logger;
  late Directory projectDir;
  late Platform platform;
  late Directory flutterRootDir;

  setUp(() {
    fileSystem = MemoryFileSystem.test();
    logger = BufferLogger.test();
    projectDir = fileSystem.directory('/project');
    flutterRootDir = fileSystem.directory('/flutter_root');
    platform = FakePlatform();
  });

  testWithoutContext('No checks passes', () async {
    final validator = DeferredComponentsPrebuildValidator(
      projectDir,
      logger,
      platform,
      exitOnFail: false,
      title: 'test check',
    );
    validator.displayResults();
    validator.attemptToolExit();
    expect(logger.statusText, 'test check passed.\n');
  });

  testWithoutContext('clearTempDir passes', () async {
    final validator = DeferredComponentsPrebuildValidator(
      projectDir,
      logger,
      platform,
      exitOnFail: false,
      title: 'test check',
    );
    validator.displayResults();
    validator.attemptToolExit();

    expect(logger.statusText, 'test check passed.\n');
  });

  testUsingContext(
    'androidComponentSetup build.gradle does not exist',
    () async {
      final Directory templatesDir = flutterRootDir.childDirectory('templates');
      final Directory deferredComponentDir = templatesDir
          .childDirectory('module')
          .childDirectory('android')
          .childDirectory('deferred_component');
      final File buildGradleTemplate = deferredComponentDir.childFile('build.gradle.tmpl');
      final File androidManifestTemplate = deferredComponentDir
          .childDirectory('src')
          .childDirectory('main')
          .childFile('AndroidManifest.xml.tmpl');

      deferredComponentDir.createSync(recursive: true);
      buildGradleTemplate.createSync(recursive: true);
      androidManifestTemplate.createSync(recursive: true);
      buildGradleTemplate.writeAsStringSync(
        'fake build.gradle template {{componentName}}',
        flush: true,
        mode: FileMode.append,
      );
      androidManifestTemplate.writeAsStringSync(
        'fake AndroidManifest.xml template {{componentName}}',
        flush: true,
        mode: FileMode.append,
      );

      final validator = DeferredComponentsPrebuildValidator(
        projectDir,
        logger,
        platform,
        exitOnFail: false,
        title: 'test check',
        templatesDir: templatesDir,
      );
      final Directory componentDir = projectDir
          .childDirectory('android')
          .childDirectory('component1');
      final File file = componentDir
          .childDirectory('src')
          .childDirectory('main')
          .childFile('AndroidManifest.xml');
      if (file.existsSync()) {
        file.deleteSync();
      }
      file.createSync(recursive: true);
      await validator.checkAndroidDynamicFeature(<DeferredComponent>[
        DeferredComponent(name: 'component1'),
      ]);
      validator.displayResults();
      validator.attemptToolExit();

      file.deleteSync();
      expect(logger.statusText.contains('Newly generated android files:\n'), true);
      expect(
        logger.statusText.contains(
          'build/${DeferredComponentsValidator.kDeferredComponentsTempDirectory}/component1/build.gradle\n',
        ),
        true,
      );
    },
    overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.any(),
    },
  );

  testUsingContext(
    'androidComponentSetup AndroidManifest.xml does not exist',
    () async {
      final Directory templatesDir = flutterRootDir.childDirectory('templates');
      final Directory deferredComponentDir = templatesDir
          .childDirectory('module')
          .childDirectory('android')
          .childDirectory('deferred_component');
      final File buildGradleTemplate = deferredComponentDir.childFile('build.gradle.tmpl');
      final File androidManifestTemplate = deferredComponentDir
          .childDirectory('src')
          .childDirectory('main')
          .childFile('AndroidManifest.xml.tmpl');

      deferredComponentDir.createSync(recursive: true);
      buildGradleTemplate.createSync(recursive: true);
      androidManifestTemplate.createSync(recursive: true);
      buildGradleTemplate.writeAsStringSync(
        'fake build.gradle template {{componentName}}',
        flush: true,
        mode: FileMode.append,
      );
      androidManifestTemplate.writeAsStringSync(
        'fake AndroidManifest.xml template {{componentName}}',
        flush: true,
        mode: FileMode.append,
      );

      final validator = DeferredComponentsPrebuildValidator(
        projectDir,
        logger,
        platform,
        exitOnFail: false,
        title: 'test check',
        templatesDir: templatesDir,
      );
      final Directory componentDir = projectDir
          .childDirectory('android')
          .childDirectory('component1');
      final File file = componentDir.childFile('build.gradle');
      if (file.existsSync()) {
        file.deleteSync();
      }
      file.createSync(recursive: true);
      await validator.checkAndroidDynamicFeature(<DeferredComponent>[
        DeferredComponent(name: 'component1'),
      ]);
      validator.displayResults();
      validator.attemptToolExit();

      file.deleteSync();
      expect(logger.statusText.contains('Newly generated android files:\n'), true);
      expect(
        logger.statusText.contains(
          'build/${DeferredComponentsValidator.kDeferredComponentsTempDirectory}/component1/src/main/AndroidManifest.xml\n',
        ),
        true,
      );
    },
    overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.any(),
    },
  );

  testWithoutContext('androidComponentSetup all files exist passes', () async {
    final Directory templatesDir = flutterRootDir
        .childDirectory('templates')
        .childDirectory('deferred_component');
    final File buildGradleTemplate = templatesDir.childFile('build.gradle.tmpl');
    final File androidManifestTemplate = templatesDir
        .childDirectory('src')
        .childDirectory('main')
        .childFile('AndroidManifest.xml.tmpl');
    if (templatesDir.existsSync()) {
      templatesDir.deleteSync(recursive: true);
    }
    buildGradleTemplate.createSync(recursive: true);
    androidManifestTemplate.createSync(recursive: true);
    buildGradleTemplate.writeAsStringSync(
      'fake build.gradle template {{componentName}}',
      flush: true,
      mode: FileMode.append,
    );
    androidManifestTemplate.writeAsStringSync(
      'fake AndroidManifest.xml template {{componentName}}',
      flush: true,
      mode: FileMode.append,
    );

    final validator = DeferredComponentsPrebuildValidator(
      projectDir,
      logger,
      platform,
      exitOnFail: false,
      title: 'test check',
      templatesDir: templatesDir,
    );
    final Directory componentDir = projectDir
        .childDirectory('android')
        .childDirectory('component1');
    final File buildGradle = componentDir.childFile('build.gradle');
    if (buildGradle.existsSync()) {
      buildGradle.deleteSync();
    }
    buildGradle.createSync(recursive: true);
    final File manifest = componentDir
        .childDirectory('src')
        .childDirectory('main')
        .childFile('AndroidManifest.xml');
    if (manifest.existsSync()) {
      manifest.deleteSync();
    }
    manifest.createSync(recursive: true);
    await validator.checkAndroidDynamicFeature(<DeferredComponent>[
      DeferredComponent(name: 'component1'),
    ]);
    validator.displayResults();
    validator.attemptToolExit();

    manifest.deleteSync();
    buildGradle.deleteSync();
    expect(logger.statusText, 'test check passed.\n');
  });

  testWithoutContext('androidStringMapping creates new file', () async {
    final validator = DeferredComponentsPrebuildValidator(
      projectDir,
      logger,
      platform,
      exitOnFail: false,
      title: 'test check',
    );
    final Directory baseModuleDir = projectDir.childDirectory('android').childDirectory('app');
    final File stringRes = baseModuleDir
        .childDirectory('src')
        .childDirectory('main')
        .childDirectory('res')
        .childDirectory('values')
        .childFile('strings.xml');
    if (stringRes.existsSync()) {
      stringRes.deleteSync();
    }
    final File manifest = baseModuleDir
        .childDirectory('src')
        .childDirectory('main')
        .childFile('AndroidManifest.xml');
    if (manifest.existsSync()) {
      manifest.deleteSync();
    }
    manifest.createSync(recursive: true);
    manifest.writeAsStringSync(
      '''
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.splitaot">
    <application
        android:name="io.flutter.app.FlutterPlayStoreSplitApplication"
        android:label="splitaot"
        android:extractNativeLibs="false">
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:windowSoftInputMode="adjustResize">
        </activity>
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
        <meta-data
            android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping"
            android:value="invalidmapping" />
    </application>
</manifest>
''',
      flush: true,
      mode: FileMode.append,
    );
    validator.checkAndroidResourcesStrings(<DeferredComponent>[
      DeferredComponent(name: 'component1', libraries: <String>['lib2']),
      DeferredComponent(name: 'component2', libraries: <String>['lib1', 'lib4']),
    ]);
    validator.displayResults();
    validator.attemptToolExit();

    expect(logger.statusText.contains('Modified android files:\n'), false);
    expect(logger.statusText.contains('Newly generated android files:\n'), true);
    expect(
      logger.statusText.contains(
        'build/${DeferredComponentsValidator.kDeferredComponentsTempDirectory}/app/src/main/res/values/strings.xml\n',
      ),
      true,
    );

    final File stringsOutput = projectDir
        .childDirectory('build')
        .childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory)
        .childDirectory('app')
        .childDirectory('src')
        .childDirectory('main')
        .childDirectory('res')
        .childDirectory('values')
        .childFile('strings.xml');
    expect(stringsOutput.existsSync(), true);
    expect(
      stringsOutput.readAsStringSync().contains(
        '<string name="component1Name">component1</string>',
      ),
      true,
    );
    expect(
      stringsOutput.readAsStringSync().contains(
        '<string name="component2Name">component2</string>',
      ),
      true,
    );
  });

  testWithoutContext('androidStringMapping modifies strings file', () async {
    final validator = DeferredComponentsPrebuildValidator(
      projectDir,
      logger,
      platform,
      exitOnFail: false,
      title: 'test check',
    );
    final Directory baseModuleDir = projectDir.childDirectory('android').childDirectory('app');
    final File stringRes = baseModuleDir
        .childDirectory('src')
        .childDirectory('main')
        .childDirectory('res')
        .childDirectory('values')
        .childFile('strings.xml');
    if (stringRes.existsSync()) {
      stringRes.deleteSync();
    }
    stringRes.createSync(recursive: true);
    stringRes.writeAsStringSync(
      '''
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="component1Name">component1</string>
</resources>

''',
      flush: true,
      mode: FileMode.append,
    );
    validator.checkAndroidResourcesStrings(<DeferredComponent>[
      DeferredComponent(name: 'component1', libraries: <String>['lib2']),
      DeferredComponent(name: 'component2', libraries: <String>['lib1', 'lib4']),
    ]);
    validator.displayResults();
    validator.attemptToolExit();

    expect(logger.statusText.contains('Newly generated android files:\n'), false);
    expect(logger.statusText.contains('Modified android files:\n'), true);
    expect(
      logger.statusText.contains(
        'build/${DeferredComponentsValidator.kDeferredComponentsTempDirectory}/app/src/main/res/values/strings.xml\n',
      ),
      true,
    );

    final File stringsOutput = projectDir
        .childDirectory('build')
        .childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory)
        .childDirectory('app')
        .childDirectory('src')
        .childDirectory('main')
        .childDirectory('res')
        .childDirectory('values')
        .childFile('strings.xml');
    expect(stringsOutput.existsSync(), true);
    expect(
      stringsOutput.readAsStringSync().contains(
        '<string name="component1Name">component1</string>',
      ),
      true,
    );
    expect(
      stringsOutput.readAsStringSync().contains(
        '<string name="component2Name">component2</string>',
      ),
      true,
    );
  });
}
