package com.p3.bewoplaner.page.login;

import com.google.gson.JsonObject;

import com.bumptech.glide.Glide;
import com.p3.bewoplaner.R;
import com.p3.bewoplaner.data.AppPrefsConstants;
import com.p3.bewoplaner.data.api.common.ErrorHandlingResponseConverter;
import com.p3.bewoplaner.data.api.common.ErrorThrowable;
import com.p3.bewoplaner.data.api.common.ErrorType;
import com.p3.bewoplaner.data.api.common.ErrorUtils;
import com.p3.bewoplaner.data.api.responses.RegisterResponse;
import com.p3.bewoplaner.data.api.services.LoginService;
import com.p3.bewoplaner.data.api.services.RegisterService;
import com.p3.bewoplaner.data.api.services.VoucherService;
import com.p3.bewoplaner.data.prefs.AppPrefs;
import com.p3.bewoplaner.data.prefs.BooleanPreference;
import com.p3.bewoplaner.data.prefs.LongPreference;
import com.p3.bewoplaner.data.prefs.StringPreference;
import com.p3.bewoplaner.data.repository.GroupRealmRepository;
import com.p3.bewoplaner.data.repository.MessageRealmRepository;
import com.p3.bewoplaner.data.repository.RealmProvider;
import com.p3.bewoplaner.data.repository.UserRealmRepository;
import com.p3.bewoplaner.model.User;
import com.p3.bewoplaner.page.main.LogoutNavigation;
import com.p3.bewoplaner.page.main.MainNavigation;
import com.p3.bewoplaner.page.mvp.MVPAbstractPresenter;
import com.p3.bewoplaner.utils.SystemUtils;
import com.p3.bewoplaner.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.functions.Func1;
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";

    @Inject
    LoginService mLoginService;

    @Inject
    RegisterService mRegisterService;

    @Inject
    VoucherService mVoucherService;

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

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

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

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

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

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

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

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

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

    @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;

    private String mLastVoucher;

    @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);
    }

    /**
     * Try to login with the given parameters
     * @param username login username as {@code String}
     * @param password login password as {@code String}
     */
    private void loginCall(String username, String password) {
        getView().showLoading();
        Subscription subscription = mLoginService.login(username, password)
                .compose(RxUtils.provideDefaultTransformer())
                .flatMap(new ErrorHandlingResponseConverter<>())
                .map(loginResponse -> 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;
        }
        if (ErrorUtils.getErrorType(throwable) == ErrorType.DATA_INPUT) {
            ErrorThrowable errorThrowable = (ErrorThrowable) throwable;
            if (errorThrowable.getError().loginFailed != null && errorThrowable.getError().loginFailed.size() > 0) {
                getView().showError(errorThrowable.getError().loginFailed.get(0));
                return;
            }
        }
        getView().showError(R.string.error_something_went_wrong);
    }

    /**
     * Set user data in preferences if login is successful.
     * @param user {@code User} the current logged in user.
     */

    private void loginSuccess(User user) {
        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) {
        if (!checkPassword(password)) {
            return;
        }
        if (!checkUsername(username)) {
            return;
        }
        loginCall(username, password);
    }

    /**
     * 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);
    }

    /**
     * Checks if the voucher necessary for login is correct.
     */

    @Override
    public void checkVoucher(String customerId, String voucher, String password, boolean isChecked) {
        mLastVoucher = voucher;
        mAccountNumberPreference.set(customerId);
        mSavePasswordPreference.set(isChecked);
        if (!checkPassword(password)) {
            return;
        }
        if (TextUtils.isEmpty(voucher)) {
            getView().showError(R.string.error_voucher_code_must_not_be_null);
            return;
        }
        if (TextUtils.isEmpty(customerId)) {
            getView().showError(R.string.error_customer_id_must_not_be_null);
            return;
        }
        getView().showLoading();
        Subscription subscription = mVoucherService.checkVoucher(customerId, voucher)
                .flatMap(new Func1<JsonObject, Observable<Boolean>>() {
                    @Override
                    public Observable<Boolean> call(JsonObject response) {
                        if (response.has(voucher) && response.get(voucher) != null) {
                            JsonObject voucherObject = response.getAsJsonObject(voucher);
                            if (voucherObject.has("CustomerID")
                                    && !TextUtils.isEmpty(voucherObject.get("CustomerID").getAsString())
                                    && voucherObject.has("ServerName")
                                    && !TextUtils.isEmpty(voucherObject.get("ServerName").getAsString())) {
                                String serverName = voucherObject.get("ServerName").getAsString();
                                if (!serverName.startsWith("https")) {
                                    serverName = "https://" + serverName + "/";
                                }
                                mApiEndpointPreference.set(serverName);
                                return Observable.just(true);
                            }
                        }
                        return Observable.just(false);
                    }
                })
                .compose(RxUtils.provideDefaultTransformer())
                .subscribe(this::checkVoucherSuccessful, this::checkVoucherError);
        addSubscription(subscription);
    }

    private void checkVoucherError(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 checkVoucherSuccessful(Boolean vouchIsValid) {
        getView().showContent();
        if (vouchIsValid) {
            mChatCodePreference.set(mLastVoucher);
            getView().voucherIsValid();
            return;
        }
        deleteData(true);
        getView().voucherIsNotValid();
    }

    /**
     * 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);
                    }
                });
    }

    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));
            }
        }
    }

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

    @Override
    public void checkIfVoucherStillAvailable(LogoutNavigation logoutNavigation) {
        if (mAccountNumberPreference.get() == null) {
            return;
        }
        String voucher = mChatCodePreference.get();
        String customerId = mAccountNumberPreference.get();
        Subscription subscription = mVoucherService.checkVoucher(customerId, voucher)
                .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, "Please make sure you have internet connection", Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onNext(JsonObject response) {
                        if (!response.has(voucher) || response.get(voucher) == null) {
                            logoutNavigation.logoutDirectly();
                            return;
                        }
                        JsonObject voucherObject = response.getAsJsonObject(voucher);
                        if (!voucherObject.has("CustomerID")
                                || TextUtils.isEmpty(voucherObject.get("CustomerID").getAsString())
                                || !voucherObject.has("ServerName")
                                || TextUtils.isEmpty(voucherObject.get("ServerName").getAsString())) {
                            logoutNavigation.logoutDirectly();
                        }
                    }
                });
        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 registerSuccessful(String s) {
    }

    private boolean checkPassword(String password) {
        if (password.length() >= 8) {
            return true;
        }
        getView().showError(R.string.error_password_too_short);
        return false;
    }

    private boolean checkUsername(String username) {
        if (username.length() >= 4) {
            return true;
        }
        getView().showError(R.string.error_username_too_short);
        return false;
    }
}
