// ignore_for_file: inference_failure_on_function_invocation

import 'dart:async';
import 'dart:collection';
import 'dart:typed_data';

import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart';
import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/services/user.service.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/components/emoji_picker.bottom.dart';
import 'package:twonly/src/visual/components/notification_badge.comp.dart';
import 'package:twonly/src/visual/elements/my_button.element.dart';
import 'package:twonly/src/visual/helpers/media_view_sizing.helper.dart';
import 'package:twonly/src/visual/helpers/screenshot.helper.dart';
import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart';
import 'package:twonly/src/visual/views/camera/camera_preview_components/save_to_gallery.dart';
import 'package:twonly/src/visual/views/camera/share_image_contact_selection.view.dart';
import 'package:twonly/src/visual/views/camera/share_image_contact_selection_components/select_show_time.dart';
import 'package:twonly/src/visual/views/camera/share_image_editor_components/action_button.dart';
import 'package:twonly/src/visual/views/camera/share_image_editor_components/image_item.dart';
import 'package:twonly/src/visual/views/camera/share_image_editor_components/layer_data.dart';
import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers_viewer.dart';
import 'package:video_player/video_player.dart';

List<Layer> layers = [];
List<Layer> undoLayers = [];
List<Layer> removedLayers = [];

class ShareImageEditorView extends StatefulWidget {
  const ShareImageEditorView({
    required this.sharedFromGallery,
    required this.mediaFileService,
    this.screenshotImage,
    this.previewLink,
    super.key,
    this.sendToGroup,
    this.mainCameraController,
  });
  final ScreenshotImageHelper? screenshotImage;
  final Group? sendToGroup;
  final bool sharedFromGallery;
  final MediaFileService mediaFileService;
  final MainCameraController? mainCameraController;
  final PreviewLink? previewLink;
  @override
  State<ShareImageEditorView> createState() => _ShareImageEditorView();
}

class _ShareImageEditorView extends State<ShareImageEditorView> {
  double tabDownPosition = 0;
  bool sendingOrLoadingImage = true;
  bool loadingImage = true;
  bool isDisposed = false;
  HashSet<String> selectedGroupIds = HashSet();
  double widthRatio = 1;
  double heightRatio = 1;
  double pixelRatio = 1;
  VideoPlayerController? videoController;
  ImageItem currentImage = ImageItem();
  ScreenshotController screenshotController = ScreenshotController();
  Timer? _imageLoadingTimer;

  MediaFileService get mediaService => widget.mediaFileService;
  MediaFile get media => widget.mediaFileService.mediaFile;

  @override
  void initState() {
    super.initState();

    if (media.type != MediaType.gif) {
      layers.add(FilterLayerData(key: GlobalKey()));
    }

    if (widget.previewLink != null &&
        widget.previewLink!.shouldGeneratePreview) {
      layers.add(
        LinkPreviewLayerData(key: GlobalKey(), link: widget.previewLink!.url),
      );
    }

    if (widget.sendToGroup != null) {
      selectedGroupIds.add(widget.sendToGroup!.groupId);
    }

    if (widget.mediaFileService.mediaFile.type == MediaType.image ||
        widget.mediaFileService.mediaFile.type == MediaType.gif) {
      if (widget.screenshotImage != null) {
        loadImage(widget.screenshotImage!);
      } else {
        if (widget.mediaFileService.tempPath.existsSync()) {
          loadImage(
            ScreenshotImageHelper(file: widget.mediaFileService.tempPath),
          );
        } else if (widget.mediaFileService.originalPath.existsSync()) {
          loadImage(
            ScreenshotImageHelper(file: widget.mediaFileService.originalPath),
          );
        }
      }
    }

    if (media.type == MediaType.video) {
      setState(() {
        sendingOrLoadingImage = false;
        loadingImage = false;
      });
      videoController = VideoPlayerController.file(
        mediaService.originalPath,
        videoPlayerOptions: VideoPlayerOptions(),
      );
      videoController?.setLooping(true);
      videoController
          ?.initialize()
          .then((_) async {
            await videoController!.play();
            setState(() {});
          })
          // ignore: argument_type_not_assignable_to_error_handler
          .catchError(Log.error);
    }
  }

  @override
  void dispose() {
    isDisposed = true;
    layers.clear();
    videoController?.dispose();
    twonlyDB.mediaFilesDao.updateAllMediaFiles(
      const MediaFilesCompanion(
        isDraftMedia: Value(false),
      ),
    );
    _imageLoadingTimer?.cancel();
    super.dispose();
  }

  void updateSelectedGroupIds(String groupId, bool checked) {
    if (checked) {
      if (media.requiresAuthentication) {
        selectedGroupIds.clear();
      }
      selectedGroupIds.add(groupId);
    } else {
      selectedGroupIds.remove(groupId);
    }
    setState(() {});
  }

  Future<void> _setMaxShowTime(int? maxShowTime, bool storeAsDefault) async {
    await mediaService.setDisplayLimit(maxShowTime);
    if (!mounted) return;
    setState(() {});
    if (storeAsDefault) {
      await UserService.update((user) {
        user.defaultShowTime = maxShowTime;
      });
    }
  }

  Future<void> _setImageDisplayTime() async {
    if (media.type == MediaType.video) {
      await mediaService.setDisplayLimit(
        (media.displayLimitInMilliseconds == null) ? 0 : null,
      );
      if (!mounted) return;
      setState(() {});
      return;
    }

    final options = [
      1000,
      2000,
      3000,
      4000,
      5000,
      6000,
      7000,
      8000,
      9000,
      10000,
      15000,
      20000,
      null,
    ];

    var initialItem = options.length - 1;
    if (media.displayLimitInMilliseconds != null) {
      initialItem = options.indexOf(media.displayLimitInMilliseconds);
      if (initialItem == -1) {
        initialItem = options.length - 1;
      }
    }
    await showModalBottomSheet(
      context: context,
      backgroundColor: Colors.black,
      builder: (context) {
        return SelectShowTime(
          initialItem: initialItem,
          setMaxShowTime: _setMaxShowTime,
          options: options,
        );
      },
    );
  }

  List<Widget> get actionsAtTheRight {
    if (layers.isNotEmpty &&
        (layers.first.isEditing ||
            (layers.last.isEditing && layers.last.hasCustomActionButtons))) {
      return [];
    }
    return <Widget>[
      if (media.type != MediaType.gif)
        ActionButton(
          Icons.text_fields_rounded,
          tooltipText: context.lang.addTextItem,
          onPressed: () async {
            layers = layers.where((x) => !x.isDeleted).toList();
            if (layers.any((x) => x.isEditing)) return;
            undoLayers.clear();
            removedLayers.clear();
            layers.add(
              TextLayerData(
                key: GlobalKey(),
                textLayersBefore: layers.whereType<TextLayerData>().length,
              ),
            );
            setState(() {});
          },
        ),
      const SizedBox(height: 8),
      if (media.type != MediaType.gif)
        ActionButton(
          Icons.draw_rounded,
          tooltipText: context.lang.addDrawing,
          onPressed: () async {
            undoLayers.clear();
            removedLayers.clear();
            layers.add(DrawLayerData(key: GlobalKey()));
            setState(() {});
          },
        ),
      const SizedBox(height: 8),
      if (media.type != MediaType.gif)
        ActionButton(
          Icons.add_reaction_outlined,
          tooltipText: context.lang.addEmoji,
          onPressed: () async {
            final layer =
                await showModalBottomSheet(
                      context: context,
                      backgroundColor: Colors.black,
                      builder: (context) {
                        return const EmojiPickerBottom();
                      },
                    )
                    as Layer?;
            if (layer == null) return;
            undoLayers.clear();
            removedLayers.clear();
            layers.add(layer);
            setState(() {});
          },
        ),
      const SizedBox(height: 8),
      NotificationBadgeComp(
        count: (media.type == MediaType.video)
            ? '0'
            : media.displayLimitInMilliseconds == null
            ? '∞'
            : (media.displayLimitInMilliseconds! ~/ 1000).toString(),
        child: ActionButton(
          (media.type == MediaType.video)
              ? media.displayLimitInMilliseconds == null
                    ? Icons.repeat_rounded
                    : Icons.repeat_one_rounded
              : Icons.timer_outlined,
          tooltipText: context.lang.protectAsARealTwonly,
          onPressed: _setImageDisplayTime,
        ),
      ),
      if (media.type == MediaType.video) ...[
        const SizedBox(height: 8),
        ActionButton(
          (mediaService.removeAudio)
              ? Icons.volume_off_rounded
              : Icons.volume_up_rounded,
          tooltipText: 'Enable Audio in Video',
          color: (mediaService.removeAudio)
              ? Colors.white.withAlpha(160)
              : Colors.white,
          onPressed: () async {
            await mediaService.toggleRemoveAudio();
            if (mediaService.removeAudio) {
              await videoController?.setVolume(0);
            } else {
              await videoController?.setVolume(100);
            }
            if (mounted) setState(() {});
          },
        ),
      ],
      if (media.type == MediaType.image) ...[
        const SizedBox(height: 8),
        ActionButton(
          Icons.crop_rotate_outlined,
          tooltipText: 'Crop or rotate image',
          color: Colors.white,
          onPressed: () async {
            final first = layers.first;
            if (first is BackgroundLayerData) {
              first.isEditing = !first.isEditing;
            }
            setState(() {});
            // await mediaService.toggleRemoveAudio();
            // if (mediaService.removeAudio) {
            //   await videoController?.setVolume(0);
            // } else {
            //   await videoController?.setVolume(100);
            // }
            // if (mounted) setState(() {});
          },
        ),
      ],
      const SizedBox(height: 8),
      ActionButton(
        FontAwesomeIcons.shieldHeart,
        tooltipText: context.lang.protectAsARealTwonly,
        color: media.requiresAuthentication
            ? Theme.of(context).colorScheme.primary
            : Colors.white,
        onPressed: () async {
          await mediaService.setRequiresAuth(!media.requiresAuthentication);
          selectedGroupIds = HashSet();
          setState(() {});
        },
      ),
    ];
  }

  Future<bool?> _showBackDialog() {
    return showDialog<bool>(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: Text(
            context.lang.dialogAskDeleteMediaFilePopTitle,
          ),
          actions: [
            MyButton(
              variant: MyButtonVariant.primaryMiddle,
              onPressed: () {
                Navigator.pop(context, true);
              },
              child: Text(context.lang.dialogAskDeleteMediaFilePopDelete),
            ),
            TextButton(
              child: Text(context.lang.cancel),
              onPressed: () {
                Navigator.pop(context, false);
              },
            ),
          ],
        );
      },
    );
  }

  Future<void> askToCloseThenClose() async {
    final shouldPop = await _showBackDialog() ?? false;
    if (mounted && shouldPop) {
      Navigator.pop(context);
    }
  }

  List<Widget> get actionsAtTheTop {
    if (layers.isNotEmpty &&
        (layers.first.isEditing ||
            (layers.last.isEditing && layers.last.hasCustomActionButtons))) {
      return [];
    }
    return [
      ActionButton(
        FontAwesomeIcons.xmark,
        tooltipText: context.lang.close,
        onPressed: () async {
          final nonImageFilterLayer = layers.where(
            (x) => x is! BackgroundLayerData && x is! FilterLayerData,
          );
          if (nonImageFilterLayer.isEmpty) {
            Navigator.pop(context, false);
          } else {
            await askToCloseThenClose();
          }
        },
      ),
      Expanded(child: Container()),
      const SizedBox(width: 8),
      ActionButton(
        FontAwesomeIcons.rotateLeft,
        tooltipText: context.lang.undo,
        disable: layers.where((x) => !x.isDeleted).length <= 2,
        onPressed: () {
          if (removedLayers.isNotEmpty) {
            final lastLayer = removedLayers.removeLast()
              ..isDeleted = false
              ..isEditing = false;
            layers.add(lastLayer);
            setState(() {});
            return;
          }
          layers = layers.where((x) => !x.isDeleted).toList();
          if (layers.length <= 2) {
            // do not remove image layer and filter layer
            return;
          }
          undoLayers.add(layers.removeLast());
          setState(() {});
        },
      ),
      const SizedBox(width: 8),
      ActionButton(
        FontAwesomeIcons.rotateRight,
        tooltipText: context.lang.redo,
        disable: undoLayers.isEmpty,
        onPressed: () {
          if (undoLayers.isEmpty) return;
          layers.add(undoLayers.removeLast());
          setState(() {});
        },
      ),
      const SizedBox(width: 70),
    ];
  }

  Future<void> pushShareImageView() async {
    final mediaStoreFuture = storeImageAsOriginal();

    await videoController?.pause();
    if (isDisposed || !mounted) return;
    final wasSend =
        await Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => ShareImageView(
                  selectedGroupIds: selectedGroupIds,
                  updateSelectedGroupIds: updateSelectedGroupIds,
                  mediaStoreFuture: mediaStoreFuture,
                  mediaFileService: mediaService,
                  additionalData: getAdditionalData(),
                ),
              ),
            )
            as bool?;
    if (wasSend != null && wasSend && mounted) {
      widget.mainCameraController?.onImageSend();
      Navigator.pop(context, true);
    } else {
      await videoController?.play();
    }
  }

  Future<ScreenshotImageHelper?> getEditedImageBytes() async {
    if (layers.length == 1) {
      if (layers.first is BackgroundLayerData) {
        return (layers.first as BackgroundLayerData).image.image;
      }
    }
    if (layers.length == 2) {
      final filterLayer = layers[1];
      if (layers.first is BackgroundLayerData &&
          filterLayer is FilterLayerData) {
        if (filterLayer.page == 1) {
          return (layers.first as BackgroundLayerData).image.image;
        }
      }
    }

    for (final x in layers) {
      x.showCustomButtons = false;
    }
    setState(() {});

    // Make a short delay, so the setState does have its effect...
    await Future.delayed(const Duration(milliseconds: 80));

    final image = await screenshotController.capture(
      pixelRatio: pixelRatio,
    );
    if (image == null) {
      Log.warn('screenshotController did not return image bytes');
      return null;
    }

    for (final x in layers) {
      x.showCustomButtons = true;
    }
    if (mounted) {
      setState(() {});
    }
    return image;
  }

  Future<ScreenshotImageHelper?> storeImageAsOriginal() async {
    Uint8List? gifBytes;
    ScreenshotImageHelper? image;
    if (media.type == MediaType.gif) {
      gifBytes = await widget.screenshotImage?.getBytes();
    } else {
      image = await getEditedImageBytes();
      if (image != null) {
        await image.getBytes();
      }
    }

    if (mediaService.overlayImagePath.existsSync()) {
      mediaService.overlayImagePath.deleteSync();
    }
    if (mediaService.tempPath.existsSync()) {
      mediaService.tempPath.deleteSync();
    }
    if (mediaService.originalPath.existsSync()) {
      if (media.type == MediaType.image) {
        mediaService.originalPath.deleteSync();
      }
    }

    if (media.type == MediaType.gif) {
      if (gifBytes != null) {
        mediaService.originalPath.writeAsBytesSync(gifBytes.toList());
      }
    } else {
      if (image == null) return null;
      final bytes = await image.getBytes();
      if (bytes == null) {
        Log.warn('imageBytes are empty');
        return null;
      }
      if (media.type == MediaType.image || media.type == MediaType.gif) {
        mediaService.originalPath.writeAsBytesSync(bytes);
      } else if (media.type == MediaType.video) {
        mediaService.overlayImagePath.writeAsBytesSync(bytes);
      } else {
        Log.error('MediaType not supported: ${media.type}');
      }
    }
    return image;
  }

  Future<void> storeIoImageAsDraft(
    ScreenshotImageHelper screenshotImage,
  ) async {
    final imageBytes = await screenshotImage.getBytes();
    mediaService.originalPath.writeAsBytesSync(imageBytes!.toList());
  }

  Future<void> loadImage(ScreenshotImageHelper screenshotImage) async {
    if (screenshotImage.image == null &&
        screenshotImage.imageBytes == null &&
        screenshotImage.imageBytesFuture != null) {
      // this ensures that the imageBytes are defined
      await storeIoImageAsDraft(screenshotImage);
    } else {
      // store this image so it can be used as a draft in case the app is restarted
      unawaited(storeIoImageAsDraft(screenshotImage));
    }

    if (screenshotImage.image == null) {
      final imageBytes = await screenshotImage.getBytes();
      if (imageBytes != null) {
        screenshotImage.image = await decodeImageFromList(imageBytes);
      }
    }
    if (screenshotImage.image == null) {
      Log.error('Could not load screenshotImage.image');
      return;
    }

    currentImage.load(screenshotImage);

    if (isDisposed) return;

    if (!context.mounted) return;

    Future.delayed(const Duration(milliseconds: 500), () async {
      if (context.mounted) {
        await widget.mainCameraController?.closeCamera();
      }
    });

    setState(() {
      layers.insert(
        0,
        BackgroundLayerData(
          key: GlobalKey(),
          image: currentImage,
        ),
      );
    });
    // It is important that the user can sending the image only when the image is fully loaded otherwise if the user
    // will click on send before the image is painted the screenshot will be transparent..
    _imageLoadingTimer = Timer.periodic(const Duration(milliseconds: 10), (
      timer,
    ) {
      final imageLayer = layers.first;
      if (imageLayer is BackgroundLayerData) {
        if (imageLayer.imageLoaded) {
          timer.cancel();
          Future.delayed(const Duration(milliseconds: 50), () {
            if (context.mounted) {
              setState(() {
                sendingOrLoadingImage = false;
                loadingImage = false;
              });
            }
          });
        }
      }
    });
  }

  AdditionalMessageData? getAdditionalData() {
    AdditionalMessageData? additionalData;

    if (widget.previewLink != null) {
      additionalData = AdditionalMessageData(
        type: AdditionalMessageData_Type.LINK,
        link: widget.previewLink!.url.toString(),
      );
    }
    return additionalData;
  }

  Future<void> sendImageToSinglePerson() async {
    if (sendingOrLoadingImage) return;
    setState(() {
      sendingOrLoadingImage = true;
    });

    if (!context.mounted) return;

    widget.mainCameraController?.onImageSend();

    // must be awaited so the widget for the screenshot is not already disposed when sending..
    await storeImageAsOriginal();

    // Insert media file into the messages database and start uploading process in the background
    await insertMediaFileInMessagesTable(
      mediaService,
      [widget.sendToGroup!.groupId],
      additionalData: getAdditionalData(),
    );

    if (mounted) {
      Navigator.pop(context, true);
    }
  }

  Widget _buildScreenshotViewer() {
    return Screenshot(
      controller: screenshotController,
      child: LayersViewer(
        layers: layers.where((x) => !x.isDeleted).toList(),
        onUpdate: () {
          for (final layer in layers) {
            layer.isEditing = false;
            if (layer.isDeleted) {
              removedLayers.add(layer);
            }
          }
          layers = layers.where((x) => !x.isDeleted).toList();
          setState(() {});
        },
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    pixelRatio = MediaQuery.of(context).devicePixelRatio;

    return PopScope<bool?>(
      canPop: false,
      onPopInvokedWithResult: (didPop, result) async {
        if (didPop) return;
        await askToCloseThenClose();
      },
      child: Scaffold(
        backgroundColor: widget.sharedFromGallery
            ? null
            : Colors.white.withAlpha(0),
        resizeToAvoidBottomInset: false,
        body: Stack(
          fit: StackFit.expand,
          children: [
            GestureDetector(
              onTapDown: (details) {
                if (details.globalPosition.dy > 60) {
                  tabDownPosition = details.globalPosition.dy - 60;
                } else {
                  tabDownPosition = details.globalPosition.dy;
                }
              },
              onTap: () {
                if (layers.any((x) => x.isEditing)) {
                  return;
                }
                layers = layers.where((x) => !x.isDeleted).toList();
                undoLayers.clear();
                removedLayers.clear();
                layers.add(
                  TextLayerData(
                    key: GlobalKey(),
                    offset: Offset(0, tabDownPosition),
                    textLayersBefore: layers.whereType<TextLayerData>().length,
                  ),
                );
                setState(() {});
              },
              child: MediaViewSizingHelper(
                requiredHeight: 59,
                bottomNavigation: ColoredBox(
                  color: Theme.of(context).colorScheme.surface,
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      SaveToGalleryButton(
                        storeImageAsOriginal: storeImageAsOriginal,
                        mediaService: mediaService,
                        displayButtonLabel: widget.sendToGroup == null,
                        isLoading: loadingImage,
                      ),
                      if (widget.sendToGroup != null) const SizedBox(width: 10),
                      if (widget.sendToGroup != null)
                        MyButton(
                          variant: MyButtonVariant.secondaryMiddle,
                          onPressed: pushShareImageView,
                          child: const FaIcon(
                            FontAwesomeIcons.userPlus,
                            size: 14,
                          ),
                        ),
                      SizedBox(width: widget.sendToGroup == null ? 20 : 10),
                      IntrinsicWidth(
                        child: MyButton(
                          variant: MyButtonVariant.primaryMiddle,
                          onPressed: sendingOrLoadingImage
                              ? null
                              : () async {
                                  if (widget.sendToGroup == null) {
                                    return pushShareImageView();
                                  }
                                  await sendImageToSinglePerson();
                                },
                          child: Row(
                            mainAxisSize: MainAxisSize.min,
                            children: [
                              if (sendingOrLoadingImage)
                                const SizedBox(
                                  height: 12,
                                  width: 12,
                                  child: CircularProgressIndicator.adaptive(
                                    strokeWidth: 2,
                                    valueColor: AlwaysStoppedAnimation(
                                      Colors.black87,
                                    ),
                                  ),
                                )
                              else
                                const FaIcon(
                                  FontAwesomeIcons.solidPaperPlane,
                                  size: 14,
                                ),
                              const SizedBox(width: 8),
                              Text(
                                (widget.sendToGroup == null)
                                    ? context.lang.shareImagedEditorShareWith
                                    : substringBy(
                                        widget.sendToGroup!.groupName,
                                        15,
                                      ),
                              ),
                            ],
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
                child: SizedBox(
                  height: currentImage.height / pixelRatio,
                  width: currentImage.width / pixelRatio,
                  child: Stack(
                    children: [
                      if (videoController != null &&
                          videoController!.value.isInitialized)
                        Positioned.fill(
                          child: Center(
                            child: AspectRatio(
                              aspectRatio: videoController!.value.aspectRatio,
                              child: Stack(
                                children: [
                                  Positioned.fill(
                                    child: VideoPlayer(videoController!),
                                  ),
                                  Positioned.fill(
                                    child: _buildScreenshotViewer(),
                                  ),
                                ],
                              ),
                            ),
                          ),
                        )
                      else
                        _buildScreenshotViewer(),
                    ],
                  ),
                ),
              ),
            ),
            Positioned(
              top: 10,
              left: 5,
              right: 0,
              child: SafeArea(
                child: Row(
                  children: actionsAtTheTop,
                ),
              ),
            ),
            Positioned(
              right: 6,
              top: 100,
              child: Container(
                alignment: Alignment.bottomCenter,
                padding: const EdgeInsets.symmetric(vertical: 16),
                child: SafeArea(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: actionsAtTheRight,
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
