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

import 'package:file/file.dart';
import 'package:flutter_tools/src/android/gradle_utils.dart' show getGradlewFileName;
import 'package:flutter_tools/src/base/io.dart';
import 'package:xml/xml.dart';

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

final deeplinkFlagMetaData = XmlElement(XmlName('meta-data'), <XmlAttribute>[
  XmlAttribute(XmlName('name', 'android'), 'flutter_deeplinking_enabled'),
  XmlAttribute(XmlName('value', 'android'), 'true'),
]);
final pureHttpIntentFilter = XmlElement(
  XmlName('intent-filter'),
  <XmlAttribute>[XmlAttribute(XmlName('autoVerify', 'android'), 'true')],
  <XmlElement>[
    XmlElement(XmlName('action'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.action.VIEW'),
    ]),
    XmlElement(XmlName('category'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.category.DEFAULT'),
    ]),
    XmlElement(XmlName('category'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.category.BROWSABLE'),
    ]),
    XmlElement(XmlName('data'), <XmlAttribute>[
      XmlAttribute(XmlName('scheme', 'android'), 'http'),
      XmlAttribute(XmlName('host', 'android'), 'pure-http.com'),
    ]),
  ],
);

final nonHttpIntentFilter = XmlElement(
  XmlName('intent-filter'),
  <XmlAttribute>[XmlAttribute(XmlName('autoVerify', 'android'), 'true')],
  <XmlElement>[
    XmlElement(XmlName('action'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.action.VIEW'),
    ]),
    XmlElement(XmlName('category'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.category.DEFAULT'),
    ]),
    XmlElement(XmlName('category'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.category.BROWSABLE'),
    ]),
    XmlElement(XmlName('data'), <XmlAttribute>[
      XmlAttribute(XmlName('scheme', 'android'), 'custom'),
      XmlAttribute(XmlName('host', 'android'), 'custom.com'),
    ]),
  ],
);

final hybridIntentFilter = XmlElement(
  XmlName('intent-filter'),
  <XmlAttribute>[XmlAttribute(XmlName('autoVerify', 'android'), 'true')],
  <XmlElement>[
    XmlElement(XmlName('action'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.action.VIEW'),
    ]),
    XmlElement(XmlName('category'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.category.DEFAULT'),
    ]),
    XmlElement(XmlName('category'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.category.BROWSABLE'),
    ]),
    XmlElement(XmlName('data'), <XmlAttribute>[
      XmlAttribute(XmlName('scheme', 'android'), 'custom'),
      XmlAttribute(XmlName('host', 'android'), 'hybrid.com'),
    ]),
    XmlElement(XmlName('data'), <XmlAttribute>[XmlAttribute(XmlName('scheme', 'android'), 'http')]),
  ],
);

final nonAutoVerifyIntentFilter = XmlElement(
  XmlName('intent-filter'),
  <XmlAttribute>[],
  <XmlElement>[
    XmlElement(XmlName('action'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.action.VIEW'),
    ]),
    XmlElement(XmlName('category'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.category.DEFAULT'),
    ]),
    XmlElement(XmlName('category'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.category.BROWSABLE'),
    ]),
    XmlElement(XmlName('data'), <XmlAttribute>[
      XmlAttribute(XmlName('scheme', 'android'), 'http'),
      XmlAttribute(XmlName('host', 'android'), 'non-auto-verify.com'),
    ]),
  ],
);
final nonActionIntentFilter = XmlElement(
  XmlName('intent-filter'),
  <XmlAttribute>[XmlAttribute(XmlName('autoVerify', 'android'), 'true')],
  <XmlElement>[
    XmlElement(XmlName('category'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.category.DEFAULT'),
    ]),
    XmlElement(XmlName('category'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.category.BROWSABLE'),
    ]),
    XmlElement(XmlName('data'), <XmlAttribute>[
      XmlAttribute(XmlName('scheme', 'android'), 'http'),
      XmlAttribute(XmlName('host', 'android'), 'non-action.com'),
    ]),
  ],
);
final nonDefaultCategoryIntentFilter = XmlElement(
  XmlName('intent-filter'),
  <XmlAttribute>[XmlAttribute(XmlName('autoVerify', 'android'), 'true')],
  <XmlElement>[
    XmlElement(XmlName('action'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.action.VIEW'),
    ]),
    XmlElement(XmlName('category'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.category.BROWSABLE'),
    ]),
    XmlElement(XmlName('data'), <XmlAttribute>[
      XmlAttribute(XmlName('scheme', 'android'), 'http'),
      XmlAttribute(XmlName('host', 'android'), 'non-default-category.com'),
    ]),
  ],
);
final nonBrowsableCategoryIntentFilter = XmlElement(
  XmlName('intent-filter'),
  <XmlAttribute>[XmlAttribute(XmlName('autoVerify', 'android'), 'true')],
  <XmlElement>[
    XmlElement(XmlName('action'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.action.VIEW'),
    ]),
    XmlElement(XmlName('category'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.category.DEFAULT'),
    ]),
    XmlElement(XmlName('data'), <XmlAttribute>[
      XmlAttribute(XmlName('scheme', 'android'), 'http'),
      XmlAttribute(XmlName('host', 'android'), 'non-browsable-category.com'),
    ]),
  ],
);
final nonSchemeCategoryIntentFilter = XmlElement(
  XmlName('intent-filter'),
  <XmlAttribute>[XmlAttribute(XmlName('autoVerify', 'android'), 'true')],
  <XmlElement>[
    XmlElement(XmlName('action'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.action.VIEW'),
    ]),
    XmlElement(XmlName('category'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.category.DEFAULT'),
    ]),
    XmlElement(XmlName('category'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.category.BROWSABLE'),
    ]),
    XmlElement(XmlName('data'), <XmlAttribute>[
      XmlAttribute(XmlName('host', 'android'), 'non-browsable-category.com'),
    ]),
  ],
);
final nonHostCategoryIntentFilter = XmlElement(
  XmlName('intent-filter'),
  <XmlAttribute>[XmlAttribute(XmlName('autoVerify', 'android'), 'true')],
  <XmlElement>[
    XmlElement(XmlName('action'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.action.VIEW'),
    ]),
    XmlElement(XmlName('category'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.category.DEFAULT'),
    ]),
    XmlElement(XmlName('category'), <XmlAttribute>[
      XmlAttribute(XmlName('name', 'android'), 'android.intent.category.BROWSABLE'),
    ]),
    XmlElement(XmlName('data'), <XmlAttribute>[
      XmlAttribute(XmlName('scheme', 'android'), 'http'),
      XmlAttribute(XmlName('path', 'android'), '/path1'),
    ]),
  ],
);

void main() {
  late Directory tempDir;

  setUp(() async {
    tempDir = createResolvedTempDirectorySync('run_test.');
  });

  tearDown(() async {
    tryToDelete(tempDir);
  });

  void testDeeplink(
    dynamic deeplink,
    String? scheme,
    String? host,
    String path, {
    required bool hasAutoVerify,
    required bool hasActionView,
    required bool hasDefaultCategory,
    required bool hasBrowsableCategory,
  }) {
    deeplink as Map<String, dynamic>;
    expect(deeplink['scheme'], scheme);
    expect(deeplink['host'], host);
    expect(deeplink['path'], path);
    final intentFilterCheck = deeplink['intentFilterCheck'] as Map<String, dynamic>;
    expect(intentFilterCheck['hasAutoVerify'], hasAutoVerify);
    expect(intentFilterCheck['hasActionView'], hasActionView);
    expect(intentFilterCheck['hasDefaultCategory'], hasDefaultCategory);
    expect(intentFilterCheck['hasBrowsableCategory'], hasBrowsableCategory);
  }

  testWithoutContext(
    'gradle task outputs<mode>AppLinkSettings works when a project has app links',
    () async {
      // Create a new flutter project.
      ProcessResult result = await processManager.run(<String>[
        flutterBin,
        'create',
        tempDir.path,
        '--project-name=testapp',
      ], workingDirectory: tempDir.path);
      printOnFailure('Created testapp in $tempDir');
      expect(result, const ProcessResultMatcher());
      // Adds intent filters for app links
      final String androidManifestPath = fileSystem.path.join(
        tempDir.path,
        'android',
        'app',
        'src',
        'main',
        'AndroidManifest.xml',
      );
      final androidManifestFile = io.File(androidManifestPath);
      final androidManifest = XmlDocument.parse(androidManifestFile.readAsStringSync());
      final XmlElement activity = androidManifest.findAllElements('activity').first;
      activity.children.add(deeplinkFlagMetaData);
      activity.children.add(pureHttpIntentFilter);
      activity.children.add(nonHttpIntentFilter);
      activity.children.add(hybridIntentFilter);
      activity.children.add(nonAutoVerifyIntentFilter);
      activity.children.add(nonActionIntentFilter);
      activity.children.add(nonDefaultCategoryIntentFilter);
      activity.children.add(nonBrowsableCategoryIntentFilter);
      activity.children.add(nonSchemeCategoryIntentFilter);
      activity.children.add(nonHostCategoryIntentFilter);
      androidManifestFile.writeAsStringSync(androidManifest.toString(), flush: true);

      // Ensure that gradle files exists from templates.
      result = await processManager.run(<String>[
        flutterBin,
        'build',
        'apk',
        '--config-only',
      ], workingDirectory: tempDir.path);
      expect(result, const ProcessResultMatcher());

      final Directory androidApp = tempDir.childDirectory('android');
      final io.File fileDump = tempDir
          .childDirectory('build')
          .childDirectory('app')
          .childFile('app-link-settings-debug.json');
      result = await processManager.run(<String>[
        '.${platform.pathSeparator}${getGradlewFileName(platform)}',
        ...getLocalEngineArguments(),
        '-q', // quiet output.
        '-PoutputPath=${fileDump.path}',
        'outputDebugAppLinkSettings',
      ], workingDirectory: androidApp.path);

      expect(result, const ProcessResultMatcher());
      expect(fileDump.existsSync(), true);
      final json = jsonDecode(fileDump.readAsStringSync()) as Map<String, dynamic>;
      expect(json['applicationId'], 'com.example.testapp');
      expect(json['deeplinkingFlagEnabled'], true);
      final deeplinks = json['deeplinks']! as List<dynamic>;
      expect(deeplinks.length, 10);
      testDeeplink(
        deeplinks[0],
        'http',
        'pure-http.com',
        '.*',
        hasAutoVerify: true,
        hasActionView: true,
        hasDefaultCategory: true,
        hasBrowsableCategory: true,
      );
      testDeeplink(
        deeplinks[1],
        'custom',
        'custom.com',
        '.*',
        hasAutoVerify: true,
        hasActionView: true,
        hasDefaultCategory: true,
        hasBrowsableCategory: true,
      );
      testDeeplink(
        deeplinks[2],
        'custom',
        'hybrid.com',
        '.*',
        hasAutoVerify: true,
        hasActionView: true,
        hasDefaultCategory: true,
        hasBrowsableCategory: true,
      );
      testDeeplink(
        deeplinks[3],
        'http',
        'hybrid.com',
        '.*',
        hasAutoVerify: true,
        hasActionView: true,
        hasDefaultCategory: true,
        hasBrowsableCategory: true,
      );
      testDeeplink(
        deeplinks[4],
        'http',
        'non-auto-verify.com',
        '.*',
        hasAutoVerify: false,
        hasActionView: true,
        hasDefaultCategory: true,
        hasBrowsableCategory: true,
      );
      testDeeplink(
        deeplinks[5],
        'http',
        'non-action.com',
        '.*',
        hasAutoVerify: true,
        hasActionView: false,
        hasDefaultCategory: true,
        hasBrowsableCategory: true,
      );
      testDeeplink(
        deeplinks[6],
        'http',
        'non-default-category.com',
        '.*',
        hasAutoVerify: true,
        hasActionView: true,
        hasDefaultCategory: false,
        hasBrowsableCategory: true,
      );
      testDeeplink(
        deeplinks[7],
        'http',
        'non-browsable-category.com',
        '.*',
        hasAutoVerify: true,
        hasActionView: true,
        hasDefaultCategory: true,
        hasBrowsableCategory: false,
      );
      testDeeplink(
        deeplinks[8],
        null,
        'non-browsable-category.com',
        '.*',
        hasAutoVerify: true,
        hasActionView: true,
        hasDefaultCategory: true,
        hasBrowsableCategory: true,
      );
      testDeeplink(
        deeplinks[9],
        'http',
        null,
        '/path1',
        hasAutoVerify: true,
        hasActionView: true,
        hasDefaultCategory: true,
        hasBrowsableCategory: true,
      );
    },
  );

  testWithoutContext(
    'gradle task outputs<mode>AppLinkSettings works when a project does not have app link and the flutter_deeplinking_enabled flag',
    () async {
      // Create a new flutter project.
      ProcessResult result = await processManager.run(<String>[
        flutterBin,
        'create',
        tempDir.path,
        '--project-name=testapp',
      ], workingDirectory: tempDir.path);
      expect(result, const ProcessResultMatcher());

      // Ensure that gradle files exists from templates.
      result = await processManager.run(<String>[
        flutterBin,
        'build',
        'apk',
        '--config-only',
      ], workingDirectory: tempDir.path);
      expect(result, const ProcessResultMatcher());

      final Directory androidApp = tempDir.childDirectory('android');
      final io.File fileDump = tempDir
          .childDirectory('build')
          .childDirectory('app')
          .childFile('app-link-settings-debug.json');
      result = await processManager.run(<String>[
        '.${platform.pathSeparator}${getGradlewFileName(platform)}',
        ...getLocalEngineArguments(),
        '-q', // quiet output.
        '-PoutputPath=${fileDump.path}',
        'outputDebugAppLinkSettings',
      ], workingDirectory: androidApp.path);

      expect(result, const ProcessResultMatcher());
      expect(fileDump.existsSync(), true);
      final json = jsonDecode(fileDump.readAsStringSync()) as Map<String, dynamic>;
      expect(json['applicationId'], 'com.example.testapp');
      expect(json['deeplinkingFlagEnabled'], false);
      final deeplinks = json['deeplinks']! as List<dynamic>;
      expect(deeplinks.length, 0);
    },
  );
}
