// Copyright 2013 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:file_selector_platform_interface/file_selector_platform_interface.dart';
import 'package:file_selector_windows/file_selector_windows.dart';
import 'package:file_selector_windows/src/messages.g.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';

import 'file_selector_windows_test.mocks.dart';
import 'test_api.g.dart';

@GenerateMocks(<Type>[TestFileSelectorApi])
void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  final FileSelectorWindows plugin = FileSelectorWindows();
  late MockTestFileSelectorApi mockApi;

  setUp(() {
    mockApi = MockTestFileSelectorApi();
    TestFileSelectorApi.setUp(mockApi);
  });

  test('registered instance', () {
    FileSelectorWindows.registerWith();
    expect(FileSelectorPlatform.instance, isA<FileSelectorWindows>());
  });

  group('openFile', () {
    setUp(() {
      when(mockApi.showOpenDialog(any, any, any))
          .thenReturn(FileDialogResult(paths: <String>['foo']));
    });

    test('simple call works', () async {
      final XFile? file = await plugin.openFile();

      expect(file!.path, 'foo');
      final VerificationResult result =
          verify(mockApi.showOpenDialog(captureAny, null, null));
      final SelectionOptions options = result.captured[0] as SelectionOptions;
      expect(options.allowMultiple, false);
      expect(options.selectFolders, false);
    });

    test('passes the accepted type groups correctly', () async {
      const XTypeGroup group = XTypeGroup(
        label: 'text',
        extensions: <String>['txt'],
        mimeTypes: <String>['text/plain'],
      );

      const XTypeGroup groupTwo = XTypeGroup(
        label: 'image',
        extensions: <String>['jpg'],
        mimeTypes: <String>['image/jpg'],
      );

      await plugin.openFile(acceptedTypeGroups: <XTypeGroup>[group, groupTwo]);

      final VerificationResult result =
          verify(mockApi.showOpenDialog(captureAny, null, null));
      final SelectionOptions options = result.captured[0] as SelectionOptions;
      expect(
          _typeGroupListsMatch(options.allowedTypes, <TypeGroup>[
            TypeGroup(label: 'text', extensions: <String>['txt']),
            TypeGroup(label: 'image', extensions: <String>['jpg']),
          ]),
          true);
    });

    test('passes initialDirectory correctly', () async {
      await plugin.openFile(initialDirectory: '/example/directory');

      verify(mockApi.showOpenDialog(any, '/example/directory', null));
    });

    test('passes confirmButtonText correctly', () async {
      await plugin.openFile(confirmButtonText: 'Open File');

      verify(mockApi.showOpenDialog(any, null, 'Open File'));
    });

    test('throws for a type group that does not support Windows', () async {
      const XTypeGroup group = XTypeGroup(
        label: 'text',
        mimeTypes: <String>['text/plain'],
      );

      await expectLater(
          plugin.openFile(acceptedTypeGroups: <XTypeGroup>[group]),
          throwsArgumentError);
    });

    test('allows a wildcard group', () async {
      const XTypeGroup group = XTypeGroup(
        label: 'text',
      );

      await expectLater(
          plugin.openFile(acceptedTypeGroups: <XTypeGroup>[group]), completes);
    });
  });

  group('openFiles', () {
    setUp(() {
      when(mockApi.showOpenDialog(any, any, any))
          .thenReturn(FileDialogResult(paths: <String>['foo', 'bar']));
    });

    test('simple call works', () async {
      final List<XFile> file = await plugin.openFiles();

      expect(file[0].path, 'foo');
      expect(file[1].path, 'bar');
      final VerificationResult result =
          verify(mockApi.showOpenDialog(captureAny, null, null));
      final SelectionOptions options = result.captured[0] as SelectionOptions;
      expect(options.allowMultiple, true);
      expect(options.selectFolders, false);
    });

    test('passes the accepted type groups correctly', () async {
      const XTypeGroup group = XTypeGroup(
        label: 'text',
        extensions: <String>['txt'],
        mimeTypes: <String>['text/plain'],
      );

      const XTypeGroup groupTwo = XTypeGroup(
        label: 'image',
        extensions: <String>['jpg'],
        mimeTypes: <String>['image/jpg'],
      );

      await plugin.openFiles(acceptedTypeGroups: <XTypeGroup>[group, groupTwo]);

      final VerificationResult result =
          verify(mockApi.showOpenDialog(captureAny, null, null));
      final SelectionOptions options = result.captured[0] as SelectionOptions;
      expect(
          _typeGroupListsMatch(options.allowedTypes, <TypeGroup>[
            TypeGroup(label: 'text', extensions: <String>['txt']),
            TypeGroup(label: 'image', extensions: <String>['jpg']),
          ]),
          true);
    });

    test('passes initialDirectory correctly', () async {
      await plugin.openFiles(initialDirectory: '/example/directory');

      verify(mockApi.showOpenDialog(any, '/example/directory', null));
    });

    test('passes confirmButtonText correctly', () async {
      await plugin.openFiles(confirmButtonText: 'Open Files');

      verify(mockApi.showOpenDialog(any, null, 'Open Files'));
    });

    test('throws for a type group that does not support Windows', () async {
      const XTypeGroup group = XTypeGroup(
        label: 'text',
        mimeTypes: <String>['text/plain'],
      );

      await expectLater(
          plugin.openFiles(acceptedTypeGroups: <XTypeGroup>[group]),
          throwsArgumentError);
    });

    test('allows a wildcard group', () async {
      const XTypeGroup group = XTypeGroup(
        label: 'text',
      );

      await expectLater(
          plugin.openFiles(acceptedTypeGroups: <XTypeGroup>[group]), completes);
    });
  });

  group('getDirectoryPath', () {
    setUp(() {
      when(mockApi.showOpenDialog(any, any, any))
          .thenReturn(FileDialogResult(paths: <String>['foo']));
    });

    test('simple call works', () async {
      final String? path = await plugin.getDirectoryPath();

      expect(path, 'foo');
      final VerificationResult result =
          verify(mockApi.showOpenDialog(captureAny, null, null));
      final SelectionOptions options = result.captured[0] as SelectionOptions;
      expect(options.allowMultiple, false);
      expect(options.selectFolders, true);
    });

    test('passes initialDirectory correctly', () async {
      await plugin.getDirectoryPath(initialDirectory: '/example/directory');

      verify(mockApi.showOpenDialog(any, '/example/directory', null));
    });

    test('passes confirmButtonText correctly', () async {
      await plugin.getDirectoryPath(confirmButtonText: 'Open Directory');

      verify(mockApi.showOpenDialog(any, null, 'Open Directory'));
    });
  });

  group('getDirectoryPaths', () {
    setUp(() {
      when(mockApi.showOpenDialog(any, any, any))
          .thenReturn(FileDialogResult(paths: <String>['foo', 'bar']));
    });

    test('simple call works', () async {
      final List<String> paths = await plugin.getDirectoryPaths();

      expect(paths[0], 'foo');
      expect(paths[1], 'bar');
      final VerificationResult result =
          verify(mockApi.showOpenDialog(captureAny, null, null));
      final SelectionOptions options = result.captured[0] as SelectionOptions;
      expect(options.allowMultiple, true);
      expect(options.selectFolders, true);
    });

    test('passes initialDirectory correctly', () async {
      await plugin.getDirectoryPath(initialDirectory: '/example/directory');

      verify(mockApi.showOpenDialog(any, '/example/directory', null));
    });

    test('passes confirmButtonText correctly', () async {
      await plugin.getDirectoryPath(confirmButtonText: 'Open Directory');

      verify(mockApi.showOpenDialog(any, null, 'Open Directory'));
    });
  });

  group('getSaveLocation', () {
    setUp(() {
      when(mockApi.showSaveDialog(any, any, any, any))
          .thenReturn(FileDialogResult(paths: <String>['foo']));
    });

    test('simple call works', () async {
      final FileSaveLocation? location = await plugin.getSaveLocation();

      expect(location?.path, 'foo');
      expect(location?.activeFilter, null);
      final VerificationResult result =
          verify(mockApi.showSaveDialog(captureAny, null, null, null));
      final SelectionOptions options = result.captured[0] as SelectionOptions;
      expect(options.allowMultiple, false);
      expect(options.selectFolders, false);
    });

    test('passes the accepted type groups correctly', () async {
      const XTypeGroup group = XTypeGroup(
        label: 'text',
        extensions: <String>['txt'],
        mimeTypes: <String>['text/plain'],
      );

      const XTypeGroup groupTwo = XTypeGroup(
        label: 'image',
        extensions: <String>['jpg'],
        mimeTypes: <String>['image/jpg'],
      );

      await plugin
          .getSaveLocation(acceptedTypeGroups: <XTypeGroup>[group, groupTwo]);

      final VerificationResult result =
          verify(mockApi.showSaveDialog(captureAny, null, null, null));
      final SelectionOptions options = result.captured[0] as SelectionOptions;
      expect(
          _typeGroupListsMatch(options.allowedTypes, <TypeGroup>[
            TypeGroup(label: 'text', extensions: <String>['txt']),
            TypeGroup(label: 'image', extensions: <String>['jpg']),
          ]),
          true);
    });

    test('returns the selected type group correctly', () async {
      when(mockApi.showSaveDialog(any, any, any, any)).thenReturn(
          FileDialogResult(paths: <String>['foo'], typeGroupIndex: 1));
      const XTypeGroup group = XTypeGroup(
        label: 'text',
        extensions: <String>['txt'],
        mimeTypes: <String>['text/plain'],
      );

      const XTypeGroup groupTwo = XTypeGroup(
        label: 'image',
        extensions: <String>['jpg'],
        mimeTypes: <String>['image/jpg'],
      );

      final FileSaveLocation? result = await plugin
          .getSaveLocation(acceptedTypeGroups: <XTypeGroup>[group, groupTwo]);

      verify(mockApi.showSaveDialog(captureAny, null, null, null));

      expect(result?.activeFilter, groupTwo);
    });

    test('passes initialDirectory correctly', () async {
      await plugin.getSaveLocation(
          options:
              const SaveDialogOptions(initialDirectory: '/example/directory'));

      verify(mockApi.showSaveDialog(any, '/example/directory', null, null));
    });

    test('passes suggestedName correctly', () async {
      await plugin.getSaveLocation(
          options: const SaveDialogOptions(suggestedName: 'baz.txt'));

      verify(mockApi.showSaveDialog(any, null, 'baz.txt', null));
    });

    test('passes confirmButtonText correctly', () async {
      await plugin.getSaveLocation(
          options: const SaveDialogOptions(confirmButtonText: 'Save File'));

      verify(mockApi.showSaveDialog(any, null, null, 'Save File'));
    });

    test('throws for a type group that does not support Windows', () async {
      const XTypeGroup group = XTypeGroup(
        label: 'text',
        mimeTypes: <String>['text/plain'],
      );

      await expectLater(
          plugin.getSaveLocation(acceptedTypeGroups: <XTypeGroup>[group]),
          throwsArgumentError);
    });

    test('allows a wildcard group', () async {
      const XTypeGroup group = XTypeGroup(
        label: 'text',
      );

      await expectLater(
          plugin.getSaveLocation(acceptedTypeGroups: <XTypeGroup>[group]),
          completes);
    });
  });

  group('getSavePath (deprecated)', () {
    setUp(() {
      when(mockApi.showSaveDialog(any, any, any, any))
          .thenReturn(FileDialogResult(paths: <String>['foo']));
    });

    test('simple call works', () async {
      final String? path = await plugin.getSavePath();

      expect(path, 'foo');
      final VerificationResult result =
          verify(mockApi.showSaveDialog(captureAny, null, null, null));
      final SelectionOptions options = result.captured[0] as SelectionOptions;
      expect(options.allowMultiple, false);
      expect(options.selectFolders, false);
    });

    test('passes the accepted type groups correctly', () async {
      const XTypeGroup group = XTypeGroup(
        label: 'text',
        extensions: <String>['txt'],
        mimeTypes: <String>['text/plain'],
      );

      const XTypeGroup groupTwo = XTypeGroup(
        label: 'image',
        extensions: <String>['jpg'],
        mimeTypes: <String>['image/jpg'],
      );

      await plugin
          .getSavePath(acceptedTypeGroups: <XTypeGroup>[group, groupTwo]);

      final VerificationResult result =
          verify(mockApi.showSaveDialog(captureAny, null, null, null));
      final SelectionOptions options = result.captured[0] as SelectionOptions;
      expect(
          _typeGroupListsMatch(options.allowedTypes, <TypeGroup>[
            TypeGroup(label: 'text', extensions: <String>['txt']),
            TypeGroup(label: 'image', extensions: <String>['jpg']),
          ]),
          true);
    });

    test('passes initialDirectory correctly', () async {
      await plugin.getSavePath(initialDirectory: '/example/directory');

      verify(mockApi.showSaveDialog(any, '/example/directory', null, null));
    });

    test('passes suggestedName correctly', () async {
      await plugin.getSavePath(suggestedName: 'baz.txt');

      verify(mockApi.showSaveDialog(any, null, 'baz.txt', null));
    });

    test('passes confirmButtonText correctly', () async {
      await plugin.getSavePath(confirmButtonText: 'Save File');

      verify(mockApi.showSaveDialog(any, null, null, 'Save File'));
    });

    test('throws for a type group that does not support Windows', () async {
      const XTypeGroup group = XTypeGroup(
        label: 'text',
        mimeTypes: <String>['text/plain'],
      );

      await expectLater(
          plugin.getSavePath(acceptedTypeGroups: <XTypeGroup>[group]),
          throwsArgumentError);
    });

    test('allows a wildcard group', () async {
      const XTypeGroup group = XTypeGroup(
        label: 'text',
      );

      await expectLater(
          plugin.getSavePath(acceptedTypeGroups: <XTypeGroup>[group]),
          completes);
    });
  });
}

// True if the given options match.
//
// This is needed because Pigeon data classes don't have custom equality checks,
// so only match for identical instances.
bool _typeGroupListsMatch(List<TypeGroup?> a, List<TypeGroup?> b) {
  if (a.length != b.length) {
    return false;
  }
  for (int i = 0; i < a.length; i++) {
    if (!_typeGroupsMatch(a[i], b[i])) {
      return false;
    }
  }
  return true;
}

// True if the given type groups match.
//
// This is needed because Pigeon data classes don't have custom equality checks,
// so only match for identical instances.
bool _typeGroupsMatch(TypeGroup? a, TypeGroup? b) {
  return a!.label == b!.label && listEquals(a.extensions, b.extensions);
}
