package org.thoughtcrime.securesms.notifications;

import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.text.SpannableStringBuilder;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationCompat.Action;
import androidx.core.app.Person;
import androidx.core.app.RemoteInput;
import androidx.core.graphics.drawable.IconCompat;

import com.annimon.stream.Stream;
import com.bumptech.glide.load.engine.DiskCacheStrategy;

import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto;
import org.thoughtcrime.securesms.conversation.ConversationIntents;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.AvatarUtil;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.BubbleUtil;
import org.thoughtcrime.securesms.util.ConversationUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;

public class SingleRecipientNotificationBuilder extends AbstractNotificationBuilder {

  private static final String TAG = SingleRecipientNotificationBuilder.class.getSimpleName();

  private static final int BIG_PICTURE_DIMEN = 500;
  private static final int LARGE_ICON_DIMEN  = 250;

  private final List<NotificationCompat.MessagingStyle.Message> messages = new LinkedList<>();

  private SlideDeck              slideDeck;
  private CharSequence           contentTitle;
  private CharSequence           contentText;
  private Recipient              threadRecipient;
  private BubbleUtil.BubbleState defaultBubbleState;

  public SingleRecipientNotificationBuilder(@NonNull Context context, @NonNull NotificationPrivacyPreference privacy)
  {
    super(new ContextThemeWrapper(context, R.style.TextSecure_LightTheme), privacy);

    setSmallIcon(R.drawable.ic_notification);
    setColor(context.getResources().getColor(R.color.core_ultramarine));
    setCategory(NotificationCompat.CATEGORY_MESSAGE);

    if (!NotificationChannels.supported()) {
      setPriority(TextSecurePreferences.getNotificationPriority(context));
    }
  }

  public void setThread(@NonNull Recipient recipient) {
    String channelId = recipient.getNotificationChannel();
    setChannelId(channelId != null ? channelId : NotificationChannels.getMessagesChannel(context));

    if (privacy.isDisplayContact()) {
      setContentTitle(recipient.getDisplayName(context));

      if (recipient.getContactUri() != null) {
        addPerson(recipient.getContactUri().toString());
      }

      setLargeIcon(getContactDrawable(recipient));

    } else {
      setContentTitle(context.getString(R.string.SingleRecipientNotificationBuilder_signal));
      setLargeIcon(new GeneratedContactPhoto("Unknown", R.drawable.ic_profile_outline_40).asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context)));
    }

    setShortcutId(ConversationUtil.getShortcutId(recipient));
  }

  private Drawable getContactDrawable(@NonNull Recipient recipient) {
    ContactPhoto         contactPhoto         = recipient.getContactPhoto();
    FallbackContactPhoto fallbackContactPhoto = recipient.getFallbackContactPhoto();

    if (contactPhoto != null) {
      try {
        return GlideApp.with(context.getApplicationContext())
                                    .load(contactPhoto)
                                    .diskCacheStrategy(DiskCacheStrategy.ALL)
                                    .circleCrop()
                                    .submit(context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width),
                                            context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height))
                                    .get();
      } catch (InterruptedException | ExecutionException e) {
        return fallbackContactPhoto.asDrawable(context, recipient.getColor().toConversationColor(context));
      }
    } else {
      return fallbackContactPhoto.asDrawable(context, recipient.getColor().toConversationColor(context));
    }
  }

  public void setMessageCount(int messageCount) {
    setContentInfo(String.valueOf(messageCount));
    setNumber(messageCount);
  }

  public void setPrimaryMessageBody(@NonNull  Recipient threadRecipients,
                                    @NonNull  Recipient individualRecipient,
                                    @NonNull  CharSequence message,
                                    @Nullable SlideDeck slideDeck)
  {
    SpannableStringBuilder stringBuilder = new SpannableStringBuilder();

    if (privacy.isDisplayContact() && threadRecipients.isGroup()) {
      stringBuilder.append(Util.getBoldedString(individualRecipient.getDisplayName(context) + ": "));
    }

    if (privacy.isDisplayMessage()) {
      setContentText(stringBuilder.append(message));
      this.slideDeck = slideDeck;
    } else {
      setContentText(stringBuilder.append(context.getString(R.string.SingleRecipientNotificationBuilder_new_message)));
    }
  }

  public void addAndroidAutoAction(@NonNull PendingIntent androidAutoReplyIntent,
                                   @NonNull PendingIntent androidAutoHeardIntent, long timestamp)
  {

    if (contentTitle == null || contentText == null)
      return;

    RemoteInput remoteInput = new RemoteInput.Builder(AndroidAutoReplyReceiver.VOICE_REPLY_KEY)
                                  .setLabel(context.getString(R.string.MessageNotifier_reply))
                                  .build();

    NotificationCompat.CarExtender.UnreadConversation.Builder unreadConversationBuilder =
            new NotificationCompat.CarExtender.UnreadConversation.Builder(contentTitle.toString())
                                                                 .addMessage(contentText.toString())
                                                                 .setLatestTimestamp(timestamp)
                                                                 .setReadPendingIntent(androidAutoHeardIntent)
                                                                 .setReplyAction(androidAutoReplyIntent, remoteInput);

    extend(new NotificationCompat.CarExtender().setUnreadConversation(unreadConversationBuilder.build()));
  }

  public void addTurnOffTheseNotificationsAction(@NonNull PendingIntent turnOffTheseNotificationsIntent) {
    Action turnOffTheseNotifications = new Action(R.drawable.check,
                                                  context.getString(R.string.MessageNotifier_turn_off_these_notifications),
                                                  turnOffTheseNotificationsIntent);

    addAction(turnOffTheseNotifications);
  }

  public void addActions(@NonNull PendingIntent markReadIntent,
                         @NonNull PendingIntent quickReplyIntent,
                         @NonNull PendingIntent wearableReplyIntent,
                         @NonNull ReplyMethod replyMethod,
                         boolean replyEnabled)
  {
    NotificationCompat.WearableExtender extender         = new NotificationCompat.WearableExtender();
    Action                              markAsReadAction = new Action(R.drawable.check,
                                                                      context.getString(R.string.MessageNotifier_mark_read),
                                                                      markReadIntent);

    addAction(markAsReadAction);
    extender.addAction(markAsReadAction);

    if (replyEnabled) {
      String actionName = context.getString(R.string.MessageNotifier_reply);
      String label      = context.getString(replyMethodLongDescription(replyMethod));

      Action replyAction = new Action(R.drawable.ic_reply_white_36dp,
                                      actionName,
                                      quickReplyIntent);

      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        replyAction = new Action.Builder(R.drawable.ic_reply_white_36dp,
                                         actionName,
                                         wearableReplyIntent)
            .addRemoteInput(new RemoteInput.Builder(DefaultMessageNotifier.EXTRA_REMOTE_REPLY)
                                           .setLabel(label)
                                           .build())
            .build();
      }

      Action wearableReplyAction = new Action.Builder(R.drawable.ic_reply,
                                                    actionName,
                                                    wearableReplyIntent)
          .addRemoteInput(new RemoteInput.Builder(DefaultMessageNotifier.EXTRA_REMOTE_REPLY)
                                         .setLabel(label)
                                         .build())
          .build();

        addAction(replyAction);
        extend(extender.addAction(wearableReplyAction));
    }
  }

  @StringRes
  private static int replyMethodLongDescription(@NonNull ReplyMethod replyMethod) {
    switch (replyMethod) {
      case GroupMessage:
        return R.string.MessageNotifier_reply;
      case SecureMessage:
        return R.string.MessageNotifier_signal_message;
      case UnsecuredSmsMessage:
        return R.string.MessageNotifier_unsecured_sms;
      default:
        return R.string.MessageNotifier_reply;
    }
  }

  public void addMessageBody(@NonNull Recipient threadRecipient,
                             @NonNull Recipient individualRecipient,
                             @Nullable CharSequence messageBody,
                             long timestamp,
                             @Nullable SlideDeck slideDeck)
  {
    SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
    Person.Builder personBuilder = new Person.Builder()
                                             .setKey(ConversationUtil.getShortcutId(individualRecipient))
                                             .setBot(false);

    this.threadRecipient = threadRecipient;

    if (privacy.isDisplayContact()) {
      personBuilder.setName(individualRecipient.getDisplayName(context));
      personBuilder.setUri(individualRecipient.isSystemContact() ? individualRecipient.getContactUri().toString() : null);

      Bitmap bitmap = getLargeBitmap(getContactDrawable(individualRecipient));
      if (bitmap != null) {
        personBuilder.setIcon(IconCompat.createWithBitmap(bitmap));
      }
    } else {
      personBuilder.setName("");
    }

    final CharSequence text;
    if (privacy.isDisplayMessage()) {
      text = messageBody == null ? "" : messageBody;
    } else {
      text = stringBuilder.append(context.getString(R.string.SingleRecipientNotificationBuilder_new_message));
    }

    Uri    dataUri  = null;
    String mimeType = null;

    if (slideDeck != null && slideDeck.getThumbnailSlide() != null) {
      Slide thumbnail = slideDeck.getThumbnailSlide();

      dataUri  = thumbnail.getUri();
      mimeType = thumbnail.getContentType();
    }

    messages.add(new NotificationCompat.MessagingStyle.Message(text, timestamp, personBuilder.build()).setData(mimeType, dataUri));
  }

  public void setDefaultBubbleState(@NonNull BubbleUtil.BubbleState bubbleState) {
    this.defaultBubbleState = bubbleState;
  }

  @Override
  public Notification build() {
    if (privacy.isDisplayMessage()) {
      Optional<Uri> largeIconUri  = getLargeIconUri(slideDeck);
      Optional<Uri> bigPictureUri = getBigPictureUri(slideDeck);

      if (messages.size() == 1 && largeIconUri.isPresent()) {
        setLargeIcon(getNotificationPicture(largeIconUri.get(), LARGE_ICON_DIMEN));
      }

      if (messages.size() == 1 && bigPictureUri.isPresent() && Build.VERSION.SDK_INT < ConversationUtil.CONVERSATION_SUPPORT_VERSION) {
        setStyle(new NotificationCompat.BigPictureStyle()
                                       .bigPicture(getNotificationPicture(bigPictureUri.get(), BIG_PICTURE_DIMEN))
                                       .setSummaryText(getBigText()));
      } else {
        if (Build.VERSION.SDK_INT >= 24) {
          applyMessageStyle();

          if (Build.VERSION.SDK_INT >= ConversationUtil.CONVERSATION_SUPPORT_VERSION) {
            applyBubbleMetadata();
          }
        } else {
          applyLegacy();
        }
      }
    }

    return super.build();
  }

  private void applyMessageStyle() {
    ConversationUtil.pushShortcutForRecipientIfNeededSync(context, threadRecipient);
    NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(ConversationUtil.buildPersonCompat(context, Recipient.self()));

    if (threadRecipient.isGroup()) {
      if (privacy.isDisplayContact()) {
        messagingStyle.setConversationTitle(threadRecipient.getDisplayName(context));
      } else {
        messagingStyle.setConversationTitle(context.getString(R.string.SingleRecipientNotificationBuilder_signal));
      }

      messagingStyle.setGroupConversation(true);
    }

    Stream.of(messages).forEach(messagingStyle::addMessage);
    setStyle(messagingStyle);
  }

  private void applyBubbleMetadata() {
    long                              threadId       = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(threadRecipient);
    PendingIntent                     intent         = PendingIntent.getActivity(context, 0, ConversationIntents.createBubbleIntent(context, threadRecipient.getId(), threadId), 0);
    NotificationCompat.BubbleMetadata bubbleMetadata = new NotificationCompat.BubbleMetadata.Builder()
                                                                                            .setAutoExpandBubble(defaultBubbleState == BubbleUtil.BubbleState.SHOWN)
                                                                                            .setDesiredHeight(600)
                                                                                            .setIcon(AvatarUtil.getIconCompatForShortcut(context, threadRecipient))
                                                                                            .setSuppressNotification(defaultBubbleState == BubbleUtil.BubbleState.SHOWN)
                                                                                            .setIntent(intent)
                                                                                            .build();
    setBubbleMetadata(bubbleMetadata);
  }

  private void applyLegacy() {
    setStyle(new NotificationCompat.BigTextStyle().bigText(getBigText()));
  }

  private void setLargeIcon(@Nullable Drawable drawable) {
    if (drawable != null) {
      setLargeIcon(getLargeBitmap(drawable));
    }
  }

  private @Nullable Bitmap getLargeBitmap(@Nullable Drawable drawable) {
    if (drawable != null) {
      int largeIconTargetSize  = context.getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size);

      return BitmapUtil.createFromDrawable(drawable, largeIconTargetSize, largeIconTargetSize);
    }

    return null;
  }

  private static Optional<Uri> getLargeIconUri(@Nullable SlideDeck slideDeck) {
    if (slideDeck == null) {
      return Optional.absent();
    }

    Slide thumbnailSlide = Optional.fromNullable(slideDeck.getThumbnailSlide()).or(Optional.fromNullable(slideDeck.getStickerSlide())).orNull();
    return getThumbnailUri(thumbnailSlide);
  }

  private static Optional<Uri> getBigPictureUri(@Nullable SlideDeck slideDeck) {
    if (slideDeck == null) {
      return Optional.absent();
    }

    Slide thumbnailSlide = slideDeck.getThumbnailSlide();
    return getThumbnailUri(thumbnailSlide);
  }

  private static Optional<Uri> getThumbnailUri(@Nullable Slide slide) {
    if (slide != null && !slide.isInProgress() && slide.getUri() != null) {
      return Optional.of(slide.getUri());
    } else {
      return Optional.absent();
    }
  }

  private Bitmap getNotificationPicture(@NonNull Uri uri, int dimension)
  {
    try {
      return GlideApp.with(context.getApplicationContext())
                     .asBitmap()
                     .load(new DecryptableStreamUriLoader.DecryptableUri(uri))
                     .diskCacheStrategy(DiskCacheStrategy.NONE)
                     .submit(dimension, dimension)
                     .get();
    } catch (InterruptedException | ExecutionException e) {
      Log.w(TAG, e);
      return Bitmap.createBitmap(dimension, dimension, Bitmap.Config.RGB_565);
    }
  }

  @Override
  public NotificationCompat.Builder setContentTitle(CharSequence contentTitle) {
    this.contentTitle = contentTitle;
    return super.setContentTitle(contentTitle);
  }

  public NotificationCompat.Builder setContentText(CharSequence contentText) {
    this.contentText = trimToDisplayLength(contentText);
    return super.setContentText(this.contentText);
  }

  private CharSequence getBigText() {
    SpannableStringBuilder content = new SpannableStringBuilder();

    for (int i = 0; i < messages.size(); i++) {
      content.append(getBigTextFor(messages.get(i)));
      if (i < messages.size() - 1) {
        content.append('\n');
      }
    }

    return content;
  }

  private CharSequence getBigTextFor(NotificationCompat.MessagingStyle.Message message) {
    SpannableStringBuilder content = new SpannableStringBuilder();

    if (message.getPerson() != null && message.getPerson().getName() != null && threadRecipient.isGroup()) {
      content.append(Util.getBoldedString(message.getPerson().getName().toString())).append(": ");
    }

    return trimToDisplayLength(content.append(message.getText()));
  }

}
