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

import org.jivesoftware.smack.util.StringUtils;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
import org.mercury_im.messenger.core.SchedulersFacade;
import org.mercury_im.messenger.core.account.error.PasswordError;
import org.mercury_im.messenger.core.account.error.UsernameError;
import org.mercury_im.messenger.core.connection.MercuryConnection;
import org.mercury_im.messenger.core.connection.MercuryConnectionManager;
import org.mercury_im.messenger.core.connection.exception.InvalidCredentialsException;
import org.mercury_im.messenger.core.connection.exception.ServerUnreachableException;
import org.mercury_im.messenger.core.data.repository.AccountRepository;
import org.mercury_im.messenger.core.util.Optional;
import org.mercury_im.messenger.core.viewmodel.MercuryViewModel;
import org.mercury_im.messenger.entity.Account;

import java.util.logging.Logger;

import javax.inject.Inject;

import io.reactivex.Observable;
import io.reactivex.subjects.BehaviorSubject;

public class LoginViewModel implements MercuryViewModel {

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

    private final MercuryConnectionManager connectionManager;
    private final AccountRepository accountRepository;

    private final SchedulersFacade schedulers;

    private BehaviorSubject<Optional<UsernameError>> loginUsernameError;
    private BehaviorSubject<Optional<PasswordError>> loginPasswordError;
    private BehaviorSubject<Boolean> isLoginPossible;
    private BehaviorSubject<Boolean> isLoginSuccessful;

    private EntityBareJid loginUsernameValue;
    private String loginPasswordValue;

    @Inject
    public LoginViewModel(MercuryConnectionManager connectionManager,
                          AccountRepository accountRepository,
                          SchedulersFacade schedulers) {
        this.connectionManager = connectionManager;
        this.accountRepository = accountRepository;
        this.schedulers = schedulers;

        reset();
    }

    public void reset() {
        loginUsernameError = BehaviorSubject.createDefault(new Optional<>());
        loginPasswordError = BehaviorSubject.createDefault(new Optional<>());
        isLoginPossible = BehaviorSubject.createDefault(false);
        isLoginSuccessful = BehaviorSubject.createDefault(false);

        loginUsernameValue = null;
        loginPasswordValue = null;
    }

    public Observable<Optional<UsernameError>> getLoginUsernameError() {
        return loginUsernameError;
    }

    public Observable<Optional<PasswordError>> getLoginPasswordError() {
        return loginPasswordError;
    }

    public Observable<Boolean> isLoginPossible() {
        return isLoginPossible;
    }

    public Observable<Boolean> isLoginSuccessful() {
        return isLoginSuccessful;
    }

    public synchronized void onLoginUsernameChanged(String username) {
        if (StringUtils.isNullOrEmpty(username)) {
            loginUsernameValue = null;
            loginUsernameError.onNext(new Optional<>(UsernameError.emptyUsername));
        } else {
            try {
                loginUsernameValue = JidCreate.entityBareFrom(username);
                loginUsernameError.onNext(new Optional<>());
            } catch (XmppStringprepException e) {
                loginUsernameValue = null;
                loginUsernameError.onNext(new Optional<>(UsernameError.invalidUsername));
            }
        }
        updateLoginPossible();
    }

    public synchronized void onLoginPasswordChanged(String password) {
        if (StringUtils.isNullOrEmpty(password)) {
            loginPasswordValue = null;
            loginPasswordError.onNext(new Optional<>(PasswordError.emptyPassword));
        } else {
            loginPasswordValue = password;
            loginPasswordError.onNext(new Optional<>());
        }
        updateLoginPossible();
    }

    private synchronized void updateLoginPossible() {
        isLoginPossible.onNext(loginUsernameValue != null && loginPasswordValue != null);
    }

    public synchronized void login() {
        if (!isLoginPossible.getValue()) {
            // Prevent race condition where account would be logged in twice
            return;
        }
        isLoginPossible.onNext(false);

        Account account = createAccountEntity();

        //MercuryConnection connection = connectionManager.createConnection(account);
        addDisposable(accountRepository.upsertAccount(account).ignoreElement()
                //.andThen(connection.connect())
                //.andThen(connection.login())
                //.andThen(connectionManager.registerConnection(connection))
                .subscribeOn(schedulers.getNewThread())
                .observeOn(schedulers.getUiScheduler())
                .subscribe(
                        this::onLoginSuccessful,
                        this::onLoginFailed
                ));
    }

    private Account createAccountEntity() {
        Account account = new Account();
        account.setAddress(loginUsernameValue.asEntityBareJidString());
        account.setPassword(loginPasswordValue);
        account.setEnabled(true);

        return account;
    }

    private void onLoginSuccessful() {
        isLoginSuccessful.onNext(true);
    }

    private void onLoginFailed(Throwable error) {
        isLoginPossible.onNext(true);
        if (error instanceof InvalidCredentialsException) {
            loginPasswordError.onNext(new Optional<>(PasswordError.incorrectPassword));
        } else if (error instanceof ServerUnreachableException) {
            loginUsernameError.onNext(new Optional<>(UsernameError.unreachableServer));
        }
    }
}
