// 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:meta/meta.dart';

import '../../base/file_system.dart';
import '../../base/project_migrator.dart';
import '../../base/version.dart';
import '../../project.dart';
import '../android_studio.dart';
import '../gradle_utils.dart';
import '../java.dart';

// Android Studio 2022.2 "Flamingo" is the first to bundle a Java 17 JDK.
// Previous versions bundled a Java 11 JDK.
@visibleForTesting
final Version androidStudioFlamingo = Version(2022, 2, 0);
@visibleForTesting
const String gradleVersion7_6_1 = r'7.6.1';

// String that can be placed in the gradle-wrapper.properties to opt out of this
// migrator.
@visibleForTesting
const String optOutFlag = 'NoFlutterGradleWrapperUpgrade';
// Only the major version matters.
final Version flamingoBundledJava = Version(17, 0, 0);

// These gradle versions were chosen because they
// 1. Were output by 'flutter create' at some point in flutter's history and
// 2. Are less than 7.3, the lowest supported gradle version for JDK 17
const List<String> gradleVersionsToUpgradeFrom =
    <String>['5.6.2', '6.7'];

// Define log messages as constants to re-use in testing.
@visibleForTesting
const String gradleWrapperNotFound =
    'gradle-wrapper.properties not found, skipping Gradle-Java version compatibility check.';
@visibleForTesting
const String androidStudioNotFound =
    'Android Studio version could not be detected, '
    'skipping Gradle-Java version compatibility check.';
@visibleForTesting
const String androidStudioVersionBelowFlamingo =
    'Version of Android Studio is less than Flamingo (the first impacted version),'
    ' no migration attempted.';
@visibleForTesting
const String javaVersionNot17 =
    'Version of Java is different than impacted version, no migration attempted.';
@visibleForTesting
const String javaVersionNotFound =
    'Version of Java not found, no migration attempted.';
@visibleForTesting
const String conflictDetected = 'Conflict detected between versions of Android Studio '
    'and Gradle, upgrading Gradle version from current to 7.4';
@visibleForTesting
const String gradleVersionNotFound = 'Failed to parse Gradle version from distribution url, '
    'skipping Gradle-Java version compatibility check.';
@visibleForTesting
const String optOutFlagEnabled = 'Skipping Android Studio Java-Gradle compatibility '
    "because opt out flag: '$optOutFlag' is enabled in gradle-wrapper.properties file.";
@visibleForTesting
const String errorWhileMigrating = 'Encountered an error while attempting Gradle-Java '
    'version compatibility check, skipping migration attempt. Error was: ';


/// Migrate to a newer version of Gradle when the existing one does not support
/// the version of Java provided by the detected Android Studio version.
///
/// For more info see the Gradle-Java compatibility matrix:
/// https://docs.gradle.org/current/userguide/compatibility.html
class AndroidStudioJavaGradleConflictMigration extends ProjectMigrator {
  AndroidStudioJavaGradleConflictMigration(
    super.logger,
    {required AndroidProject project,
    AndroidStudio? androidStudio,
    required Java? java,
  }) : _gradleWrapperPropertiesFile = getGradleWrapperFile(project.hostAppGradleRoot),
       _androidStudio = androidStudio,
       _java = java;

  final File _gradleWrapperPropertiesFile;
  final AndroidStudio? _androidStudio;
  final Java? _java;

  @override
  void migrate() {
    try {
      if (!_gradleWrapperPropertiesFile.existsSync()) {
        logger.printTrace(gradleWrapperNotFound);
        return;
      }

      if (_androidStudio == null || _androidStudio!.version == null) {
        logger.printTrace(androidStudioNotFound);
        return;
      } else if (_androidStudio!.version!.major < androidStudioFlamingo.major) {
        logger.printTrace(androidStudioVersionBelowFlamingo);
        return;
      }

      if (_java?.version == null) {
        logger.printTrace(javaVersionNotFound);
        return;
      }

      if (_java!.version!.major != flamingoBundledJava.major) {
        logger.printTrace(javaVersionNot17);
        return;
      }

      processFileLines(_gradleWrapperPropertiesFile);
    } on Exception catch (e) {
      logger.printTrace(errorWhileMigrating + e.toString());
    } on Error catch (e) {
      logger.printTrace(errorWhileMigrating + e.toString());
    }
  }

  @override
  String migrateFileContents(String fileContents) {
    if (fileContents.contains(optOutFlag)) {
      logger.printTrace(optOutFlagEnabled);
      return fileContents;
    }
    final RegExpMatch? gradleDistributionUrl = gradleOrgVersionMatch.firstMatch(fileContents);
    if (gradleDistributionUrl == null
        || gradleDistributionUrl.groupCount < 1
        || gradleDistributionUrl[1] == null) {
      logger.printTrace(gradleVersionNotFound);
      return fileContents;
    }
    final String existingVersionString = gradleDistributionUrl[1]!;
    if (gradleVersionsToUpgradeFrom.contains(existingVersionString)) {
      logger.printStatus('Conflict detected between Android Studio Java version and Gradle version, '
          'upgrading Gradle version from $existingVersionString to $gradleVersion7_6_1.');
      final String? gradleDistributionUrlString = gradleDistributionUrl.group(0);
      if (gradleDistributionUrlString != null) {
        final String upgradedDistributionUrl =
          gradleDistributionUrlString.replaceAll(existingVersionString, gradleVersion7_6_1);
        fileContents = fileContents.replaceFirst(gradleOrgVersionMatch, upgradedDistributionUrl);
      } else {
        logger.printTrace(gradleVersionNotFound);
      }
    }
    return fileContents;
  }
}
