
import 'package:async/async.dart';
import 'package:collection/collection.dart';
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/presentation/utils/app_toast.dart';
import 'package:core/utils/app_logger.dart';
import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart';
import 'package:model/email/attachment.dart';
import 'package:model/extensions/attachment_extension.dart';
import 'package:model/extensions/list_attachment_extension.dart';
import 'package:model/upload/file_info.dart';
import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/composer/domain/state/upload_attachment_state.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/upload_attachment_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.dart';
import 'package:tmail_ui_user/features/upload/domain/state/attachment_upload_state.dart';
import 'package:tmail_ui_user/features/upload/presentation/extensions/upload_attachment_extension.dart';
import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_state.dart';
import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_state_list.dart';
import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_status.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
import 'package:uuid/uuid.dart';

class UploadController extends BaseController {

  final _imagePaths = Get.find<ImagePaths>();
  final _appToast = Get.find<AppToast>();
  final _uuid = Get.find<Uuid>();
  final _mailboxDashBoardController = Get.find<MailboxDashBoardController>();

  final UploadAttachmentInteractor _uploadAttachmentInteractor;

  final listUploadAttachments = <UploadFileState>[].obs;
  final uploadInlineViewState = Rx<Either<Failure, Success>>(Right(UIState.idle));

  final StreamGroup<Either<Failure, Success>> _progressUploadStateStreamGroup
    = StreamGroup<Either<Failure, Success>>.broadcast();
  final StreamGroup<Either<Failure, Success>> _progressUploadInlineImageStateStreamGroup
    = StreamGroup<Either<Failure, Success>>.broadcast();

  final UploadFileStateList _uploadingStateFiles = UploadFileStateList();
  final UploadFileStateList _uploadingStateInlineFiles = UploadFileStateList();

  UploadController(this._uploadAttachmentInteractor);

  @override
  void onInit() {
    _registerProgressUploadStateStream();
    super.onInit();
  }

  @override
  void onClose() {
    _uploadingStateFiles.clear();
    _progressUploadStateStreamGroup.close();
    _uploadingStateInlineFiles.clear();
    _progressUploadInlineImageStateStreamGroup.close();
    super.onClose();
  }

  void _registerProgressUploadStateStream() {
    _progressUploadStateStreamGroup.stream.listen(_handleProgressUploadStateStream);
    _progressUploadInlineImageStateStreamGroup.stream.listen(_handleProgressUploadInlineImageStateStream);
  }

  void _handleProgressUploadStateStream(Either<Failure, Success> uploadState) {
    uploadState.fold(
      (failure) {
          log('UploadController::_handleProgressUploadStateStream():failure: $failure');
          if (failure is ErrorAttachmentUploadState) {
            _uploadingStateFiles.updateElementByUploadTaskId(
                failure.uploadId,
                (currentState) => currentState?.copyWith(uploadStatus: UploadFileStatus.uploadFailed));
            deleteFileUploaded(failure.uploadId);
            _handleUploadAttachmentsFailure(failure);
          }
        },
      (success) {
          if (success is UploadingAttachmentUploadState) {
            log('UploadController::_registerUploadAttachmentsState():uploading[${success.uploadId}]: ${success.progress}');
            _uploadingStateFiles.updateElementByUploadTaskId(
                success.uploadId,
                (currentState) {
                  if (currentState?.uploadStatus.completed ?? false) {
                    return currentState;
                  } else {
                    return currentState?.copyWith(
                        uploadingProgress: (success.progress * 100 / success.total).floor(),
                        uploadStatus: UploadFileStatus.uploading);
                  }
                }
            );

            _refreshListUploadAttachmentState();
          } else if (success is SuccessAttachmentUploadState) {
            log('UploadController::_handleProgressUploadStateStream():succeed[${success.uploadId}]');
            _uploadingStateFiles.updateElementByUploadTaskId(
              success.uploadId,
              (currentState) {
                final newState = currentState?.copyWith(
                    uploadingProgress: 100,
                    uploadStatus: UploadFileStatus.succeed,
                    attachment: success.attachment
                );
                return newState;
              },
            );

            _refreshListUploadAttachmentState();
            _handleUploadAttachmentsSuccess(success);
          }
        }
    );
  }

  void _handleProgressUploadInlineImageStateStream(Either<Failure, Success> uploadState) {
    uploadState.fold(
      (failure) {
        log('UploadController::_handleProgressUploadInlineImageStateStream():failure: $failure');
        if (failure is ErrorAttachmentUploadState) {
          uploadInlineViewState.value = Left(failure);
          _deleteInlineFileUploaded(failure.uploadId);
          if (currentContext != null && currentOverlayContext != null) {
            _appToast.showToastWithIcon(currentOverlayContext!,
              message: AppLocalizations.of(currentContext!).thisImageCannotBeAdded,
              textColor: AppColor.toastErrorBackgroundColor,
              iconColor: AppColor.toastErrorBackgroundColor,
              icon: _imagePaths.icInsertImage
            );
          }
        }
      },
      (success) {
        if (success is UploadingAttachmentUploadState) {
          log('UploadController::_handleProgressUploadInlineImageStateStream():uploading[${success.uploadId}]: ${success.progress}');
          _uploadingStateInlineFiles.updateElementByUploadTaskId(
              success.uploadId,
              (currentState) {
                if (currentState?.uploadStatus.completed ?? false) {
                  return currentState;
                } else {
                  return currentState?.copyWith(
                      uploadingProgress: (success.progress * 100 / success.total).floor(),
                      uploadStatus: UploadFileStatus.uploading);
                }
              }
          );
          uploadInlineViewState.value = Right(success);
        } else if (success is SuccessAttachmentUploadState) {
          log('UploadController::_handleProgressUploadInlineImageStateStream():succeed[${success.uploadId}]');
          final inlineAttachment = success.attachment.toAttachmentWithDisposition(
              disposition: ContentDisposition.inline,
              cid: _uuid.v1());
          _uploadingStateInlineFiles.updateElementByUploadTaskId(
            success.uploadId,
            (currentState) {
              final newState = currentState?.copyWith(
                  uploadingProgress: 100,
                  uploadStatus: UploadFileStatus.succeed,
                  attachment: inlineAttachment
              );
              return newState;
            },
          );
          final newUploadSuccess = SuccessAttachmentUploadState(
            success.uploadId,
            inlineAttachment,
            success.fileInfo);
          _handleUploadInlineAttachmentsSuccess(newUploadSuccess);
        }
      }
    );
  }

  void initializeUploadAttachments(List<Attachment> attachments) {
    final listUploadFilesState = attachments
        .map((attachment) => UploadFileState(
            UploadTaskId(attachment.blobId!.value),
            uploadStatus: UploadFileStatus.succeed,
            attachment: attachment))
        .toList();
    _uploadingStateFiles.addAll(listUploadFilesState);
    _refreshListUploadAttachmentState();
  }

  void deleteFileUploaded(UploadTaskId uploadId) {
    _uploadingStateFiles.deleteElementByUploadTaskId(uploadId);
    _refreshListUploadAttachmentState();
  }

  Future<void> justUploadAttachmentsAction(List<FileInfo> uploadFiles, Uri uploadUri) {
    return Future.forEach<FileInfo>(uploadFiles, (uploadFile) async {
      await uploadFileAction(uploadFile, uploadUri);
    });
  }

  Future<void> uploadFileAction(FileInfo uploadFile, Uri uploadUri, {bool isInline = false}) {
    log('UploadController::_uploadFile():fileName: ${uploadFile.fileName}');
    consumeState(_uploadAttachmentInteractor.execute(
      uploadFile,
      uploadUri,
      cancelToken: CancelToken(),
      isInline: isInline
    ));
    return Future.value();
  }

  void _refreshListUploadAttachmentState() {
    listUploadAttachments.value =
        _uploadingStateFiles.uploadingStateFiles.whereNotNull().toList();
    listUploadAttachments.refresh();
  }

  List<Attachment> get attachmentsUploaded {
    if (listUploadAttachments.isEmpty) {
      return List.empty();
    }
    return listUploadAttachments
        .map((fileState) => fileState.attachment)
        .whereNotNull()
        .toList();
  }

  Set<EmailBodyPart>? generateAttachments() {
    if (attachmentsUploaded.isEmpty) {
      return null;
    }
    return attachmentsUploaded
      .map((attachment) => attachment.toEmailBodyPart(
          disposition: ContentDisposition.attachment.value))
      .toSet();
  }

  void _handleUploadAttachmentsFailure(ErrorAttachmentUploadState failure) {
    if (currentContext != null && currentOverlayContext != null) {
      _appToast.showToastWithIcon(currentOverlayContext!,
          message: AppLocalizations.of(currentContext!).can_not_upload_this_file_as_attachments,
          textColor: AppColor.toastErrorBackgroundColor,
          iconColor: AppColor.toastErrorBackgroundColor,
          icon: _imagePaths.icAttachment);
    }
  }

  void _handleUploadAttachmentsSuccess(SuccessAttachmentUploadState success) {
    log('UploadController::_handleUploadAttachmentsSuccess(): $success');
    if (currentContext != null && currentOverlayContext != null && _uploadingStateFiles.allSuccess) {
      _appToast.showToastWithIcon(currentOverlayContext!,
          message: AppLocalizations.of(currentContext!).attachments_uploaded_successfully,
          iconColor: AppColor.primaryColor,
          icon: _imagePaths.icAttachment);
    }
  }

  bool hasEnoughMaxAttachmentSize({List<FileInfo>? listFiles}) {
    final currentTotalAttachmentsSize = attachmentsUploaded.totalSize();
    final totalInlineAttachmentsSize = inlineAttachmentsUploaded.totalSize();
    log('UploadController::_validateAttachmentsSize(): $currentTotalAttachmentsSize');
    log('UploadController::_validateAttachmentsSize(): totalInlineAttachmentsSize: $totalInlineAttachmentsSize');
    num uploadedTotalSize = 0;
    if (listFiles != null && listFiles.isNotEmpty) {
      final uploadedListSize = listFiles.map((file) => file.fileSize).toList();
      uploadedTotalSize = uploadedListSize.reduce((sum, size) => sum + size);
      log('UploadController::_validateAttachmentsSize(): uploadedTotalSize: $uploadedTotalSize');
    }

    final totalSizeReadyToUpload = currentTotalAttachmentsSize +
        totalInlineAttachmentsSize +
        uploadedTotalSize;
    log('UploadController::_validateAttachmentsSize(): totalSizeReadyToUpload: $totalSizeReadyToUpload');

    final maxSizeAttachmentsPerEmail = _mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value;
    if (maxSizeAttachmentsPerEmail != null) {
      return totalSizeReadyToUpload <= maxSizeAttachmentsPerEmail;
    } else {
      return false;
    }
  }

  bool get allUploadAttachmentsCompleted {
    return listUploadAttachments
        .every((uploadFile) => uploadFile.uploadStatus.completed);
  }

  void _deleteInlineFileUploaded(UploadTaskId uploadId) {
    _uploadingStateInlineFiles.deleteElementByUploadTaskId(uploadId);
  }

  void clearInlineFileUploaded() {
    _uploadingStateInlineFiles.clear();
  }

  void _handleUploadInlineAttachmentsSuccess(SuccessAttachmentUploadState success) {
    uploadInlineViewState.value = Right(success);
  }

  void clearUploadInlineViewState() {
    uploadInlineViewState.value = Right(UIState.idle);
  }

  List<Attachment> get inlineAttachmentsUploaded {
    if (_uploadingStateInlineFiles.uploadingStateFiles.isEmpty) {
      return List.empty();
    }
    return _uploadingStateInlineFiles.uploadingStateFiles
        .whereNotNull()
        .map((fileState) => fileState.attachment)
        .whereNotNull()
        .where((attachment) => attachment.cid != null)
        .toList();
  }

  Map<String, Attachment> get mapInlineAttachments {
    final inlineAttachments = _uploadingStateInlineFiles.uploadingStateFiles
        .whereNotNull()
        .map((fileState) => fileState.attachment)
        .whereNotNull()
        .where((attachment) => attachment.cid != null)
        .toList();

    log('UploadController::mapInlineAttachments(): $inlineAttachments');

    if (inlineAttachments.isEmpty) {
      return {};
    }

    final mapInlineAttachments = {
      for (var item in inlineAttachments) item.cid!: item
    };

    return mapInlineAttachments;
  }

  @override
  void onDone() {
    viewState.value.fold(_handleFailureViewState, _handleSuccessViewState);
  }

  void _handleFailureViewState(Failure failure) async {
    logError('UploadController::_handleFailureViewState():failure: $failure');
    if (failure is UploadAttachmentFailure) {
      if (failure.isInline) {
        if (currentContext != null && currentOverlayContext != null) {
          _appToast.showToastWithIcon(currentOverlayContext!,
            message: AppLocalizations.of(currentContext!).thisImageCannotBeAdded,
            textColor: AppColor.toastErrorBackgroundColor,
            iconColor: AppColor.toastErrorBackgroundColor,
            icon: _imagePaths.icInsertImage
          );
        }
      } else {
        if (currentContext != null && currentOverlayContext != null) {
          _appToast.showToastWithIcon(currentOverlayContext!,
            message: AppLocalizations.of(currentContext!).can_not_upload_this_file_as_attachments,
            textColor: AppColor.toastErrorBackgroundColor,
            iconColor: AppColor.toastErrorBackgroundColor,
            icon: _imagePaths.icAttachment
          );
        }
      }
    }
  }

  void _handleSuccessViewState(Success success) async {
    log('UploadController::_handleSuccessViewState():success: $success');
    if (success is UploadAttachmentSuccess) {
      if (success.isInline) {
        _uploadingStateInlineFiles.add(success.uploadAttachment.toUploadFileState());
        await _progressUploadInlineImageStateStreamGroup.add(success.uploadAttachment.progressState);
      } else {
        _uploadingStateFiles.add(success.uploadAttachment.toUploadFileState());
        await _progressUploadStateStreamGroup.add(success.uploadAttachment.progressState);
        _refreshListUploadAttachmentState();
      }
    }
  }
}