package org.mercury_im.messenger.core.viewmodel.account.detail;

import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore;
import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil;
import org.jivesoftware.smackx.pep.PepManager;
import org.jivesoftware.smackx.pubsub.LeafNode;
import org.jivesoftware.smackx.pubsub.PayloadItem;
import org.jivesoftware.smackx.pubsub.PubSubManager;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.core.SchedulersFacade;
import org.mercury_im.messenger.core.connection.MercuryConnectionManager;
import org.mercury_im.messenger.core.data.repository.AccountRepository;
import org.mercury_im.messenger.core.data.repository.OpenPgpRepository;
import org.mercury_im.messenger.core.util.Optional;
import org.mercury_im.messenger.core.viewmodel.MercuryViewModel;
import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem;
import org.mercury_im.messenger.entity.Account;
import org.pgpainless.key.OpenPgpV4Fingerprint;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import javax.inject.Inject;

import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.Single;

import static org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil.PEP_NODE_PUBLIC_KEYS;

public class AccountDetailsViewModel implements MercuryViewModel {

    private MercuryConnectionManager connectionManager;
    private final OpenPgpRepository openPgpRepository;
    private final AccountRepository accountRepository;
    private final SchedulersFacade schedulers;

    @Inject
    public AccountDetailsViewModel(MercuryConnectionManager connectionManager, OpenPgpRepository openPgpRepository, AccountRepository accountRepository, SchedulersFacade schedulers) {
        this.connectionManager = connectionManager;
        this.openPgpRepository = openPgpRepository;
        this.accountRepository = accountRepository;
        this.schedulers = schedulers;
    }

    public void markFingerprintTrusted(UUID accountId, OpenPgpV4Fingerprint fingerprint, boolean trusted) {
        addDisposable(openPgpRepository.storeTrust(accountId, fingerprint, trusted ? OpenPgpTrustStore.Trust.trusted : OpenPgpTrustStore.Trust.untrusted)
                .compose(schedulers.executeUiSafeCompletable())
                .subscribe());
    }

    public Single<EntityBareJid> getJid(UUID accountId) {
        return accountRepository.getAccount(accountId).toSingle()
                .compose(schedulers.executeUiSafeSingle())
                .map(Account::getJid);
    }

    public Observable<Optional<OpenPgpV4Fingerprint>> observeLocalFingerprint(UUID accountId) {
        return openPgpRepository.observeLocalFingerprintOf(accountId);
    }

    public Observable<List<FingerprintViewItem>> observeRemoteFingerprints(UUID accountId) {
        return observeLocalFingerprint(accountId)
                .flatMap(optional -> accountRepository.getAccount(accountId).toSingle()
                        .flatMapObservable(account -> openPgpRepository.observeFingerprints(accountId, account.getJid())
                                .map(list -> {
                                    if (!optional.isPresent()) {
                                        return list;
                                    }

                                    List<FingerprintViewItem> remoteFingerprints = new ArrayList<>();
                                    for(FingerprintViewItem f : list) {
                                        if (!f.getFingerprint().equals(optional.getItem())) {
                                            remoteFingerprints.add(f);
                                        }
                                    }
                                    return remoteFingerprints;
                                })));
    }

    public Completable unpublishPublicKey(UUID accountId, OpenPgpV4Fingerprint fingerprint) {
        return deletePublicKeyNode(accountId, fingerprint)
                .andThen(removePublicKeyFromPubKeyList(accountId, fingerprint));
    }

    private Completable removePublicKeyFromPubKeyList(UUID accountId, OpenPgpV4Fingerprint fingerprint) {
        return Completable.fromAction(() -> {
            XMPPConnection xmppConnection = connectionManager.getConnection(accountId).getConnection();
            PepManager pepManager = PepManager.getInstanceFor(xmppConnection);
            PubSubManager pubSubManager = pepManager.getPepPubSubManager();
            PublicKeysListElement publishedKeys = OpenPgpPubSubUtil.fetchPubkeysList(xmppConnection);
            PublicKeysListElement.Builder builder = PublicKeysListElement.builder();
            for (PublicKeysListElement.PubkeyMetadataElement meta : publishedKeys.getMetadata().values()) {
                if (meta.getV4Fingerprint().equals(fingerprint)) {
                    continue;
                }
                builder.addMetadata(meta);
            }
            publishedKeys = builder.build();

            LeafNode metadataNode = pubSubManager.getOrCreateLeafNode(PEP_NODE_PUBLIC_KEYS);
            metadataNode.publish(new PayloadItem<>(publishedKeys));
        });
    }

    private Completable deletePublicKeyNode(UUID accountId, OpenPgpV4Fingerprint fingerprint) {
        return Completable.fromAction(() -> {
            XMPPConnection xmppConnection = connectionManager.getConnection(accountId).getConnection();
            PepManager pepManager = PepManager.getInstanceFor(xmppConnection);
            OpenPgpPubSubUtil.deletePublicKeyNode(pepManager, fingerprint);
        });
    }
}
