package de.beyondsoft.ownchat.page.login;

import com.google.gson.JsonObject;

import com.bumptech.glide.Glide;

import de.beyondsoft.ownchat.BewoplanerApplication;
import de.beyondsoft.ownchat.R;
import de.beyondsoft.ownchat.data.AppPrefsConstants;
import de.beyondsoft.ownchat.data.api.common.ErrorHandlingResponseConverter;
import de.beyondsoft.ownchat.data.api.common.ErrorThrowable;
import de.beyondsoft.ownchat.data.api.common.ErrorType;
import de.beyondsoft.ownchat.data.api.common.ErrorUtils;
import de.beyondsoft.ownchat.data.api.responses.RegisterResponse;
import de.beyondsoft.ownchat.data.api.services.LoginService;
import de.beyondsoft.ownchat.data.api.services.RegisterService;
import de.beyondsoft.ownchat.data.prefs.AppPrefs;
import de.beyondsoft.ownchat.data.prefs.BooleanPreference;
import de.beyondsoft.ownchat.data.prefs.LongPreference;
import de.beyondsoft.ownchat.data.prefs.StringPreference;
import de.beyondsoft.ownchat.data.repository.GroupRealmRepository;
import de.beyondsoft.ownchat.data.repository.MessageRealmRepository;
import de.beyondsoft.ownchat.data.repository.RealmProvider;
import de.beyondsoft.ownchat.data.repository.UserRealmRepository;
import de.beyondsoft.ownchat.model.User;
import de.beyondsoft.ownchat.page.main.LogoutNavigation;
import de.beyondsoft.ownchat.page.main.MainNavigation;
import de.beyondsoft.ownchat.page.mvp.MVPAbstractPresenter;
import de.beyondsoft.ownchat.utils.SystemUtils;
import de.beyondsoft.ownchat.utils.rx.RxUtils;

import com.securepreferences.SecurePreferences;

import android.app.NotificationManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;

import java.io.File;
import java.net.SocketTimeoutException;

import javax.inject.Inject;
import javax.inject.Named;

import io.realm.Realm;
import rx.Observable;
import rx.Subscriber;
import rx.Subscription;
import rx.schedulers.Schedulers;

/**
 * Created by laszloffiferenc on 2/21/17.
 */
public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements LoginView.Callback {

    private static final String TAG = "error";

    //region Dependency injection
    @Inject
    LoginService mLoginService;

    @Inject
    RegisterService mRegisterService;

    @Inject
    @Named(AppPrefsConstants.VERIFY_CHAT_CODE_ERROR_MESSAGE)
    StringPreference mVerifyChatCodeErrorMessage;

    @Inject
    @Named(AppPrefsConstants.SHOW_LOGIN_AGAIN)
    BooleanPreference mShowLoginAgainPreference;

    @Inject
    @Named(AppPrefsConstants.API_ENDPOINT)
    StringPreference mApiEndpointPreference;

    @Inject
    @Named(AppPrefsConstants.LOGIN_TOKEN)
    StringPreference mLoginTokenPreference;

    @Inject
    @Named(AppPrefsConstants.CHAT_CODE)
    StringPreference mChatCodePreference;

    @Inject
    @Named(AppPrefsConstants.USER_PASSWORD)
    StringPreference mPasswordPreference;

    @Inject
    @Named(AppPrefsConstants.USER_NAME)
    StringPreference mUserNamePreference;

    @Inject
    @Named(AppPrefsConstants.SAVE_PASSWORD)
    BooleanPreference mSavePasswordPreference;

    @Inject
    @Named(AppPrefsConstants.USER_ID)
    LongPreference mUserIdReference;

    @Inject
    @Named(AppPrefsConstants.ACCOUNT_NO)
    StringPreference mAccountNumberPreference;

    @Inject
    @Named(AppPrefsConstants.LAST_ACCOUNT_NO)
    StringPreference mLastAccountNumberPreference;

    @Inject
    @Named(AppPrefsConstants.AVATAR)
    StringPreference mAvatarPreference;

    @Inject
    @Named(AppPrefsConstants.NAME)
    StringPreference mProfileNamePreference;

    @Inject
    @Named(AppPrefsConstants.PHONE_NUMBER)
    StringPreference mPhoneNumberPreference;

    @Inject
    @Named(AppPrefsConstants.UPLOAD_MAX_SIZE)
    LongPreference mUploadMaxSizePreference;

    @Inject
    @Named(AppPrefsConstants.IS_EMPLOYEE)
    BooleanPreference mIsEmployee;

    @Inject
    @AppPrefs
    SharedPreferences mSharedPreferences;

    @Inject
    @AppPrefs
    SecurePreferences mSecurePreferences;

    @Inject
    SystemUtils mSystemUtils;

    @Inject
    GroupRealmRepository mGroupRealmRepository;

    @Inject
    MessageRealmRepository mMessageRealmRepository;

    @Inject
    UserRealmRepository mUserRealmRepository;

    @Inject
    RealmProvider mRealmProvider;

    @Inject
    NotificationManager mNotificationManager;

    @Inject
    Glide mGlide;

    @Inject
    Context mContext;
    //endregion

    private String mLastVoucher;

    private int mStatus;

    @Inject
    LoginPresenter() {
    }

    @Override
    protected void afterAttachView(@NonNull LoginView view) {
        super.afterAttachView(view);
        view.setCallback(this);
    }

    @Override
    protected void beforeDetachView(@NonNull LoginView view) {
        view.setCallback(null);
        super.beforeDetachView(view);
    }

    //region Login and device registration

    /**
     * Try to login with the given parameters
     *
     * @param username login username as {@code String}
     * @param password login password as {@code String}
     * @param chatCode login chat code as {@code String}
     */
    private void loginCall(String username, String password, String chatCode) {
        getView().showLoading();

        mLastVoucher = chatCode;

        Subscription subscription = mLoginService.loginWithChatCode(username, password, chatCode)
                .compose(RxUtils.provideDefaultTransformer())
                .flatMap(new ErrorHandlingResponseConverter<>())
                .map(loginResponse -> {
                    if(mSavePasswordPreference.get()) {
                        mUserNamePreference.set(username);
                        mPasswordPreference.set(password);
                    } else {
                        mUserNamePreference.delete();
                        mPasswordPreference.delete();
                    }

                    return loginResponse.response.user;
                })
                .subscribe(this::loginSuccess, this::loginError);

        addSubscription(subscription);
    }

    /**
     * Error to show in view if something went wrong during the login.
     */
    private void loginError(Throwable throwable) {
        getView().showContent();

        if(mSystemUtils.isNetworkUnavailable()) {
            getView().showError(R.string.error_no_internet_connection);
            return;
        }

        try {
            ErrorThrowable errorThrowable = (ErrorThrowable) throwable;

            switch(ErrorUtils.getErrorType(throwable)) {
                case DATA_INPUT:
                    if(errorThrowable.getError().loginFailed != null && errorThrowable.getError().loginFailed.size() > 0) {
                        getView().showError(R.string.error_invalid_login_credentials);
                        return;
                    }
                case WRONG_CHATCODE:
                    if(errorThrowable.getError().chatCodeFailed != null && errorThrowable.getError().chatCodeFailed.size() > 0) {
                        getView().showError(R.string.error_wrong_chat_code);
                        return;
                    }
                case DEACTIVATED_CHATCODE:
                    if(errorThrowable.getError().chatCodeInactive != null && errorThrowable.getError().chatCodeInactive.size() > 0) {
                        getView().showError(R.string.error_deactivated_chat_code);
                        return;
                    }
                case CHATCODE_TOO_SHORT:
                    if(errorThrowable.getError().chatCodeTooShort != null && errorThrowable.getError().chatCodeTooShort.size() > 0) {
                        getView().showError(R.string.error_chat_code_too_short);
                        return;
                    }
                default:
                    getView().showError(R.string.error_something_went_wrong);
            }
        } catch(Exception e) {
            getView().showError(R.string.login_service_type_is_undefined);
            e.printStackTrace();
        }
    }

    /**
     * Set user data in preferences if login is successful.
     *
     * @param user {@code User} the current logged in user.
     */
    private void loginSuccess(User user) {
        String accountNumber = mAccountNumberPreference.get();
        String lastAccountNumber = mLastAccountNumberPreference.get();

        if(!accountNumber.equals(lastAccountNumber)) {
            deleteDataOnCustomerIdChange();
        }

        mIsEmployee.set(user.isEmployee);
        mChatCodePreference.set(mLastVoucher);
        mLoginTokenPreference.set(user.token);
        mUserIdReference.set(user.id);
        mAvatarPreference.set(user.avatar);
        mPhoneNumberPreference.set(user.mobile);
        mProfileNamePreference.set(user.firstName.concat(" ").concat(user.lastName));

        getView().loginSuccessful();
    }

    /**
     * Checks the {@code String} username and {@code String} password
     * if are corresponding to criteria.
     */
    @Override
    public void tryToLogin(String username, String password, String chatCode) {
        if(!checkPassword(password)) {
            return;
        }

        if(!checkUsername(username)) {
            return;
        }

        loginCall(username, password, chatCode);
    }

    /**
     * Register the current device. Used if the login is successful.
     */
    @Override
    public void tryToRegisterDevice(String deviceId, String fcmToken) {
        Subscription subscription = mRegisterService.registerDevice(deviceId, fcmToken)
                .compose(RxUtils.provideDefaultTransformer())
                .flatMap(new ErrorHandlingResponseConverter<>())
                .map(registerResponse -> registerResponse.response)
                .subscribe(this::registerSuccessful, this::registerError);

        addSubscription(subscription);
    }

    private void registerError(Throwable throwable) {
        if(ErrorUtils.getErrorType(throwable) == ErrorType.DATA_INPUT) {
            ErrorThrowable errorThrowable = (ErrorThrowable) throwable;

            if(errorThrowable.getError().fcmToken != null && errorThrowable.getError().fcmToken.size() > 0) {
                Log.d(TAG, "fcmToken error: " + errorThrowable.getError().fcmToken.get(0));
                return;
            }

            if(errorThrowable.getError().deviceId != null && errorThrowable.getError().deviceId.size() > 0) {
                Log.d(TAG, "fcmToken error: " + errorThrowable.getError().deviceId.get(0));
            }
        }
    }

    private void registerSuccessful(String s) {
    }
    //endregion

    //region CheckTypeAndCustomerId

    /**
     * Checks if the voucher necessary for login is correct.
     */
    @Override
    public void checkTypeAndCustomerId(String customerId, String password, boolean isChecked) {
        mLastAccountNumberPreference.set(mAccountNumberPreference.get());

        mAccountNumberPreference.set(customerId);
        mSavePasswordPreference.set(isChecked);

        if(!checkPassword(password)) {
            return;
        }

        if(TextUtils.isEmpty(customerId)) {
            getView().showError(R.string.error_customer_id_must_not_be_null);
            return;
        }

        getView().showLoading();

        Subscription subscription = mLoginService.getServiceType("1", customerId)
                .flatMap(response -> {
                    JsonObject serviceObject = response.getAsJsonObject();

                    if(serviceObject.has("Status")
                            && !TextUtils.isEmpty(serviceObject.get("Status").getAsString())
                            && serviceObject.has("URL")
                            && !TextUtils.isEmpty(serviceObject.get("URL").getAsString())) {


                        String serverName = serviceObject.get("URL").getAsString();

                        if(!serverName.startsWith("https")) {
                            serverName = "https://" + serverName + "/";
                        }

                        mApiEndpointPreference.set(serverName);

                        return Observable.just(true);
                    } else if(serviceObject.has("Status") && !TextUtils.isEmpty(serviceObject.get("Status").getAsString())) {
                        mStatus = serviceObject.get("Status").getAsInt();
                    }

                    return Observable.just(false);
                })
                .compose(RxUtils.provideDefaultTransformer())
                .subscribe(this::checkTypeAndCustomerIdSuccessful, this::checkTypeAndCustomerIdError);

        addSubscription(subscription);
    }

    private void checkTypeAndCustomerIdError(Throwable throwable) {
        getView().showContent();

        if(mSystemUtils.isNetworkUnavailable()) {
            getView().showError(R.string.error_no_internet_connection);
            return;
        }

        getView().showError(R.string.error_something_went_wrong);
    }

    private void checkTypeAndCustomerIdSuccessful(Boolean customerIdIsValid) {
        getView().showContent();

        if(customerIdIsValid) {
            getView().customerIdIsValid();
            return;
        }

        deleteData(false);
        getView().customerIdIsNotValid(mStatus);
    }
    //endregion

    //region UploadMaxSize
    @Override
    public void retrieveUploadMaxSize() {
        getView().showLoading();

        Subscription subscription = mLoginService.getUploadMaxSize()
                .flatMap(response -> {
                    JsonObject maxSizeObject = response.getAsJsonObject();

                    if(maxSizeObject.has("file_upload_max_size")) {
                        mUploadMaxSizePreference.set(maxSizeObject.get("file_upload_max_size").getAsLong());
                    }

                    return Observable.just(0);
                })
                .compose(RxUtils.provideDefaultTransformer())
                .subscribe(this::retrieveUploadMaxSizeSuccessful, this::retrieveUploadMaxSizeError);

        addSubscription(subscription);
    }

    private void retrieveUploadMaxSizeError(Throwable throwable) {
        getView().showContent();

        if(mSystemUtils.isNetworkUnavailable()) {
            getView().showError(R.string.error_no_internet_connection);
            return;
        }

        getView().showError(R.string.login_service_type_is_undefined);
    }

    private void retrieveUploadMaxSizeSuccessful(long retrievedMaxSize) {
        getView().showContent();

        getView().retrievedUploadMaxSize();
    }
    //endregion

    //region Logout

    /**
     * Logout from application deletes all login preferences and redirects to login screen.
     */
    @Override
    public void logout(String deviceId, boolean clearAllPrefs, MainNavigation mainNavigation) {
        mNotificationManager.cancelAll();

        if(!mLoginTokenPreference.isSet()) {
            if(mainNavigation != null) {
                mainNavigation.goToLoginActivity();
            }

            return;
        }

        mRegisterService.unregisterDevice(deviceId)
                .compose(RxUtils.provideDefaultTransformer())
                .flatMap(new ErrorHandlingResponseConverter<>())
                .subscribe(new Subscriber<RegisterResponse>() {
                    @Override
                    public void onCompleted() {
                        //after device unregister we remove also the token
                        mLoginTokenPreference.delete();
                        deleteData(clearAllPrefs);

                        if(mainNavigation != null) {
                            mainNavigation.goToLoginActivity();
                        }
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.d(TAG, "onError: " + e);
                        //after device unregister we remove also the token
                        mLoginTokenPreference.delete();
                        deleteData(clearAllPrefs);

                        if(mainNavigation != null) {
                            mainNavigation.goToLoginActivity();
                        }
                    }

                    @Override
                    public void onNext(RegisterResponse registerResponse) {
                        Log.d(TAG, "onNext: " + registerResponse);
                    }
                });
    }

    @Override
    public void tryToLogout(String deviceId, MainNavigation mainNavigation) {
        if(mSavePasswordPreference.get()) {
            return;
        }

        logout(deviceId, false, mainNavigation);
    }
    //endregion

    @Override
    public void verifyChatCode(LogoutNavigation logoutNavigation) {
        if(mAccountNumberPreference.get() == null) {
            return;
        }

        String chatCode = mChatCodePreference.get();
        String customerId = mAccountNumberPreference.get();
        String userId = mUserIdReference.get().toString();

        Subscription subscription = mLoginService.verifyChatCode(customerId, userId, chatCode)
                .compose(RxUtils.provideDefaultTransformer())
                .subscribe(new Subscriber<JsonObject>() {
                    @Override
                    public void onCompleted() {
                    }

                    @Override
                    public void onError(Throwable e) {
                        if(mSystemUtils.isNetworkUnavailable() || e instanceof SocketTimeoutException) {
                            return;
                        }

                        Toast.makeText(mContext, mContext.getString(R.string.error_token_validation_failed), Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onNext(JsonObject response) {
                        if(!response.has("result") || response.get("result") == null) {
                            getView().showError("Test message after verifying the chat code");
                            logoutNavigation.logoutDirectly(false);
                            return;
                        }

                        int result = response.get("result").getAsInt();

                        boolean shouldClearAllPrefs = false;

                        if(result == 0) {
                            deleteData(true);
                            shouldClearAllPrefs = true;

                            getView().showError(R.string.error_deactivated_chat_code);
                        }

                        if(result == 1) {
                            mShowLoginAgainPreference.set(true);
                        }

                        logoutNavigation.logoutDirectly(shouldClearAllPrefs);
                    }
                });

        addSubscription(subscription);
    }

    /**
     * Clears all login preferences.
     */
    private void deleteData(boolean clearAllPrefs) {
        if(clearAllPrefs) {
            Observable.create(subscriber -> mGlide.clearDiskCache())
                    .subscribeOn(Schedulers.io())
                    .subscribe();

            mGlide.clearMemory();
            mSharedPreferences.edit().clear().apply();
            mAccountNumberPreference.deleteFromSecurePreference();
            mChatCodePreference.deleteFromSecurePreference();
            mSavePasswordPreference.deleteFromSecurePreference();

            File dir = mContext.getExternalFilesDir(null);

            if(dir != null) {
                if(dir.isDirectory()) {
                    String[] children = dir.list();

                    for(String aChildren : children) {
                        //noinspection ResultOfMethodCallIgnored
                        new File(dir, aChildren).delete();
                    }
                } else {
                    Log.e("deleteData", dir.getAbsolutePath() + " is not a directory!");
                }
            } else {
                dir = new File(Environment.getExternalStorageDirectory(), "BeWoPlaner");

                if(dir.isDirectory()) {
                    String[] children = dir.list();

                    for(String aChildren : children) {
                        //noinspection ResultOfMethodCallIgnored
                        new File(dir, aChildren).delete();
                    }
                } else {
                    Log.e("deleteData", dir.getAbsolutePath() + " is not a directory!");
                }
            }

            Realm realm = mRealmProvider.provide();
            realm.executeTransaction(realm1 -> realm1.deleteAll());
        } else {
            mApiEndpointPreference.delete();
        }
    }

    private void deleteDataOnCustomerIdChange() {
        File dir = mContext.getExternalFilesDir(null);
        mGlide.clearMemory();

        if(dir != null) {
            if(dir.isDirectory()) {
                String[] children = dir.list();

                for(String aChildren : children) {
                    //noinspection ResultOfMethodCallIgnored
                    new File(dir, aChildren).delete();
                }
            } else {
                Log.e("deleteData", dir.getAbsolutePath() + " is not a directory!");
            }
        } else {
            dir = new File(Environment.getExternalStorageDirectory(), "BeWoPlaner");

            if(dir.isDirectory()) {
                String[] children = dir.list();

                for(String aChildren : children) {
                    //noinspection ResultOfMethodCallIgnored
                    new File(dir, aChildren).delete();
                }
            } else {
                Log.e("deleteData", dir.getAbsolutePath() + " is not a directory!");
            }
        }

        Realm realm = mRealmProvider.provide();
        realm.executeTransaction(realm1 -> realm1.deleteAll());
    }

    private boolean checkPassword(String password) {
        if(password.length() >= 1) {
            return true;
        }

        getView().showError(R.string.error_password_too_short);
        return false;
    }

    private boolean checkUsername(String username) {
        if(username.length() >= 1) {
            return true;
        }

        getView().showError(R.string.error_username_too_short);
        return false;
    }
}
