// 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 DeferredComponentsPrebuildValidator 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 DeferredComponentsPrebuildValidator 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 DeferredComponentsPrebuildValidator 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 DeferredComponentsPrebuildValidator 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 DeferredComponentsPrebuildValidator 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 DeferredComponentsPrebuildValidator 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 DeferredComponentsPrebuildValidator 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);
  });
}
