import 'dart:async';

import 'package:file_selector/file_selector.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:localsend_app/config/theme.dart';
import 'package:localsend_app/gen/strings.g.dart';
import 'package:localsend_app/model/cross_file.dart';
import 'package:localsend_app/pages/apk_picker_page.dart';
import 'package:localsend_app/provider/device_info_provider.dart';
import 'package:localsend_app/provider/selection/selected_sending_files_provider.dart';
import 'package:localsend_app/util/determine_image_type.dart';
import 'package:localsend_app/util/file_path_helper.dart';
import 'package:localsend_app/util/image_converter.dart';
import 'package:localsend_app/util/native/channel/android_channel.dart' as android_channel;
import 'package:localsend_app/util/native/cross_file_converters.dart';
import 'package:localsend_app/util/native/pick_directory_path.dart';
import 'package:localsend_app/util/native/platform_check.dart';
import 'package:localsend_app/util/ui/asset_picker_translated_text_delegate.dart';
import 'package:localsend_app/widget/dialogs/loading_dialog.dart';
import 'package:localsend_app/widget/dialogs/message_input_dialog.dart';
import 'package:localsend_app/widget/dialogs/no_permission_dialog.dart';
import 'package:localsend_isolates/model/file_type.dart';
import 'package:localsend_isolates/util/sleep.dart';
import 'package:logging/logging.dart';
import 'package:pasteboard/pasteboard.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:refena_flutter/refena_flutter.dart';
import 'package:routerino/routerino.dart';
import 'package:uri_content/uri_content.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';

final _logger = Logger('FilePickerHelper');
final _uriContent = UriContent();

enum FilePickerOption {
  file(Icons.description),
  folder(Icons.folder),
  media(Icons.image),
  text(Icons.subject),
  app(Icons.apps),
  clipboard(Icons.paste)
  ;

  const FilePickerOption(this.icon);

  final IconData icon;

  String get label {
    switch (this) {
      case FilePickerOption.file:
        return t.sendTab.picker.file;
      case FilePickerOption.folder:
        return t.sendTab.picker.folder;
      case FilePickerOption.media:
        return t.sendTab.picker.media;
      case FilePickerOption.text:
        return t.sendTab.picker.text;
      case FilePickerOption.app:
        return t.sendTab.picker.app;
      case FilePickerOption.clipboard:
        return t.sendTab.picker.clipboard;
    }
  }

  /// Returns the options for the current platform.
  static List<FilePickerOption> getOptionsForPlatform() {
    if (checkPlatform([TargetPlatform.iOS])) {
      // On iOS, picking from media is most common.
      // The file app is very limited.
      return [
        FilePickerOption.media,
        FilePickerOption.text,
        FilePickerOption.clipboard,
        FilePickerOption.file,
        FilePickerOption.folder,
      ];
    } else if (checkPlatform([TargetPlatform.android])) {
      // On android, the file app is most powerful.
      // It actually also allows to pick media files.
      return [
        FilePickerOption.file,
        FilePickerOption.media,
        FilePickerOption.clipboard,
        FilePickerOption.text,
        FilePickerOption.folder,
        FilePickerOption.app,
      ];
    } else {
      // Desktop
      return [
        FilePickerOption.file,
        FilePickerOption.folder,
        FilePickerOption.text,
        FilePickerOption.clipboard,
      ];
    }
  }
}

class PickFileAction extends AsyncGlobalAction {
  final FilePickerOption option;
  final BuildContext context;

  PickFileAction({
    required this.option,
    required this.context,
  });

  @override
  Future<void> reduce() async {
    switch (option) {
      case FilePickerOption.file:
        await _pickFiles(context, ref);
        break;
      case FilePickerOption.folder:
        // ignore: use_build_context_synchronously
        await _pickFolder(context, ref);
        break;
      case FilePickerOption.media:
        // ignore: use_build_context_synchronously
        await _pickMedia(context, ref);
        break;
      case FilePickerOption.text:
        // ignore: use_build_context_synchronously
        await _pickText(context, ref);
        break;
      case FilePickerOption.clipboard:
        // ignore: use_build_context_synchronously
        await _pickClipboard(context, ref);
        break;
      case FilePickerOption.app:
        // ignore: use_build_context_synchronously
        await _pickApp(context);
        break;
    }
  }

  @override
  String toString() {
    return 'PickAction(option: $option)';
  }
}

Future<void> _pickFiles(BuildContext context, Ref ref) async {
  if (checkPlatform([TargetPlatform.android])) {
    // On android, the files are copied to the cache which takes some time.
    // ignore: unawaited_futures
    showDialog(
      context: context,
      barrierDismissible: false,
      builder: (_) => const LoadingDialog(),
    );
  }
  try {
    if (defaultTargetPlatform == TargetPlatform.android) {
      final result = await android_channel.pickFilesAndroid();
      if (result != null) {
        await ref
            .redux(selectedSendingFilesProvider)
            .dispatchAsync(
              AddFilesAction(
                files: result,
                converter: CrossFileConverters.convertFileInfo,
              ),
            );
      }
    } else {
      final result = await openFiles();
      await ref
          .redux(selectedSendingFilesProvider)
          .dispatchAsync(
            AddFilesAction(
              files: result,
              converter: CrossFileConverters.convertXFile,
            ),
          );
    }
  } catch (e) {
    if (e is PlatformException && e.code == 'CANCELED') {
      // User canceled the file picker
      _logger.info('User canceled file picker');
      return;
    }

    // ignore: use_build_context_synchronously
    await showDialog(context: context, builder: (_) => const NoPermissionDialog());
    _logger.warning('Failed to pick files', e);
  } finally {
    // ignore: use_build_context_synchronously
    Routerino.context.popUntilRoot(); // remove loading dialog
  }
}

Future<void> _pickFolder(BuildContext context, Ref ref) async {
  if (checkPlatform([TargetPlatform.android])) {
    try {
      await Permission.storage.request();
    } catch (e) {
      _logger.warning('Failed to request storage permission', e);
    }
  }

  if (!context.mounted) {
    return;
  }

  // ignore: unawaited_futures
  showDialog(
    context: context,
    barrierDismissible: false,
    builder: (_) => const LoadingDialog(),
  );
  await sleepAsync(200); // Wait for the dialog to be shown
  try {
    if (defaultTargetPlatform == TargetPlatform.android && (ref.read(deviceInfoProvider).androidSdkInt ?? 0) >= android_channel.contentUriMinSdk) {
      // Android 8 and above have more predictable content URIs that we can parse.
      final result = await android_channel.pickDirectoryAndroid();
      if (result != null) {
        await ref.redux(selectedSendingFilesProvider).dispatchAsync(AddAndroidDirectoryAction(result));
      }
    } else {
      final directoryPath = await pickDirectoryPath();
      if (directoryPath != null) {
        await ref.redux(selectedSendingFilesProvider).dispatchAsync(AddDirectoryAction(directoryPath));
      }
    }
  } catch (e) {
    if (e is PlatformException && e.code == 'CANCELED') {
      // User canceled the file picker
      _logger.info('User canceled file picker');
      return;
    }

    _logger.warning('Failed to pick directory', e);
    // ignore: use_build_context_synchronously
    await showDialog(context: context, builder: (_) => const NoPermissionDialog());
  } finally {
    // ignore: use_build_context_synchronously
    Routerino.context.popUntilRoot(); // remove loading dialog
  }
}

Future<void> _pickMedia(BuildContext context, Ref ref) async {
  if (checkPlatform([TargetPlatform.android])) {
    await PhotoManager.requestPermissionExtend(
      requestOption: const PermissionRequestOption(
        androidPermission: AndroidPermission(
          type: RequestType.common,
          mediaLocation: true,
        ),
      ),
    );
  }

  if (!context.mounted) return;

  final oldBrightness = Theme.of(context).brightness;
  final List<AssetEntity>? result = await AssetPicker.pickAssets(
    context,
    pickerConfig: const AssetPickerConfig(maxAssets: 999, textDelegate: TranslatedAssetPickerTextDelegate()),
  );

  WidgetsBinding.instance.addPostFrameCallback((_) async {
    // restore brightness for Android
    await sleepAsync(500);
    if (context.mounted) {
      await updateSystemOverlayStyleWithBrightness(oldBrightness);
    }
  });

  if (result != null) {
    await ref
        .redux(selectedSendingFilesProvider)
        .dispatchAsync(
          AddFilesAction(
            files: result,
            converter: CrossFileConverters.convertAssetEntity,
          ),
        );
  }
}

Future<void> _pickText(BuildContext context, Ref ref) async {
  final result = await showDialog<String>(context: context, builder: (_) => const MessageInputDialog());
  if (result != null) {
    ref.redux(selectedSendingFilesProvider).dispatch(AddMessageAction(message: result));
  }
}

Future<void> _pickClipboard(BuildContext context, Ref ref) async {
  final data = await Clipboard.getData(Clipboard.kTextPlain);
  if (data?.text != null) {
    ref.redux(selectedSendingFilesProvider).dispatch(AddMessageAction(message: data!.text!));
    return;
  }

  final image = await Pasteboard.image;
  if (image != null) {
    // Adding temporary variable because Dart analyzer somehow doesn't properly downcast Uint8List? to Uint8List
    Uint8List currImage = image;
    String imageType = determineImageType(image);

    // On Windows, Pasteboard read image from clipboard as BMP which is large and inefficient. Attempt to convert to PNG
    if (imageType == 'bmp') {
      try {
        final pngImage = await convertBmpToPng(image);
        currImage = pngImage;
        imageType = 'png';
      } catch (e) {
        // Fail to convert to png, proceed with existing bmp
      }
    }

    final now = DateTime.now();
    final fileName =
        'clipboard_${now.year}-${now.month.twoDigitString}-${now.day.twoDigitString}_${now.hour.twoDigitString}-${now.minute.twoDigitString}.$imageType';
    ref
        .redux(selectedSendingFilesProvider)
        .dispatch(
          AddBinaryAction(
            bytes: currImage,
            fileType: FileType.image,
            fileName: fileName,
          ),
        );
    return;
  }

  final List<String> files = await Pasteboard.files();
  if (files.isNotEmpty) {
    await ref
        .redux(selectedSendingFilesProvider)
        .dispatchAsync(
          AddFilesAction(
            files: files.map((e) => XFile(e)).toList(),
            converter: (file) async {
              if (!file.path.startsWith('content://')) {
                return CrossFileConverters.convertXFile(file);
              }
              // handle content uri
              return CrossFile(
                name: file.name,
                fileType: file.name.guessFileType(),
                size: await _uriContent.getContentLength(Uri.parse(file.path)) ?? -1,
                path: file.path,
                thumbnail: null,
                asset: null,
                bytes: null,
                lastModified: null,
                lastAccessed: null,
              );
            },
          ),
        );
    return;
  }

  if (!context.mounted) {
    return;
  }

  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(
      content: Text(t.general.noItemInClipboard),
    ),
  );
}

Future<void> _pickApp(BuildContext context) async {
  // Currently, only Android APK
  await context.push(() => const ApkPickerPage());
}

extension on int {
  String get twoDigitString => toString().padLeft(2, '0');
}
