import 'dart:async';
import 'dart:io';

import 'package:drift/drift.dart' show Value;
import 'package:flutter/foundation.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:pro_video_editor/pro_video_editor.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/channels/video_compression.channel.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/utils/log.dart';

Future<void> compressImage(
  File sourceFile,
  File destinationFile,
) async {
  final stopwatch = Stopwatch()..start();

  try {
    var compressedBytes = await FlutterImageCompress.compressWithFile(
      sourceFile.path,
      format: CompressFormat.webp,
      quality: 90,
    );

    if (compressedBytes == null) {
      throw Exception(
        'Could not compress media file: Sending original file.',
      );
    }

    Log.info('Compressed images size in bytes: ${compressedBytes.length}');

    if (compressedBytes.length >= 2 * 1000 * 1000) {
      // if the media file is over 1MB compress it with 60%
      final tmpCompressedBytes = await FlutterImageCompress.compressWithFile(
        sourceFile.path,
        format: CompressFormat.webp,
        quality: 60,
      );
      if (tmpCompressedBytes == null) {
        Log.error(
          'Could not compress media file with 60%: $sourceFile. Sending original 90% compressed file.',
        );
      } else {
        compressedBytes = tmpCompressedBytes;
      }
    }

    await destinationFile.writeAsBytes(compressedBytes);
  } catch (e) {
    Log.warn('$e');
    sourceFile.copySync(destinationFile.path);
  }

  stopwatch.stop();

  Log.info(
    'Compression of the image took: ${stopwatch.elapsedMilliseconds} milliseconds.',
  );
}

Future<void> compressAndOverlayVideo(MediaFileService media) async {
  if (media.tempPath.existsSync()) {
    media.tempPath.deleteSync();
  }
  if (media.ffmpegOutputPath.existsSync()) {
    media.ffmpegOutputPath.deleteSync();
  }

  final stopwatch = Stopwatch()..start();

  try {
    final task = VideoRenderData(
      videoSegments: [
        VideoSegment(video: EditorVideo.file(media.originalPath)),
      ],
      imageLayers: [
        if (media.overlayImagePath.existsSync())
          ImageLayer(image: EditorLayerImage.file(media.overlayImagePath)),
      ],
      enableAudio: !media.removeAudio,
    );

    await ProVideoEditor.instance.renderVideoToFile(
      media.ffmpegOutputPath.path,
      task,
    );

    if (Platform.isIOS ||
        media.ffmpegOutputPath.statSync().size >= 10_000_000 ||
        !kReleaseMode) {
      String? compressedPath;
      try {
        compressedPath = await VideoCompressionChannel.compressVideo(
          inputPath: media.ffmpegOutputPath.path,
          outputPath: media.tempPath.path,
          onProgress: (progress) async {
            await twonlyDB.mediaFilesDao.updateMedia(
              media.mediaFile.mediaId,
              MediaFilesCompanion(
                preProgressingProcess: Value((progress * 100).toInt()),
              ),
            );
          },
        );
      } catch (e) {
        Log.warn('during video compression: $e');
      }

      if (compressedPath == null) {
        Log.warn('Could not compress video using original video.');
        // as a fall back use the non compressed version
        media.ffmpegOutputPath.copySync(media.tempPath.path);
      }
    } else {
      // In case the video is smaller than 10MB do not compress it...
      media.ffmpegOutputPath.copySync(media.tempPath.path);
    }

    stopwatch.stop();

    final sizeFrom = (media.ffmpegOutputPath.statSync().size / 1024 / 1024)
        .toStringAsFixed(2);
    final sizeTo = (media.tempPath.statSync().size / 1024 / 1024)
        .toStringAsFixed(2);

    Log.info(
      'It took ${stopwatch.elapsedMilliseconds}ms to compress the video. Reduced from $sizeFrom to $sizeTo bytes.',
    );
  } catch (e) {
    Log.error(e);
    // Log.error('Compression failed for the video with exit code $returnCode.');
    // Log.error(await session.getAllLogsAsString());
    // This should not happen, but in case "notify" the user that the video was not send... This is absolutely bad, but
    // better this way then sending an uncompressed media file which potentially is 100MB big :/
    // Hopefully the user will report the strange behavior <3
    await twonlyDB.messagesDao.updateMessagesByMediaId(
      media.mediaFile.mediaId,
      const MessagesCompanion(isDeletedFromSender: Value(true)),
    );
    media.fullMediaRemoval();
    await media.setUploadState(UploadState.uploaded);
  }
}
