package org.mercury_im.messenger.core.viewmodel.ikey;

import org.bouncycastle.openpgp.PGPException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.ikey.IkeyManager;
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
import org.jivesoftware.smackx.ox.element.SecretkeyElement;
import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException;
import org.jivesoftware.smackx.pubsub.PubSubException;
import org.mercury_im.messenger.core.connection.MercuryConnection;
import org.mercury_im.messenger.core.connection.MercuryConnectionManager;
import org.mercury_im.messenger.core.crypto.OpenPgpSecretKeyBackupPassphraseGenerator;
import org.mercury_im.messenger.core.crypto.ikey.IkeyInitializer;
import org.mercury_im.messenger.core.util.Optional;
import org.mercury_im.messenger.core.viewmodel.MercuryViewModel;

import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.inject.Inject;

import io.reactivex.Maybe;
import io.reactivex.MaybeObserver;
import io.reactivex.Single;
import lombok.Getter;

public class IkeySetupViewModel implements MercuryViewModel {

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

    private final MercuryConnectionManager connectionManager;
    private final IkeyInitializer ikeyInitializer;

    private IkeyManager ikeyManager;
    private SecretkeyElement secretkeyElement;

    @Getter
    private final OpenPgpSecretKeyBackupPassphrase backupCreationPassphrase;

    @Inject
    public IkeySetupViewModel(MercuryConnectionManager connectionManager, IkeyInitializer ikeyInitializer, OpenPgpSecretKeyBackupPassphraseGenerator passphraseGenerator) {
        this.connectionManager = connectionManager;
        this.ikeyInitializer = ikeyInitializer;

        this.backupCreationPassphrase = passphraseGenerator.generateBackupPassphrase();
    }

    public void init(UUID accountId) {
        MercuryConnection connection = connectionManager.getConnection(accountId);
        ikeyManager = ikeyInitializer.initFor(connection);
    }

    public Single<Optional<SecretkeyElement>> fetchBackupElement() {
        return fetchMaybeBackupElement()
                .map(Optional::new)
                .toSingle(new Optional<>());
    }

    private Maybe<SecretkeyElement> fetchMaybeBackupElement() {
        return new Maybe<SecretkeyElement>() {
            @Override
            protected void subscribeActual(MaybeObserver<? super SecretkeyElement> observer) {
                try {
                    secretkeyElement = ikeyManager.fetchSecretIdentityKey();
                    if (secretkeyElement != null) {
                        LOGGER.log(Level.INFO, "Found IKey backup element.");
                        observer.onSuccess(secretkeyElement);
                    } else {
                        LOGGER.log(Level.INFO, "Ikey backup element is null.");
                        observer.onComplete();
                    }
                } catch (PubSubException.NotALeafNodeException e) {
                    LOGGER.log(Level.INFO, "Ikey backup node is not a LeafNode.");
                    observer.onComplete();
                } catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException e) {
                    LOGGER.log(Level.INFO, "Error restoring Ikey backup", e);
                    observer.onError(e);
                }
            }
        };
    }

    public void generateIdentityKey() throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
        ikeyManager.generateIdentityKey();
    }

    public void restoreBackup(OpenPgpSecretKeyBackupPassphrase passphrase) throws IOException, PGPException, InvalidBackupCodeException {
        ikeyManager.restoreSecretKeyBackup(secretkeyElement, passphrase);
    }
}
