// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

@Tags(['ff'])
@TestOn('vm')
library webdriver.support.firefox_profile_test;

import 'dart:convert' show base64, Encoding, utf8;
import 'dart:io' as io;

import 'package:archive/archive_io.dart' as archive;
import 'package:test/test.dart';
import 'package:webdriver/async_core.dart';
import 'package:webdriver/support/firefox_profile.dart';

void main() {
  group('Firefox profile', () {
    test('parse and serialize string value with quotes', () {
      const value = r'user_pref("extensions.xpiState", '
          r'"{\"app-global\":{\"{972ce4c6-'
          r'7e08-4474-a285-3208198ce6fd}\":{\"d\":\"/opt/firefox/browser/'
          r'extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}\",\"e\":true,\'
          r'"v\":\"40.0\",\"st\":1439535413000,\"mt\":1438968709000}}}");';
      final option = PrefsOption.parse(value);
      expect(option, isA<StringOption>());
      expect(option.asPrefString, value);
    });

    test('parse and serialize string value with backslash', () {
      const value = 'user_pref("browser.cache.disk.parent_directory", '
          r'"\\\\volume\\web\\cache\\mz");';
      final option = PrefsOption.parse(value);
      expect(option, isA<StringOption>());
      expect(option.asPrefString, value);
    });

    test('parse and serialize integer value', () {
      const value = 'user_pref("browser.cache.frecency_experiment", 3);';
      final option = PrefsOption.parse(value);
      expect(option, isA<IntegerOption>());
      expect(option.asPrefString, value);
    });

    test('parse and serialize negative integer value', () {
      const value = 'user_pref("browser.cache.frecency_experiment", -3);';
      final option = PrefsOption.parse(value);
      expect(option, isA<IntegerOption>());
      expect(option.asPrefString, value);
    });

    test('parse and serialize boolean true', () {
      const value =
          'user_pref("browser.cache.disk.smart_size.first_run", true);';
      final option = PrefsOption.parse(value);
      expect(option, isA<BooleanOption>());
      expect(option.asPrefString, value);
    });

    test('parse and serialize boolean false', () {
      const value =
          'user_pref("browser.cache.disk.smart_size.first_run", false);';
      final option = PrefsOption.parse(value);
      expect(option, isA<BooleanOption>());
      expect(option.asPrefString, value);
    });

    test('parse boolean uppercase True', () {
      const value =
          'user_pref("browser.cache.disk.smart_size.first_run", True);';
      final option = PrefsOption.parse(value);
      expect(option, isA<BooleanOption>());
      expect(option.value, true);
    });

    test('added value should be in prefs', () {
      final profile = FirefoxProfile();
      final option =
          PrefsOption('browser.bookmarks.restore_default_bookmarks', false);

      expect(profile.setOption(option), true);

      expect(
          profile.userPrefs,
          anyElement((PrefsOption e) =>
              e.name == option.name && e.value == option.value));
    });

    test('overriding locked value should be ignored', () {
      final profile = FirefoxProfile();
      final lockedOption = PrefsOption('javascript.enabled', false);
      final lockedOptionOrig =
          profile.prefs.firstWhere((e) => e.name == lockedOption.name);
      expect(lockedOption.value, isNot(lockedOptionOrig.value));

      expect(profile.setOption(lockedOption), false);

      expect(profile.userPrefs, isNot(anyElement(lockedOption)));
      expect(profile.prefs.firstWhere((e) => e.name == lockedOption.name).value,
          lockedOptionOrig.value);
    });

    test('removing locked value should be ignored', () {
      final profile = FirefoxProfile();
      final lockedOption = PrefsOption('javascript.enabled', false);
      final lockedOptionOrig =
          profile.prefs.firstWhere((e) => e.name == lockedOption.name);
      expect(lockedOption.value, isNot(lockedOptionOrig.value));

      expect(profile.removeOption(lockedOption.name), false);

      expect(profile.userPrefs, isNot(anyElement(lockedOption)));
      expect(profile.prefs.firstWhere((e) => e.name == lockedOption.name).value,
          lockedOptionOrig.value);
    });

    test('encode/decode "user.js" in-memory', () {
      final profile = FirefoxProfile();
      // ignore: deprecated_member_use_from_same_package
      profile.setOption(PrefsOption(Capabilities.hasNativeEvents, true));

      final zipArchive = unpackArchiveData(profile.toJson());

      final expectedFiles = ['prefs.js', 'user.js'];
      expect(
          zipArchive.files.length, greaterThanOrEqualTo(expectedFiles.length));
      expect(
        zipArchive.files,
        anyElement((archive.ArchiveFile f) => f.name == 'prefs.js'),
      );

      final prefs = FirefoxProfile.loadPrefsFile(MockFile(
        String.fromCharCodes(
          zipArchive.files.firstWhere((f) => f.name == 'user.js').content
              as List<int>,
        ),
      ));
      expect(
        prefs,
        anyElement(
          (PrefsOption o) =>
              // ignore: deprecated_member_use_from_same_package
              o.name == Capabilities.hasNativeEvents && o.value == true,
        ),
      );
    });

    test('encode/decode profile directory from disk', () {
      final profile = FirefoxProfile(
          profileDirectory: io.Directory('test/support/firefox_profile'));
      // ignore: deprecated_member_use_from_same_package
      profile.setOption(PrefsOption(Capabilities.hasNativeEvents, true));

      final zipArchive = unpackArchiveData(profile.toJson());

      final expectedFiles = [
        'prefs.js',
        'user.js',
        'addons.js',
        'webapps/webapps.json'
      ];
      expect(
          zipArchive.files.length, greaterThanOrEqualTo(expectedFiles.length));
      expect(
        zipArchive.files,
        anyElement((archive.ArchiveFile f) => f.name == 'prefs.js'),
      );

      final prefs = FirefoxProfile.loadPrefsFile(
        MockFile(
          String.fromCharCodes(
            zipArchive.files.firstWhere((f) => f.name == 'user.js').content
                as List<int>,
          ),
        ),
      );
      expect(
        prefs,
        anyElement(
          (PrefsOption o) =>
              // ignore: deprecated_member_use_from_same_package
              o.name == Capabilities.hasNativeEvents && o.value == true,
        ),
      );
    });
  }, timeout: const Timeout(Duration(minutes: 2)));
}

archive.Archive unpackArchiveData(Map profileData) {
  final zipData = base64.decode(profileData['firefox_profile'] as String);
  return archive.ZipDecoder().decodeBytes(zipData);
}

/// Simulate file for `FirefoxProfile.loadPrefsFile()`
class MockFile implements io.File {
  String content;

  MockFile(this.content);

  @override
  String readAsStringSync({Encoding encoding = utf8}) => content;

  @override
  dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
