package org.thoughtcrime.securesms.recipients.ui.bottomsheet;

import android.app.Activity;
import android.content.Context;
import android.widget.Toast;

import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;

import com.google.android.material.dialog.MaterialAlertDialogBuilder;

import org.signal.core.util.ThreadUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.storageservice.storage.protos.groups.AccessControl;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.settings.conversation.ConversationSettingsNavigator;
import org.thoughtcrime.securesms.conversation.colors.ColorizerV2;
import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.model.IdentityRecord;
import org.thoughtcrime.securesms.database.model.StoryViewState;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.LiveGroup;
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabel;
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabelRepository;
import org.thoughtcrime.securesms.groups.memberlabel.StyledMemberLabel;
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
import org.thoughtcrime.securesms.groups.ui.GroupErrors;
import org.thoughtcrime.securesms.groups.ui.addtogroup.AddToGroupsActivity;
import org.thoughtcrime.securesms.jobs.AvatarGroupsV2DownloadJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.stories.StoryViewerArgs;
import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import org.thoughtcrime.securesms.verify.VerifyIdentityActivity;

import java.util.Objects;
import java.util.Optional;

import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;

final class RecipientDialogViewModel extends ViewModel {

  private final Context                                context;
  private final RecipientDialogRepository              recipientDialogRepository;
  private final LiveData<Recipient>                    recipient;
  private final MutableLiveData<IdentityRecord>        identity;
  private final LiveData<AdminActionStatus>            adminActionStatus;
  private final LiveData<Boolean>                      canAddToAGroup;
  private final MutableLiveData<Boolean>               adminActionBusy;
  private final MutableLiveData<StoryViewState>        storyViewState;
  private final MutableLiveData<RecipientDetailsState> recipientDetailsState;
  private final CompositeDisposable                    disposables;
  private final boolean                                isDeprecatedOrUnregistered;

  private RecipientDialogViewModel(@NonNull Context context,
                                   @NonNull RecipientDialogRepository recipientDialogRepository)
  {
    this.context                    = context;
    this.recipientDialogRepository  = recipientDialogRepository;
    this.identity                   = new MutableLiveData<>();
    this.adminActionBusy            = new MutableLiveData<>(false);
    this.storyViewState             = new MutableLiveData<>();
    this.recipientDetailsState      = new MutableLiveData<>();
    this.disposables                = new CompositeDisposable();
    this.isDeprecatedOrUnregistered = SignalStore.misc().isClientDeprecated() || TextSecurePreferences.isUnauthorizedReceived(context);

    boolean recipientIsSelf = recipientDialogRepository.getRecipientId().equals(Recipient.self().getId());

    final LiveRecipient liveRecipient = Recipient.live(recipientDialogRepository.getRecipientId());
    recipient = liveRecipient.getLiveData();

    if (recipientDialogRepository.getGroupId() != null && recipientDialogRepository.getGroupId().isV2() && !recipientIsSelf) {
      LiveGroup source = new LiveGroup(recipientDialogRepository.getGroupId());

      adminActionStatus = LiveDataUtil.combineLatest(source.getGroupRecord(), recipient, (group, r) -> {
        boolean                      active         = group.isActive();
        boolean                      localAdmin     = group.isAdmin(Recipient.self());
        GroupTable.MemberLevel       memberLevel    = group.memberLevel(r);
        boolean                      inGroup        = memberLevel.isInGroup();
        boolean                      recipientAdmin = memberLevel == GroupTable.MemberLevel.ADMINISTRATOR;
        DecryptedGroup               decryptedGroup = group.getHasV2GroupProperties() ? group.requireV2GroupProperties().getDecryptedGroup() : null;
        AccessControl.AccessRequired linkAccess     = decryptedGroup != null && decryptedGroup.accessControl != null ? decryptedGroup.accessControl.addFromInviteLink
                                                                                                                     : AccessControl.AccessRequired.UNKNOWN;
        boolean                      isLinkActive   = linkAccess == AccessControl.AccessRequired.ANY || linkAccess == AccessControl.AccessRequired.ADMINISTRATOR;

        return new AdminActionStatus(active && inGroup && localAdmin,
                                     active && inGroup && localAdmin && !recipientAdmin,
                                     active && inGroup && localAdmin && recipientAdmin,
                                     isLinkActive);
      });
    } else {
      adminActionStatus = new MutableLiveData<>(new AdminActionStatus(false, false, false, false));
    }

    boolean isSelf = recipientDialogRepository.getRecipientId().equals(Recipient.self().getId());
    if (!isSelf) {
      recipientDialogRepository.getIdentity(identity::postValue);
    }

    MutableLiveData<Integer> localGroupCount = new MutableLiveData<>(0);

    canAddToAGroup = LiveDataUtil.combineLatest(recipient, localGroupCount,
                                                (r, count) -> count > 0 && r.isRegistered() && !r.isGroup() && !r.isSelf() && !r.isBlocked());

    recipientDialogRepository.getActiveGroupCount(localGroupCount::postValue);

    Disposable storyViewStateDisposable = StoryViewState.getForRecipientId(recipientDialogRepository.getRecipientId())
                                                        .subscribe(storyViewState::postValue);

    disposables.add(storyViewStateDisposable);

    Disposable recipientDisposable = liveRecipient.observable().subscribe(this::updateRecipientDetailsState);
    disposables.add(recipientDisposable);
  }

  private void updateRecipientDetailsState(@NonNull Recipient recipient) {
    GroupId groupId   = recipientDialogRepository.getGroupId();
    String  aboutText = recipient.isReleaseNotes() ? null : recipient.getCombinedAboutAndEmoji();

    if (groupId != null && groupId.isV2() && recipient.isIndividual() && !recipient.isSelf()) {
      SignalExecutors.BOUNDED.execute(() -> {
        GroupId.V2        v2GroupId   = (GroupId.V2) groupId;
        MemberLabel       label       = MemberLabelRepository.getInstance().getLabelSync(v2GroupId, recipient);
        StyledMemberLabel styledLabel = null;

        if (label != null) {
          ColorizerV2           colorizer   = new ColorizerV2();
          Optional<GroupRecord> groupRecord = SignalDatabase.groups().getGroup(v2GroupId);
          if (groupRecord.isPresent() && groupRecord.get().getHasV2GroupProperties()) {
            colorizer.onGroupMembershipChanged(groupRecord.get().requireV2GroupProperties().getMemberServiceIds());
          }
          styledLabel = new StyledMemberLabel(label, colorizer.getIncomingGroupSenderColor(context, recipient));
        }

        recipientDetailsState.postValue(new RecipientDetailsState(styledLabel, aboutText));
      });
    } else {
      recipientDetailsState.postValue(new RecipientDetailsState(null, aboutText));
    }
  }

  @Override protected void onCleared() {
    super.onCleared();
    disposables.clear();
  }

  boolean isDeprecatedOrUnregistered() {
    return isDeprecatedOrUnregistered;
  }

  LiveData<StoryViewState> getStoryViewState() {
    return storyViewState;
  }

  LiveData<Recipient> getRecipient() {
    return recipient;
  }

  public LiveData<Boolean> getCanAddToAGroup() {
    return canAddToAGroup;
  }

  LiveData<AdminActionStatus> getAdminActionStatus() {
    return adminActionStatus;
  }

  LiveData<IdentityRecord> getIdentity() {
    return identity;
  }

  LiveData<Boolean> getAdminActionBusy() {
    return adminActionBusy;
  }

  LiveData<RecipientDetailsState> getRecipientDetails() {
    return recipientDetailsState;
  }

  void onNoteToSelfClicked(@NonNull Activity activity) {
    if (storyViewState.getValue() == null || storyViewState.getValue() == StoryViewState.NONE) {
      onMessageClicked(activity);
    } else {
      activity.startActivity(StoryViewerActivity.createIntent(
          activity,
          new StoryViewerArgs.Builder(recipientDialogRepository.getRecipientId(), recipient.getValue().getShouldHideStory())
              .isFromQuote(true)
              .build()));
    }
  }

  void onMessageClicked(@NonNull Activity activity) {
    recipientDialogRepository.getRecipient(recipient -> CommunicationActions.startConversation(activity, recipient, null));
  }

  void onSecureCallClicked(@NonNull FragmentActivity activity, @NonNull CommunicationActions.OnUserAlreadyInAnotherCall onUserAlreadyInAnotherCall) {
    recipientDialogRepository.getRecipient(recipient -> CommunicationActions.startVoiceCall(activity, recipient, onUserAlreadyInAnotherCall));
  }

  void onInsecureCallClicked(@NonNull FragmentActivity activity) {
    recipientDialogRepository.getRecipient(recipient -> CommunicationActions.startInsecureCall(activity, recipient));
  }

  void onSecureVideoCallClicked(@NonNull FragmentActivity activity, @NonNull CommunicationActions.OnUserAlreadyInAnotherCall onUserAlreadyInAnotherCall) {
    recipientDialogRepository.getRecipient(recipient -> CommunicationActions.startVideoCall(activity, recipient, onUserAlreadyInAnotherCall));
  }

  void onBlockClicked(@NonNull Recipient recipient) {
    RecipientUtil.blockNonGroup(context, recipient);
  }

  void onUnblockClicked(@NonNull Recipient recipient) {
    RecipientUtil.unblock(recipient);
  }

  void onViewSafetyNumberClicked(@NonNull Activity activity, @NonNull IdentityRecord identityRecord) {
    VerifyIdentityActivity.startOrShowExchangeMessagesDialog(activity, identityRecord);
  }

  void onAvatarClicked(@NonNull FragmentActivity activity) {
    if (storyViewState.getValue() == null || storyViewState.getValue() == StoryViewState.NONE) {
      recipientDialogRepository.getRecipient(recipient -> ConversationSettingsNavigator.navigate(activity, recipient));
    } else {
      activity.startActivity(StoryViewerActivity.createIntent(
          activity,
          new StoryViewerArgs.Builder(recipientDialogRepository.getRecipientId(), recipient.getValue().getShouldHideStory())
              .isFromQuote(true)
              .build()));
    }
  }

  void onMakeGroupAdminClicked(@NonNull Activity activity) {
    new MaterialAlertDialogBuilder(activity)
        .setMessage(context.getString(R.string.RecipientBottomSheet_s_will_be_able_to_edit_group, Objects.requireNonNull(recipient.getValue()).getDisplayName(context)))
        .setPositiveButton(R.string.RecipientBottomSheet_make_admin,
                           (dialog, which) -> {
                             adminActionBusy.setValue(true);
                             recipientDialogRepository.setMemberAdmin(true, result -> {
                                                                        adminActionBusy.setValue(false);
                                                                        if (!result) {
                                                                          Toast.makeText(activity, R.string.ManageGroupActivity_failed_to_update_the_group, Toast.LENGTH_SHORT).show();
                                                                        }
                                                                      },
                                                                      this::showErrorToast);
                           })
        .setNegativeButton(android.R.string.cancel, (dialog, which) -> {})
        .show();
  }

  @MainThread
  void onRemoveGroupAdminClicked(@NonNull Activity activity) {
    Recipient groupMember = Objects.requireNonNull(recipient.getValue());

    recipientDialogRepository.willAdminDemotionClearLabel(willDemotionClearLabel -> {
      int messageRes = willDemotionClearLabel ? R.string.RecipientBottomSheet_remove_s_as_group_admin_and_clear_member_label
                                              : R.string.RecipientBottomSheet_remove_s_as_group_admin;

      new MaterialAlertDialogBuilder(activity)
          .setMessage(context.getString(messageRes, groupMember.getDisplayName(context)))
          .setPositiveButton(R.string.RecipientBottomSheet_remove_as_admin,
                             (dialog, which) -> {
                               adminActionBusy.setValue(true);
                               recipientDialogRepository.setMemberAdmin(false, result -> {
                                                                          adminActionBusy.setValue(false);
                                                                          if (!result) {
                                                                            Toast.makeText(activity, R.string.ManageGroupActivity_failed_to_update_the_group, Toast.LENGTH_SHORT).show();
                                                                          }
                                                                        },
                                                                        this::showErrorToast);
                             })
          .setNegativeButton(android.R.string.cancel, (dialog, which) -> {})
          .show();
    });
  }

  void onRemoveFromGroupClicked(@NonNull Activity activity, boolean isLinkActive, @NonNull Runnable onSuccess) {
    new MaterialAlertDialogBuilder(activity)
        .setMessage(context.getString(isLinkActive ? R.string.RecipientBottomSheet_remove_s_from_the_group_they_will_not_be_able_to_rejoin
                                                   : R.string.RecipientBottomSheet_remove_s_from_the_group,
                                      Objects.requireNonNull(recipient.getValue()).getDisplayName(context)))
        .setPositiveButton(R.string.RecipientBottomSheet_remove,
                           (dialog, which) -> {
                             adminActionBusy.setValue(true);
                             recipientDialogRepository.removeMember(result -> {
                                                                      adminActionBusy.setValue(false);
                                                                      if (result) {
                                                                        onSuccess.run();
                                                                      }
                                                                    },
                                                                    this::showErrorToast);
                           })
        .setNegativeButton(android.R.string.cancel, (dialog, which) -> {})
        .show();
  }

  void refreshRecipient() {
    recipientDialogRepository.refreshRecipient();
  }

  void onAddToGroupButton(@NonNull Activity activity) {
    recipientDialogRepository.getGroupMembership(existingGroups -> activity.startActivity(AddToGroupsActivity.createIntent(activity, recipientDialogRepository.getRecipientId(), existingGroups)));
  }

  @WorkerThread
  private void showErrorToast(@NonNull GroupChangeFailureReason e) {
    ThreadUtil.runOnMain(() -> Toast.makeText(context, GroupErrors.getUserDisplayMessage(e), Toast.LENGTH_LONG).show());
  }

  public void onTapToViewAvatar(@NonNull Recipient recipient) {
    SignalExecutors.BOUNDED.execute(() -> SignalDatabase.recipients().manuallyUpdateShowAvatar(recipient.getId(), true));
    if (recipient.isPushV2Group()) {
      AvatarGroupsV2DownloadJob.enqueueUnblurredAvatar(recipient.requireGroupId().requireV2());
    } else {
      RetrieveProfileAvatarJob.enqueueUnblurredAvatar(recipient);
    }
  }

  public void onResetBlurAvatar(@NonNull Recipient recipient) {
    SignalExecutors.BOUNDED.execute(() -> SignalDatabase.recipients().manuallyUpdateShowAvatar(recipient.getId(), false));
  }

  public void refreshGroupId(@Nullable GroupId groupId) {
    if (groupId != null) {
      SignalExecutors.BOUNDED.execute(() -> {
        RecipientId groupRecipientId = SignalDatabase.groups().getGroup(groupId).get().getRecipientId();
        Recipient.live(groupRecipientId).refresh();
      });
    }
  }

  static class AdminActionStatus {
    private final boolean canRemove;
    private final boolean canMakeAdmin;
    private final boolean canMakeNonAdmin;
    private final boolean isLinkActive;

    AdminActionStatus(boolean canRemove, boolean canMakeAdmin, boolean canMakeNonAdmin, boolean isLinkActive) {
      this.canRemove       = canRemove;
      this.canMakeAdmin    = canMakeAdmin;
      this.canMakeNonAdmin = canMakeNonAdmin;
      this.isLinkActive    = isLinkActive;
    }

    boolean isCanRemove() {
      return canRemove;
    }

    boolean isCanMakeAdmin() {
      return canMakeAdmin;
    }

    boolean isCanMakeNonAdmin() {
      return canMakeNonAdmin;
    }

    boolean isLinkActive() {
      return isLinkActive;
    }
  }

  public static class Factory implements ViewModelProvider.Factory {

    private final Context     context;
    private final RecipientId recipientId;
    private final GroupId     groupId;

    Factory(@NonNull Context context, @NonNull RecipientId recipientId, @Nullable GroupId groupId) {
      this.context     = context;
      this.recipientId = recipientId;
      this.groupId     = groupId;
    }

    @Override
    public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
      //noinspection unchecked
      return (T) new RecipientDialogViewModel(context, new RecipientDialogRepository(context, recipientId, groupId));
    }
  }
}
