// 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:flutter_tools/src/base/error_handling_io.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';

import '../src/common.dart';
import 'test_utils.dart';

void main() {
  const String customLLDBInitFileSchemeSetting =
      r'customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"';
  test(
    'Ensure lldb is added to Xcode project',
    () async {
      final Directory workingDirectory = fileSystem.systemTempDirectory.createTempSync(
        'lldb_test.',
      );
      try {
        final String workingDirectoryPath = workingDirectory.path;
        const String appName = 'lldb_test';

        final ProcessResult createResult = await processManager.run(<String>[
          flutterBin,
          ...getLocalEngineArguments(),
          'create',
          '--org',
          'io.flutter.devicelab',
          '-i',
          'swift',
          appName,
          '--platforms=ios',
        ], workingDirectory: workingDirectory.path);
        expect(
          createResult.exitCode,
          0,
          reason:
              'Failed to create app: \n'
              'stdout: \n${createResult.stdout}\n'
              'stderr: \n${createResult.stderr}\n',
        );

        final String appDirectoryPath = fileSystem.path.join(workingDirectoryPath, appName);

        final File schemeFile = fileSystem
            .directory(appDirectoryPath)
            .childDirectory('ios')
            .childDirectory('Runner.xcodeproj')
            .childDirectory('xcshareddata')
            .childDirectory('xcschemes')
            .childFile('Runner.xcscheme');
        expect(schemeFile.existsSync(), isTrue);

        // Remove customLLDBInitFile from the scheme so we can validate it
        // gets re-added if missing.
        expect(schemeFile.readAsStringSync(), contains(customLLDBInitFileSchemeSetting));
        schemeFile.writeAsStringSync(
          schemeFile.readAsStringSync().replaceAll(customLLDBInitFileSchemeSetting, ''),
        );

        final File lldbInitFile = fileSystem
            .directory(appDirectoryPath)
            .childDirectory('ios')
            .childDirectory('Flutter')
            .childDirectory('ephemeral')
            .childFile('flutter_lldbinit');
        expect(lldbInitFile.existsSync(), isTrue);

        final File lldbPythonFile = fileSystem
            .directory(appDirectoryPath)
            .childDirectory('ios')
            .childDirectory('Flutter')
            .childDirectory('ephemeral')
            .childFile('flutter_lldb_helper.py');
        expect(lldbPythonFile.existsSync(), isTrue);

        // Delete LLDB files so we can verify they get re-added if missing.
        lldbInitFile.deleteSync();
        lldbPythonFile.deleteSync();

        final ProcessResult buildResult = await processManager.run(<String>[
          flutterBin,
          ...getLocalEngineArguments(),
          'build',
          'ios',
        ], workingDirectory: appDirectoryPath);
        expect(
          buildResult.exitCode,
          0,
          reason:
              'Failed to build the app: \n'
              'stdout: \n${buildResult.stdout}\n'
              'stderr: \n${buildResult.stderr}\n',
        );

        expect(schemeFile.readAsStringSync(), contains(customLLDBInitFileSchemeSetting));
        expect(lldbInitFile.existsSync(), isTrue);
        expect(lldbPythonFile.existsSync(), isTrue);
      } finally {
        ErrorHandlingFileSystem.deleteIfExists(workingDirectory, recursive: true);
      }
    },
    skip: !platform.isMacOS, // [intended] Can only build on macOS.
  );

  test(
    'Ensure lldb is added to Xcode project when using flavor',
    () async {
      final Directory workingDirectory = fileSystem.systemTempDirectory.createTempSync(
        'lldb_test.',
      );
      try {
        final String workingDirectoryPath = workingDirectory.path;
        const String appName = 'lldb_test';
        const String flavor = 'vanilla';

        final ProcessResult createResult = await processManager.run(<String>[
          flutterBin,
          ...getLocalEngineArguments(),
          'create',
          '--org',
          'io.flutter.devicelab',
          '-i',
          'swift',
          appName,
          '--platforms=ios',
        ], workingDirectory: workingDirectory.path);
        expect(
          createResult.exitCode,
          0,
          reason:
              'Failed to create app: \n'
              'stdout: \n${createResult.stdout}\n'
              'stderr: \n${createResult.stderr}\n',
        );

        final String appDirectoryPath = fileSystem.path.join(workingDirectoryPath, appName);
        final File schemeFile = fileSystem
            .directory(appDirectoryPath)
            .childDirectory('ios')
            .childDirectory('Runner.xcodeproj')
            .childDirectory('xcshareddata')
            .childDirectory('xcschemes')
            .childFile('Runner.xcscheme');
        expect(schemeFile.existsSync(), isTrue);

        final File pbxprojFile = fileSystem
            .directory(appDirectoryPath)
            .childDirectory('ios')
            .childDirectory('Runner.xcodeproj')
            .childFile('project.pbxproj');
        expect(pbxprojFile.existsSync(), isTrue);

        // Create flavor
        final File flavorSchemeFile = fileSystem
            .directory(appDirectoryPath)
            .childDirectory('ios')
            .childDirectory('Runner.xcodeproj')
            .childDirectory('xcshareddata')
            .childDirectory('xcschemes')
            .childFile('$flavor.xcscheme');
        flavorSchemeFile.createSync(recursive: true);
        flavorSchemeFile.writeAsStringSync(schemeFile.readAsStringSync());

        String pbxprojContents = pbxprojFile.readAsStringSync();
        pbxprojContents = pbxprojContents.replaceAll('97C147071CF9000F007C117D /* Release */,', '''
97C147071CF9000F007C117D /* Release */,
78624EC12D71262400FF7985 /* Release-vanilla */,
''');
        pbxprojContents = pbxprojContents.replaceAll('97C147041CF9000F007C117D /* Release */,', '''
97C147041CF9000F007C117D /* Release */,
78624EC02D71262400FF7985 /* Release-vanilla */,
''');

        pbxprojContents = pbxprojContents.replaceAll(
          '/* Begin XCBuildConfiguration section */',
          r'''
/* Begin XCBuildConfiguration section */
78624EC12D71262400FF7985 /* Release-vanilla */ = {
			isa = XCBuildConfiguration;
			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				CLANG_ENABLE_MODULES = YES;
				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
				ENABLE_BITCODE = NO;
				INFOPLIST_FILE = Runner/Info.plist;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
				);
				PRODUCT_BUNDLE_IDENTIFIER = com.example.lldb_test;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
				SWIFT_VERSION = 5.0;
				VERSIONING_SYSTEM = "apple-generic";
			};
			name = "Release-vanilla";
		};
    		78624EC02D71262400FF7985 /* Release-vanilla */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
				ENABLE_NS_ASSERTIONS = NO;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				ENABLE_USER_SCRIPT_SANDBOXING = NO;
				GCC_C_LANGUAGE_STANDARD = gnu99;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
				MTL_ENABLE_DEBUG_INFO = NO;
				SDKROOT = iphoneos;
				SUPPORTED_PLATFORMS = iphoneos;
				SWIFT_COMPILATION_MODE = wholemodule;
				SWIFT_OPTIMIZATION_LEVEL = "-O";
				TARGETED_DEVICE_FAMILY = "1,2";
				VALIDATE_PRODUCT = YES;
			};
			name = "Release-vanilla";
		};
''',
        );
        pbxprojFile.writeAsStringSync(pbxprojContents);

        // Remove customLLDBInitFile from the flavor's scheme so we can validate
        // it gets re-added later.
        expect(flavorSchemeFile.readAsStringSync(), contains(customLLDBInitFileSchemeSetting));
        flavorSchemeFile.writeAsStringSync(
          flavorSchemeFile.readAsStringSync().replaceAll(customLLDBInitFileSchemeSetting, ''),
        );

        final ProcessResult buildResult = await processManager.run(<String>[
          flutterBin,
          ...getLocalEngineArguments(),
          'build',
          'ios',
          '--config-only',
          '--flavor',
          flavor,
        ], workingDirectory: appDirectoryPath);
        expect(
          buildResult.exitCode,
          0,
          reason:
              'Failed to build config for the app: \n'
              'stdout: \n${buildResult.stdout}\n'
              'stderr: \n${buildResult.stderr}\n',
        );

        expect(flavorSchemeFile.readAsStringSync(), contains(customLLDBInitFileSchemeSetting));

        final File lldbInitFile = fileSystem
            .directory(appDirectoryPath)
            .childDirectory('ios')
            .childDirectory('Flutter')
            .childDirectory('ephemeral')
            .childFile('flutter_lldbinit');
        expect(lldbInitFile.existsSync(), isTrue);

        final File lldbPythonFile = fileSystem
            .directory(appDirectoryPath)
            .childDirectory('ios')
            .childDirectory('Flutter')
            .childDirectory('ephemeral')
            .childFile('flutter_lldb_helper.py');
        expect(lldbPythonFile.existsSync(), isTrue);
      } finally {
        ErrorHandlingFileSystem.deleteIfExists(workingDirectory, recursive: true);
      }
    },
    skip: !platform.isMacOS, // [intended] Can only build on macOS.
  );
}
