// 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 'dart:io' as io;

import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/globals.dart' as globals;

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

const String xcodeBackendPath = 'bin/xcode_backend.sh';
const String xcodeBackendErrorHeader = '========================================================================';

// Acceptable $CONFIGURATION/$FLUTTER_BUILD_MODE values should be debug, profile, or release
const Map<String, String> unknownConfiguration = <String, String>{
  'CONFIGURATION': 'Custom',
};

// $FLUTTER_BUILD_MODE will override $CONFIGURATION
const Map<String, String> unknownFlutterBuildMode = <String, String>{
  'FLUTTER_BUILD_MODE': 'Custom',
  'CONFIGURATION': 'Debug',
};

void main() {
  Future<void> expectXcodeBackendFails(Map<String, String> environment) async {
    final ProcessResult result = await Process.run(
      xcodeBackendPath,
      <String>['build'],
      environment: environment,
    );
    expect(result.stderr, startsWith(xcodeBackendErrorHeader));
    expect(result.exitCode, isNot(0));
  }

  test('Xcode backend fails with no arguments', () async {
    final ProcessResult result = await Process.run(
      xcodeBackendPath,
      <String>[],
      environment: <String, String>{
        'SOURCE_ROOT': '../examples/hello_world',
        'FLUTTER_ROOT': '../..',
      },
    );
    expect(result.stderr, startsWith('error: Your Xcode project is incompatible with this version of Flutter.'));
    expect(result.exitCode, isNot(0));
  }, skip: !io.Platform.isMacOS); // [intended] requires macos toolchain.

  test('Xcode backend fails for on unsupported configuration combinations', () async {
    await expectXcodeBackendFails(unknownConfiguration);
    await expectXcodeBackendFails(unknownFlutterBuildMode);
  }, skip: !io.Platform.isMacOS); // [intended] requires macos toolchain.

  test('Xcode backend warns archiving a non-release build.', () async {
    final ProcessResult result = await Process.run(
      xcodeBackendPath,
      <String>['build'],
      environment: <String, String>{
        'CONFIGURATION': 'Debug',
        'ACTION': 'install',
      },
    );
    expect(result.stdout, contains('warning: Flutter archive not built in Release mode.'));
    expect(result.exitCode, isNot(0));
  }, skip: !io.Platform.isMacOS); // [intended] requires macos toolchain.

  group('vmService Bonjour service keys', () {
    late Directory buildDirectory;
    late File infoPlist;

    setUp(() {
      buildDirectory = globals.fs.systemTempDirectory.createTempSync('flutter_tools_xcode_backend_test.');
      infoPlist = buildDirectory.childFile('Info.plist');
    });

    test('handles when the Info.plist is missing', () async {
      final ProcessResult result = await Process.run(
        xcodeBackendPath,
        <String>['test_vm_service_bonjour_service'],
        environment: <String, String>{
          'CONFIGURATION': 'Debug',
          'BUILT_PRODUCTS_DIR': buildDirectory.path,
          'INFOPLIST_PATH': 'Info.plist',
        },
      );
      expect(result, const ProcessResultMatcher(stdoutPattern: 'Info.plist does not exist.'));
    });

    const String emptyPlist = '''
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
</dict>
</plist>''';

    test('does not add keys in Release', () async {
      infoPlist.writeAsStringSync(emptyPlist);

      final ProcessResult result = await Process.run(
        xcodeBackendPath,
        <String>['test_vm_service_bonjour_service'],
        environment: <String, String>{
          'CONFIGURATION': 'Release',
          'BUILT_PRODUCTS_DIR': buildDirectory.path,
          'INFOPLIST_PATH': 'Info.plist',
        },
      );

      final String actualInfoPlist = infoPlist.readAsStringSync();
      expect(actualInfoPlist, isNot(contains('NSBonjourServices')));
      expect(actualInfoPlist, isNot(contains('dartVmService')));
      expect(actualInfoPlist, isNot(contains('NSLocalNetworkUsageDescription')));

      expect(result, const ProcessResultMatcher());
    });

    for (final String buildConfiguration in <String>['Debug', 'Profile']) {
      test('add keys in $buildConfiguration', () async {
        infoPlist.writeAsStringSync(emptyPlist);

        final ProcessResult result = await Process.run(
          xcodeBackendPath,
          <String>['test_vm_service_bonjour_service'],
          environment: <String, String>{
            'CONFIGURATION': buildConfiguration,
            'BUILT_PRODUCTS_DIR': buildDirectory.path,
            'INFOPLIST_PATH': 'Info.plist',
          },
        );

        final String actualInfoPlist = infoPlist.readAsStringSync();
        expect(actualInfoPlist, contains('NSBonjourServices'));
        expect(actualInfoPlist, contains('dartVmService'));
        expect(actualInfoPlist, contains('NSLocalNetworkUsageDescription'));

        expect(result, const ProcessResultMatcher());
      });
    }

    test('adds to existing Bonjour services, does not override network usage description', () async {
      infoPlist.writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>NSBonjourServices</key>
	<array>
		<string>_bogus._tcp</string>
	</array>
	<key>NSLocalNetworkUsageDescription</key>
	<string>Don't override this</string>
</dict>
</plist>''');

      final ProcessResult result = await Process.run(
        xcodeBackendPath,
        <String>['test_vm_service_bonjour_service'],
        environment: <String, String>{
          'CONFIGURATION': 'Debug',
          'BUILT_PRODUCTS_DIR': buildDirectory.path,
          'INFOPLIST_PATH': 'Info.plist',
        },
      );

      expect(infoPlist.readAsStringSync(), '''
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>NSBonjourServices</key>
	<array>
		<string>_dartVmService._tcp</string>
		<string>_bogus._tcp</string>
	</array>
	<key>NSLocalNetworkUsageDescription</key>
	<string>Don't override this</string>
</dict>
</plist>
''');
      expect(result, const ProcessResultMatcher());
    });

    test('does not add bonjour settings when port publication is disabled', () async {
      infoPlist.writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
</dict>
</plist>''');

      final ProcessResult result = await Process.run(
        xcodeBackendPath,
        <String>['test_vm_service_bonjour_service'],
        environment: <String, String>{
          'CONFIGURATION': 'Debug',
          'BUILT_PRODUCTS_DIR': buildDirectory.path,
          'INFOPLIST_PATH': 'Info.plist',
          'DISABLE_PORT_PUBLICATION': 'YES',
        },
      );

      expect(infoPlist.readAsStringSync().contains('NSBonjourServices'), isFalse);
      expect(infoPlist.readAsStringSync().contains('NSLocalNetworkUsageDescription'), isFalse);
      expect(result, const ProcessResultMatcher());
    });
  }, skip: !io.Platform.isMacOS); // [intended] requires macos toolchain.
}