package org.mercury_im.messenger.ui.account;

import android.app.Application;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;

import org.jivesoftware.smack.sasl.SASLErrorException;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.Messenger;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.entity.Account;
import org.mercury_im.messenger.xmpp.MercuryConnection;

import java.util.logging.Level;
import java.util.logging.Logger;

import javax.inject.Inject;

import io.reactivex.Completable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;

public class LoginViewModel extends AndroidViewModel {

    private MutableLiveData<Error> usernameError = new MutableLiveData<>(new Error());
    private MutableLiveData<Error> passwordError = new MutableLiveData<>(new Error());
    private MutableLiveData<Boolean> loginButtonEnabled = new MutableLiveData<>(false);
    private MutableLiveData<Boolean> loginCompleted = new MutableLiveData<>(false);

    private final CompositeDisposable disposable = new CompositeDisposable();

    private EntityBareJid username;
    private String password;

    @Inject
    Messenger messenger;

    public LoginViewModel(@NonNull Application application) {
        super(application);
        ((MercuryImApplication) application).getAppComponent().inject(this);
    }

    public void setUsername(String username) {
        if (username == null || username.isEmpty()) {
            this.username = null;
            usernameError.setValue(new Error(getApplication().getResources().getString(R.string.error_field_required)));
        } else {
            try {
                this.username = JidCreate.entityBareFrom(username);
            } catch (XmppStringprepException e) {
                this.username = null;
                usernameError.setValue(new Error(getApplication().getResources().getString(R.string.error_invalid_username)));
            }
        }
        updateLoginButtonState();
    }

    public void setPassword(String password) {
        if (password == null || password.isEmpty()) {
            this.password = null;
            passwordError.setValue(new Error(getApplication().getResources().getString(R.string.error_field_required)));
        } else {
            this.password = password;
        }
        updateLoginButtonState();
    }

    private void updateLoginButtonState() {
        loginButtonEnabled.setValue(username != null && !(password == null || password.isEmpty()));
    }

    public synchronized void login() {
        Boolean loginEnabled = loginButtonEnabled.getValue();
        if (loginEnabled != null && !loginEnabled) {
            // Prevent race condition where account would be logged in twice
            return;
        }
        loginButtonEnabled.setValue(false);
        Account account = new Account();
        account.setAddress(username.asUnescapedString());
        account.setPassword(password);
        MercuryConnection connection = messenger.getConnectionManager().createConnection(account);
        disposable.add(Completable.fromAction(() -> {
            connection.connect().login();
        })
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .doOnComplete(this::signalLoginSuccessful)
                .doOnError(this::signalLoginError)
                .subscribe());
    }

    private void signalLoginSuccessful() {
        Logger.getAnonymousLogger().log(Level.INFO, "Signal Login Successful");
        loginCompleted.setValue(true);
    }

    private void signalLoginError(Throwable error) {
        if (error instanceof SASLErrorException) {
            passwordError.setValue(new Error(getApplication().getResources().getString(R.string.error_incorrect_password)));
            loginButtonEnabled.setValue(true);
        } else {
            Toast.makeText(getApplication(), "A connection error occurred", Toast.LENGTH_LONG).show();
            loginButtonEnabled.setValue(true);
        }
    }

    public LiveData<Error> getPasswordError() {
        return passwordError;
    }

    public LiveData<Error> getUsernameError() {
        return usernameError;
    }

    public LiveData<Boolean> isLoginSuccessful() {
        return loginCompleted;
    }

    public LiveData<Boolean> isLoginEnabled() {
        return loginButtonEnabled;
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        disposable.clear();
    }

    public static class Error {

        private String message;

        public Error() {

        }

        public Error(String errorMessage) {
            this.message = errorMessage;
        }

        public boolean isError() {
            return message != null;
        }

        public String getErrorMessage() {
            return message;
        }
    }
}
