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

import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.mercury_im.messenger.core.SchedulersFacade;
import org.mercury_im.messenger.core.connection.MercuryConnectionManager;
import org.mercury_im.messenger.core.connection.state.ConnectionPoolState;
import org.mercury_im.messenger.core.connection.state.ConnectionState;
import org.mercury_im.messenger.core.data.repository.AccountRepository;
import org.mercury_im.messenger.core.data.repository.OpenPgpRepository;
import org.mercury_im.messenger.core.viewmodel.MercuryViewModel;
import org.mercury_im.messenger.entity.Account;
import org.pgpainless.key.OpenPgpV4Fingerprint;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.inject.Inject;

import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.ObservableTransformer;

public class AccountListViewModel implements MercuryViewModel {

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

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

    @Inject
    public AccountListViewModel(MercuryConnectionManager connectionManager,
                                AccountRepository accountRepository,
                                OpenPgpRepository openPgpRepository,
                                SchedulersFacade schedulers) {

        this.connectionManager = connectionManager;
        this.accountRepository = accountRepository;
        this.openPgpRepository = openPgpRepository;
        this.schedulers = schedulers;
    }

    public Observable<List<AccountViewItem>> observeAccounts() {
        return connectionManager.observeConnectionPool()
                .compose(toAccountViewItems);
    }

    public void onToggleAccountEnabled(Account account, boolean enabled) {
        account.setEnabled(enabled);
        addDisposable(accountRepository.upsertAccount(account)
                .subscribeOn(schedulers.getIoScheduler())
                .observeOn(schedulers.getUiScheduler())
                .subscribe(
                        success -> logAccountToggledSuccess(success, enabled),
                        error -> logAccountToggleError(account, enabled, error)
                )
        );
    }

    private void logAccountToggledSuccess(Account account, boolean enabled) {
        LOGGER.log(Level.INFO, "Account " + account.getAddress() + (enabled ? " enabled" : " disabled"));
    }

    private void logAccountToggleError(Account account, boolean enabled, Throwable error) {
        LOGGER.log(Level.SEVERE, "Account " + account.getAddress() + " could not be " + (enabled ? "enabled" : "disabled"), error);
    }

    private final ObservableTransformer<ConnectionPoolState, List<AccountViewItem>> toAccountViewItems = new ObservableTransformer<ConnectionPoolState, List<AccountViewItem>>() {
        @Override
        public ObservableSource<List<AccountViewItem>> apply(Observable<ConnectionPoolState> upstream) {
            return upstream.map(state -> {
                List<AccountViewItem> viewItems = new ArrayList<>();
                for (Map.Entry<UUID, ConnectionState> entry : state.getConnectionStates().entrySet()) {
                    ConnectionState connectionState = entry.getValue();
                    Account account = accountRepository.getAccount(entry.getKey()).blockingGet();
                    OpenPgpV4Fingerprint fingerprint = null;
                    try {
                        PGPSecretKeyRingCollection secretKeyRings = openPgpRepository.loadSecretKeysOf(account.getId(), account.getJid()).blockingGet();
                        fingerprint = new OpenPgpV4Fingerprint(secretKeyRings.getKeyRings().next());
                    } catch (NoSuchElementException e) {
                        LOGGER.log(Level.INFO, "No fingerprint found for " + account.getAddress());
                    }
                    viewItems.add(new AccountViewItem(account, connectionState.getConnectivity(), fingerprint));
                }
                return viewItems;
            });
        }
    };

    public void onDeleteAccount(UUID accountId) {
        addDisposable(accountRepository.deleteAccount(accountId)
                .subscribe(() -> LOGGER.log(Level.INFO, "Account " + accountId + " successfully deleted."),
                        e -> LOGGER.log(Level.SEVERE, "Could not delete account " + accountId, e)));
    }
}
