package org.mercury_im.messenger.core.store.crypto;

import org.jivesoftware.smackx.ikey.record.IkeyRecord;
import org.jivesoftware.smackx.ox.store.abstr.AbstractOpenPgpTrustStore;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.core.SchedulersFacade;
import org.mercury_im.messenger.core.crypto.ikey.IkeyRepository;
import org.mercury_im.messenger.core.data.repository.IkeyKeyRepository;
import org.mercury_im.messenger.core.data.repository.IkeyRecordRepository;
import org.mercury_im.messenger.core.data.repository.OpenPgpRepository;
import org.mercury_im.messenger.core.data.repository.OpenPgpTrustRepository;
import org.pgpainless.key.OpenPgpV4Fingerprint;

import java.io.IOException;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

import io.reactivex.Completable;
import io.reactivex.Maybe;
import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;

public class IkeyAwareOpenPgpStore extends MercuryOpenPgpStore {

    public IkeyAwareOpenPgpStore(UUID accountId,
                                 OpenPgpRepository openPgpRepository,
                                 OpenPgpTrustRepository openPgpTrustRepository,
                                 IkeyRepository ikeyRepository,
                                 SchedulersFacade schedulersFacade) {
        this(accountId, openPgpRepository, openPgpTrustRepository, ikeyRepository, ikeyRepository, schedulersFacade);
    }

    public IkeyAwareOpenPgpStore(UUID accountId,
                                 OpenPgpRepository openPgpRepository,
                                 OpenPgpTrustRepository openPgpTrustRepository,
                                 IkeyKeyRepository ikeyKeyRepository,
                                 IkeyRecordRepository ikeyRecordRepository,
                                 SchedulersFacade schedulers) {
        super(new KeyStore(accountId, openPgpRepository, schedulers),
                new MetadataStore(accountId, openPgpRepository, schedulers),
                new TrustStore(accountId, openPgpTrustRepository, ikeyKeyRepository, ikeyRecordRepository, schedulers));
    }

    public static class TrustStore extends AbstractOpenPgpTrustStore {

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

        private final UUID accountId;
        private final OpenPgpTrustRepository openPgpTrustRepository;
        private final IkeyKeyRepository ikeyKeyRepository;
        private final IkeyRecordRepository ikeyRecordRepository;
        private final SchedulersFacade schedulers;

        private final CompositeDisposable disposable = new CompositeDisposable();

        public TrustStore(UUID accountId, OpenPgpTrustRepository openPgpTrustRepository, IkeyKeyRepository ikeyKeyRepository, IkeyRecordRepository ikeyRecordRepository, SchedulersFacade schedulersFacade) {
            this.accountId = accountId;
            this.openPgpTrustRepository = openPgpTrustRepository;
            this.ikeyKeyRepository = ikeyKeyRepository;
            this.ikeyRecordRepository = ikeyRecordRepository;
            this.schedulers = schedulersFacade;
        }

        @Override
        protected Trust readTrust(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException {
            EntityBareJid jid = owner.asEntityBareJidOrThrow();
            Trust trust = readIkeyTrust(jid, fingerprint)
                    .blockingGet();
            return trust == null ? Trust.undecided : trust;
        }

        private Single<Trust> readIkeyTrust(EntityBareJid owner, OpenPgpV4Fingerprint fingerprint) {
            return ikeyRecordRepository.loadRecord(accountId, owner)
                    .map(record -> {
                        if (record.getTrust() == Trust.trusted && record.hasSubordinate(fingerprint)) {
                            return Trust.ikey_trusted;
                        }
                        return Trust.undecided;
                    })
                    .firstElement()
                    .flatMap(t -> t == Trust.undecided ? readManualTrust(owner, fingerprint).toMaybe() : Maybe.just(t))
                    .switchIfEmpty(readManualTrust(owner, fingerprint));
        }

        private Single<Trust> readManualTrust(EntityBareJid owner, OpenPgpV4Fingerprint fingerprint) {
            return openPgpTrustRepository.loadTrust(accountId, owner, fingerprint)
                    .doOnSuccess(trust -> LOGGER.log(Level.INFO, "Read manual trust " + trust + " for device key " + fingerprint + " of contact " + owner));
        }

        private Completable writeManualTrust(EntityBareJid owner, OpenPgpV4Fingerprint fingerprint, Trust trust) {
            return openPgpTrustRepository.storeTrust(accountId, owner.asEntityBareJidIfPossible(), fingerprint, trust)
                    .doOnComplete(() -> LOGGER.log(Level.INFO,
                            "Successfully marked device key " + fingerprint + " of " + owner + " as " + trust));
        }

        @Override
        protected void writeTrust(BareJid owner, OpenPgpV4Fingerprint fingerprint, Trust trust) throws IOException {
            EntityBareJid jid = owner.asEntityBareJidOrThrow();
            disposable.add(
                    writeManualTrust(jid, fingerprint, trust)
                            .compose(schedulers.executeUiSafeCompletable())
                            .subscribe(() -> {},
                                    e -> LOGGER.log(Level.SEVERE, "An error happened while marking device key " + fingerprint + " of " + jid + " as " + trust, e)));
        }
    }
}
