package org.mercury_im.messenger.android.ui.account.detail;

import android.app.Application;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;

import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.android.MercuryImApplication;
import org.mercury_im.messenger.android.ui.base.MercuryAndroidViewModel;
import org.mercury_im.messenger.core.SchedulersFacade;
import org.mercury_im.messenger.core.util.DefaultUtil;
import org.mercury_im.messenger.core.util.Optional;
import org.mercury_im.messenger.core.viewmodel.account.detail.AccountDetailsViewModel;
import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem;
import org.pgpainless.key.OpenPgpV4Fingerprint;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.inject.Inject;

public class AndroidAccountDetailsViewModel extends AndroidViewModel implements MercuryAndroidViewModel<AccountDetailsViewModel> {

    private static final Logger LOGGER = Logger.getLogger(AndroidAccountDetailsViewModel.class.getName());

    @Inject
    SchedulersFacade schedulers;

    @Inject
    AccountDetailsViewModel commonViewModel;

    private final UUID accountId;
    private MutableLiveData<Optional<OpenPgpV4Fingerprint>> ikeyFingerprint = new MutableLiveData<>();
    private MutableLiveData<OpenPgpV4Fingerprint> localFingerprint = new MutableLiveData<>();
    private MutableLiveData<List<FingerprintViewItem>> remoteFingerprints = new MutableLiveData<>(new ArrayList<>());
    private MutableLiveData<EntityBareJid> jid = new MutableLiveData<>(DefaultUtil.defaultJid());
    private MutableLiveData<Boolean> accountEnabled = new MutableLiveData<>(false);
    private MutableLiveData<Boolean> accountAuthenticated = new MutableLiveData<>(false);

    public AndroidAccountDetailsViewModel(@NonNull Application application, UUID accountId) {
        super(application);
        this.accountId = accountId;

        LOGGER.log(Level.INFO, "Creating AndroidAccountDetailsViewModel");
        ((MercuryImApplication) application).getAppComponent().inject(this);

        addDisposable(getCommonViewModel().observeLocalDeviceFingerprint(accountId)
                .compose(schedulers.executeUiSafeObservable())
                .filter(Optional::isPresent)
                .map(Optional::getItem)
                .subscribe(localFingerprint::postValue));

        addDisposable(getCommonViewModel().observeIkeyFingerprint(accountId)
                .compose(schedulers.executeUiSafeObservable())
                .subscribe(ikeyFingerprint::postValue,
                        e -> LOGGER.log(Level.SEVERE,  "Error displaying ikey fingerprint", e)));

        addDisposable(getCommonViewModel().observeRemoteDeviceFingerprints(accountId)
                .compose(schedulers.executeUiSafeObservable())
                .subscribe(list -> {
                            LOGGER.log(Level.INFO, "Set remote fingerprints to list: " + Arrays.toString(list.toArray()));
                            remoteFingerprints.postValue(list);
                        },
                        e -> LOGGER.log(Level.SEVERE, "Error observing remote fingerprints.", e),
                        () -> LOGGER.log(Level.INFO, "observing remote fingerprint onComplete.")));

        addDisposable(getCommonViewModel().getJid(accountId).subscribe(jid::postValue));

        addDisposable(getCommonViewModel().isAccountEnabled(accountId)
                .compose(schedulers.executeUiSafeObservable())
                .subscribe(bool -> accountEnabled.postValue(bool),
                        e -> LOGGER.log(Level.SEVERE, "Error subscribing to account enabled state", e),
                        () -> LOGGER.log(Level.INFO, "observing account enabled state onComplete.")));

        addDisposable(getCommonViewModel().isAccountAuthenticated(accountId)
        .compose(schedulers.executeUiSafeObservable())
        .subscribe(bool -> accountAuthenticated.postValue(bool),
                e -> LOGGER.log(Level.SEVERE, "Error subscribing to account authenticated state", e),
                () -> LOGGER.log(Level.INFO, "observing account authenticated state onComplete.")));
    }

    @Override
    public AccountDetailsViewModel getCommonViewModel() {
        return commonViewModel;
    }

    public LiveData<EntityBareJid> getJid() {
        return jid;
    }

    public void markFingerprintTrusted(OpenPgpV4Fingerprint fingerprint, boolean trusted) {
        getCommonViewModel().markFingerprintTrusted(accountId, fingerprint, trusted);
    }

    public void sendIkeyElement(UUID accountId) {
        getCommonViewModel().sendIkeyElement(accountId);
    }

    public LiveData<Optional<OpenPgpV4Fingerprint>> getIkeyFingerprint() {
        return ikeyFingerprint;
    }

    public LiveData<OpenPgpV4Fingerprint> getLocalFingerprint() {
        return localFingerprint;
    }

    public LiveData<List<FingerprintViewItem>> getRemoteFingerprints() {
        return remoteFingerprints;
    }

    public void unpublishPublicKey(OpenPgpV4Fingerprint fingerprint) {
        addDisposable(getCommonViewModel().unpublishPublicKey(accountId, fingerprint)
                .subscribeOn(schedulers.getNewThread())
                .observeOn(schedulers.getUiScheduler())
                .subscribe(() -> LOGGER.log(Level.INFO, "Successfully unpublished fingerprint " + fingerprint),
                        e -> LOGGER.log(Level.SEVERE, "Error unpublishing fingerprint " + fingerprint, e)));
    }

    public void onGenerateIkey() {
        addDisposable(getCommonViewModel().generateIkey(accountId)
                .subscribeOn(schedulers.getNewThread())
                .observeOn(schedulers.getUiScheduler())
                .subscribe(() -> LOGGER.log(Level.INFO, "IKey generated for account " + accountId),
                        e -> LOGGER.log(Level.SEVERE, "Could not generate Ikey", e)));
    }

    public void onDeleteIkey() {
        addDisposable(getCommonViewModel().deleteIkey(accountId)
                .subscribeOn(schedulers.getNewThread())
                .observeOn(schedulers.getUiScheduler())
                .subscribe(() -> LOGGER.log(Level.INFO, "IKey deleted for account " + accountId),
                        e -> LOGGER.log(Level.SEVERE, "Could not delete Ikey", e)));
    }

    public void onRestoreIkeyBackup() {
        addDisposable(getCommonViewModel().restoreIkeyBackup(accountId)
                .subscribeOn(schedulers.getNewThread())
                .observeOn(schedulers.getUiScheduler())
                .subscribe(() -> LOGGER.log(Level.INFO, "IKey restored for account " + accountId),
                        e -> LOGGER.log(Level.SEVERE, "Could not restore Ikey backup", e)));
    }

    public LiveData<Boolean> isAccountEnabled() {
        return accountEnabled;
    }

    public LiveData<Boolean> isAccountAuthenticated() {
        return accountAuthenticated;
    }

    public void sendPresence(UUID accountId) {
        addDisposable(
        getCommonViewModel().sendPresence(accountId)
                .subscribeOn(schedulers.getNewThread())
                .observeOn(schedulers.getUiScheduler())
                .subscribe(() -> LOGGER.log(Level.INFO, "Presence update sent for account " + accountId),
                        e -> LOGGER.log(Level.SEVERE, "Error sending presence update for account " + accountId, e)));
    }

    public static class AndroidAccountDetailsViewModelFactory implements ViewModelProvider.Factory {

        private final Application application;
        private final UUID accountId;

        public AndroidAccountDetailsViewModelFactory(Application application, UUID accountId) {
            this.application = application;
            this.accountId = accountId;
        }

        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            return (T) new AndroidAccountDetailsViewModel(application, accountId);
        }
    }
}
