package com.getbubblenow.android.activity;

import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModelProvider;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.airbnb.lottie.LottieAnimationView;
import com.getbubblenow.android.api.enums.Status;
import com.getbubblenow.android.fragment.NetworkListDialogFragment;
import com.getbubblenow.android.model.Network;
import com.getbubblenow.android.resource.StatusResource;
import com.getbubblenow.android.util.UserStore;
import com.getbubblenow.android.viewmodel.MainViewModel;
import com.getbubblenow.android.R;

import java.util.Locale;

public class MainActivity extends BaseActivityBubble {

    private static final int PROGRESS_ANIMATION_DURATION = 2000;
    private static final int PROGRESS_START_ANIMATION_DURATION = 3000;
    private static final float PROGRESS_ANIMATION_START_OFFSET = 0.26f;
    private static final float PROGRESS_ANIMATION_START_OFFSET_END = 0.27f;
    private static final float PROGRESS_ANIMATION_MIN_DURATION = 0.04f;
    private static final float PROGRESS_ANIMATION_END_OFFSET = 0.39f;
    private static final float PERCENTAGE_100 = 100f;

    private static final int REQUEST_CODE_VPN_PERMISSION = 23491;
    private static final int CONNECTED_TEXT_VIEW_LEFT_MARGIN = 16;
    private static final int CONNECTED_TEXT_VIEW_RIGHT_MARGIN = 16;
    private static final int CONNECTED_TEXT_VIEW_TOP_MARGIN = 150;
    private static final int DISCONNECTED_TEXT_VIEW_BOTTOM_MARGIN = 90;
    private static final int CONNECTED_TEXT_VIEW_BOTTOM_MARGIN = 100;
    private static final String BASE_URL_PREFIX = "https://";
    private static final String BASE_URL_SUFFIX_MY_BUBBLE = ":1443/appLogin?session=";
    private static final String BASE_URL_SUFFIX_ACCOUNT = "/appLogin?session=";
    private static final String URI_ACCOUNT = "&uri=/me";
    private static final String URI_LAUNCH_NEW_BUBBLE = "&uri=/new_bubble";
    private static final String NO_INTERNET_CONNECTION = "no internet connection";
    private static final String LOGIN_FAILED = "Login Failed";
    private static final String SUPPORT_URL = "https://support.getbubblenow.com";
    private static final String AUTH_TYPE_KEY = "authType";
    private static final String RESOURCE_KEY = "resource";
    private static final String TAG = "MainActivity";

    private boolean connectionStateFlag;

    private ValueAnimator progressAnimatorStart;
    private ValueAnimator progressAnimatorProgress;
    private MainViewModel mainViewModel;
    private TextView bubbleStatus;
    private TextView titleMyBubble;
    private Button connectButton;
    private Button launchNewBubbleButton;
    private ImageView imageMyBubble;
    private ImageView mark;
    private ImageButton accountButton;
    private ImageButton supportButton;
    private TextView logout;
    private ConstraintLayout constraint_layout_connected_title;
    private ConstraintLayout constraints_layout_connected_bubble;
    private ConstraintLayout constraint_layout_disconnected_title;
    private ConstraintLayout constraints_layout_disconnected_bubble;
    private ImageView union;
    private LottieAnimationView network_progress;
    private TextView progress_title;
    private DialogFragment networkListDialogFragment;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);
        showLoadingDialog();

        final MutableLiveData<Boolean> data = (mainViewModel.isUserLoggedInToSage(this)) ?
                mainViewModel.updatePostAuthLocalization(this, Locale.getDefault())
                : mainViewModel.updatePreAuthLocalization(this, Locale.getDefault());

        data.observe(this, isLocalizationDataUpdated -> {
            if (isLocalizationDataUpdated) {
                Log.d(LOCALIZATION_TAG, "Success");
            } else {
                Log.d(LOCALIZATION_TAG, "Fail");
            }
            startBubbleSession();
        });
    }

    @Override protected void onStop() {
        super.onStop();
        mainViewModel.disposeProgress();
    }

    private void startBubbleSession() {
        if (mainViewModel.isHaveSageURL(this)) {
            if (mainViewModel.isHaveHostName(this)) {
                closeLoadingDialog();
                setContentView(R.layout.activity_main);
                initUI();
                logout.setEnabled(true);
                connectButton.setEnabled(true);
                connectButton.setVisibility(View.VISIBLE);

                mainViewModel.buildRepositoryInstance(this, mainViewModel.getSageURL(this));

                if (mainViewModel.isVPNConnected(this, connectionStateFlag)) {
                    connectionStateFlag = false;
                    setConnectionStateUI(false);
                } else {
                    connectionStateFlag = true;
                    setConnectionStateUI(true);
                }
            } else {
                setContentView(R.layout.activity_main);
                initUI();
                logout.setEnabled(true);

                if (mainViewModel.checkRepositoryInstance()) {
                    mainViewModel.buildRepositoryInstance(this, mainViewModel.getSageURL(this));
                } else {
                    mainViewModel.buildClientService(mainViewModel.getSageURL(this));
                }
                checkBubbleCurrentStatus();
            }
        } else {
            closeLoadingDialog();
            final Intent intent = new Intent(this, LoginActivity.class);
            startActivity(intent);
            finish();
        }
    }

    private void checkBubbleCurrentStatus() {
        mainViewModel.checkBubbleOnce(this).observe(this, statusResource -> {
            switch (statusResource.status) {
                case SUCCESS:
                    if (statusResource.data == null) {
                        return;
                    }
                    if (statusResource.data.isEmpty()) {
                        setNonAvailableBubbleUI();
                        closeLoadingDialog();
                        requestBubbleStatusContinuously();
                    } else {
                        if (statusResource.data.size() == 1) {
                            loginToBubble(statusResource.data.get(0).getUuid());
                        } else {
                            final String uuid = UserStore.getInstance(this).getNetworkUuid();
                            if (networkByUuidExist(statusResource.data, uuid)) {
                                loginToBubble(uuid);
                            } else {
                                networkListDialogFragment = new NetworkListDialogFragment(
                                        statusResource.data, (view, bubble) -> {
                                            loginToBubble(bubble.getUuid());
                                            networkListDialogFragment.dismiss();
                                        }
                                );
                                networkListDialogFragment.show(getSupportFragmentManager(), "networkListDialog");
                            }
                        }
                    }
                    break;
                case ERROR:
                    closeLoadingDialog();
                    setNonAvailableBubbleUI();
                    if (NO_INTERNET_CONNECTION.equals(statusResource.message)) {
                        showNetworkNotAvailableMessage();
                        requestBubbleStatusContinuously();
                    } else {
                        showErrorDialog(statusResource.message);
                    }
                    break;
            }
        });
    }

    private boolean networkByUuidExist(final Iterable<Network> networkList, final String uuid) {
        for (final Network network : networkList) {
            if (uuid.equals(network.getUuid())) {
                return true;
            }
        }
        return false;
    }

    private void requestBubbleStatusContinuously() {
        mainViewModel.checkBubbleContinuously(this).observe(this, statusResource -> {
            if (statusResource.status == Status.SUCCESS) {
                if (statusResource.data != null && !statusResource.data.isEmpty()) {
                    loginToBubble(statusResource.data.get(0).getUuid());
                }
            }
        });
    }

    private void loginToBubble(final String uuid) {
        mainViewModel.loginToNode(this, uuid).observe(this, statusResource -> {
            switch (statusResource.status) {
                case SUCCESS:
                    setConnectedBubbleUI();
                    mainViewModel.getConfig(this).observe(this, this::processVpnConfig);
                    break;
                case LOADING:
                    break;
                case AUTH:
                    closeLoadingDialog();
                    switch (statusResource.authType) {
                        case SAGE:
                            Log.d(TAG, "Login to sage");
                            logout();
                            break;
                        case TOTP:
                        case SMS:
                        case EMAIL:
                            final Intent mfaVerifyActivityIntent = new Intent(this, MFAVerifyActivity.class);
                            mfaVerifyActivityIntent.putExtra(AUTH_TYPE_KEY, statusResource.authType);
                            startActivity(mfaVerifyActivityIntent);
                            break;
                        case NODE:
                            final String resource = statusResource.message;
                            final Intent loginActivityIntent = new Intent(this, LoginActivity.class);
                            if (resource != null && !resource.isEmpty()) {
                                loginActivityIntent.putExtra(RESOURCE_KEY, resource);
                            }
                            startActivity(loginActivityIntent);
                            finish();
                            break;
                    }
                    break;
                case EMPTY:
                    closeLoadingDialog();
                    setConnectedBubbleUI();
                    checkBubbleCurrentStatus();
                    break;
                case ERROR:
                    closeLoadingDialog();
                    if (NO_INTERNET_CONNECTION.equals(statusResource.message)) {
                        showNetworkNotAvailableMessage();
                    } else if (LOGIN_FAILED.equals(statusResource.message)) {
                        Toast.makeText(this, getString(R.string.login_failed), Toast.LENGTH_LONG).show();
                    } else {
                        showErrorDialog(statusResource.message);
                    }
                    break;
            }
        });

        mainViewModel.getNetworkStatusLiveData().observe(this, statusResource -> {
            final MutableLiveData<Long> progress = mainViewModel.getProgressLiveData();
            switch (statusResource.status) {
                case SUCCESS:
                    final Network network = statusResource.data;
                    if (network != null) {
                        if (network.isStarting() || network.isRestoring()) {
                            setProgressBubbleUI();
                            progress.observe(this, this::setProgress);
                        } else if (network.isRunning()) {
                            showLoadingDialog();
                            setConnectedBubbleUI();
                            break;
                        }
                    }
                    break;
                case EMPTY:
                    progress.removeObservers(this);
                    checkBubbleCurrentStatus();
                    break;
            }
        });
    }

    private void processVpnConfig(final StatusResource<String> stringStatusResource) {
        switch (stringStatusResource.status) {
            case SUCCESS:
                final String config = stringStatusResource.data;
                mainViewModel.setConfig(config,this);
                createTunnel();
                break;
            case LOADING:
            case EMPTY:
            case AUTH:
                break;
            case ERROR:
                closeLoadingDialog();
                if (NO_INTERNET_CONNECTION.equals(stringStatusResource.message)) {
                    showNetworkNotAvailableMessage();
                } else if (LOGIN_FAILED.equals(stringStatusResource.message)) {
                    Toast.makeText(this, getString(R.string.login_failed), Toast.LENGTH_LONG).show();
                } else {
                    showErrorDialog(stringStatusResource.message);
                }
                break;
        }
    }

    private void initUI() {
        initViews();
        initListeners();
    }

    private void initViews() {
        bubbleStatus = findViewById(R.id.bubbleStatus);
        connectButton = findViewById(R.id.connectButton);
        launchNewBubbleButton = findViewById(R.id.launchNewBubbleButton);
        imageMyBubble = findViewById(R.id.imageMyBubble);
        supportButton = findViewById(R.id.supportButton);
        mark = findViewById(R.id.mark);
        titleMyBubble = findViewById(R.id.titleMyBubble);
        accountButton = findViewById(R.id.accountButton);
        logout = findViewById(R.id.logout);
        constraint_layout_connected_title = findViewById(R.id.constraint_layout_connected_title);
        constraints_layout_connected_bubble = findViewById(R.id.constraints_layout_connected_bubble);
        constraint_layout_disconnected_title = findViewById(R.id.constraint_layout_disconnected_title);
        constraints_layout_disconnected_bubble = findViewById(R.id.constraints_layout_disconnected_bubble);
        union = findViewById(R.id.union);
        network_progress = findViewById(R.id.network_progress);
        progress_title = findViewById(R.id.progress_title);
        connectButton.setVisibility(View.GONE);
        launchNewBubbleButton.setVisibility(View.GONE);
    }

    private void initListeners() {
        connectButton.setOnClickListener(v -> connect());
        launchNewBubbleButton.setOnClickListener(v -> startNewBubble());
        accountButton.setOnClickListener(v -> showAccount());
        logout.setOnClickListener(v -> logout());
        supportButton.setOnClickListener(v -> showSupport());
    }

    private void startNewBubble() {
        final String hostname = mainViewModel.getHostNameForAccount(this);
        if (TextUtils.isEmpty(hostname)) {
            final String sageToken = mainViewModel.getSageToken(this);
            final String sageHostname = mainViewModel.getSageHostname(this);
            startActivityForUrl(sageHostname + BASE_URL_SUFFIX_ACCOUNT + sageToken + URI_LAUNCH_NEW_BUBBLE);
        } else {
            final String token = mainViewModel.getToken(this);
            startActivityForUrl(BASE_URL_PREFIX + hostname + BASE_URL_SUFFIX_MY_BUBBLE + token + URI_LAUNCH_NEW_BUBBLE);
        }
    }

    private void showAccount() {
        final String hostname = mainViewModel.getHostNameForAccount(this);
        if (TextUtils.isEmpty(hostname)) {
            final String sageToken = mainViewModel.getSageToken(this);
            final String sageHostname = mainViewModel.getSageHostname(this);
            startActivityForUrl(sageHostname + BASE_URL_SUFFIX_ACCOUNT + sageToken + URI_ACCOUNT);
        } else {
            final String token = mainViewModel.getToken(this);
            startActivityForUrl(BASE_URL_PREFIX + hostname + BASE_URL_SUFFIX_MY_BUBBLE + token + URI_ACCOUNT);
        }
    }

    private void startActivityForUrl(final String url) {
        final Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(Uri.parse(url));
        startActivity(intent);
    }

    private void showSupport(){
        final Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(Uri.parse(SUPPORT_URL));
        startActivity(intent);
    }

    private void connect() {
        mainViewModel.isHaveTunnel(this).observe(this, booleanStatusResource -> {
            if (booleanStatusResource.data != null) {
                if (booleanStatusResource.data) {
                    final boolean state = mainViewModel.isVPNConnected(this, connectionStateFlag);
                    connectionStateFlag = state;
                    mainViewModel
                            .connect(state, this)
                            .observe(this, this::setConnectionStateUI);
                } else {
                    createTunnel();
                    connect();
                }
            }
        });
    }

    private void createTunnel(){
        showLoadingDialog();
        mainViewModel
                .createTunnel(this, UserStore.getInstance(this).getConfig())
                .observe(this, objectStatusResource -> {
            switch (objectStatusResource.status) {
                case SUCCESS:
                    closeLoadingDialog();
                    logout.setEnabled(true);
                    connectButton.setEnabled(true);
                    Toast.makeText(this, getString(R.string.success), Toast.LENGTH_SHORT).show();
                    Log.d("TAG", "Success");
                    break;
                case LOADING:
                case EMPTY:
                case AUTH:
                    break;
                case ERROR:
                    closeLoadingDialog();
                    if (NO_INTERNET_CONNECTION.equals(objectStatusResource.message)) {
                        showNetworkNotAvailableMessage();
                    } else if (LOGIN_FAILED.equals(objectStatusResource.message)) {
                        Toast.makeText(this, getString(R.string.login_failed), Toast.LENGTH_LONG).show();
                    } else {
                        showErrorDialog(objectStatusResource.message);
                    }
                    break;
            }
        });
    }

    @Override protected void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_CODE_VPN_PERMISSION) {
            if (resultCode == RESULT_OK) {
                mainViewModel
                        .connectWithPermission(connectionStateFlag, this)
                        .observe(this, this::setConnectionStateUI);
            } else {
                connectionStateFlag = false;
                setConnectionStateUI(false);
            }
        }
    }

    private void setConnectionStateUI(final boolean state) {
        if (state) {
            bubbleStatus.setText(getString(R.string.connected_bubble));
            bubbleStatus.setTextColor(getResources().getColor(R.color.connectedColor));
            bubbleStatus.setPadding(CONNECTED_TEXT_VIEW_LEFT_MARGIN, CONNECTED_TEXT_VIEW_TOP_MARGIN, CONNECTED_TEXT_VIEW_RIGHT_MARGIN, DISCONNECTED_TEXT_VIEW_BOTTOM_MARGIN);
            connectButton.setText(getString(R.string.disconnect));
            imageMyBubble.setImageResource(R.drawable.bubble_connected);
            mark.setVisibility(View.VISIBLE);
            titleMyBubble.setVisibility(View.GONE);
        } else {
            bubbleStatus.setText(getString(R.string.not_connected_bubble));
            bubbleStatus.setTextColor(getResources().getColor(R.color.gray));
            bubbleStatus.setPadding(CONNECTED_TEXT_VIEW_LEFT_MARGIN, CONNECTED_TEXT_VIEW_TOP_MARGIN, CONNECTED_TEXT_VIEW_RIGHT_MARGIN, CONNECTED_TEXT_VIEW_BOTTOM_MARGIN);
            connectButton.setText(getString(R.string.connect));
            imageMyBubble.setImageResource(R.drawable.bubble_disconnected);
            mark.setVisibility(View.GONE);
            titleMyBubble.setVisibility(View.VISIBLE);
        }
    }

    private void logout() {
        final Intent intent = new Intent(this, LoginActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        mainViewModel.deleteTunnel();
        mainViewModel.removeSharedPreferences(this);
        mainViewModel.clearCredentials(getApplicationContext());
        mainViewModel.disposeBubbleCheck();
        startActivity(intent);
    }

    private void setConnectedBubbleUI() {
        union.setImageResource(R.drawable.union);
        constraint_layout_connected_title.setVisibility(View.VISIBLE);
        constraints_layout_connected_bubble.setVisibility(View.VISIBLE);
        constraint_layout_disconnected_title.setVisibility(View.GONE);
        constraints_layout_disconnected_bubble.setVisibility(View.GONE);

        connectButton.setEnabled(true);
        connectButton.setVisibility(View.VISIBLE);
        launchNewBubbleButton.setVisibility(View.GONE);

    }

    private void setNonAvailableBubbleUI() {
        union.setImageResource(R.drawable.union);
        constraint_layout_connected_title.setVisibility(View.VISIBLE);
        constraints_layout_connected_bubble.setVisibility(View.VISIBLE);
        constraint_layout_disconnected_title.setVisibility(View.GONE);
        constraints_layout_disconnected_bubble.setVisibility(View.GONE);

        launchNewBubbleButton.setEnabled(true);
        launchNewBubbleButton.setVisibility(View.VISIBLE);
        connectButton.setVisibility(View.GONE);
    }

    private void setProgressBubbleUI() {
        closeLoadingDialog();
        union.setImageResource(R.drawable.progress_background);
        constraint_layout_connected_title.setVisibility(View.GONE);
        constraints_layout_connected_bubble.setVisibility(View.GONE);
        constraint_layout_disconnected_title.setVisibility(View.VISIBLE);
        constraints_layout_disconnected_bubble.setVisibility(View.VISIBLE);

        // Progress animation setup
        if (progressAnimatorProgress == null) {
            progressAnimatorProgress = ValueAnimator.ofFloat(PROGRESS_ANIMATION_START_OFFSET, PROGRESS_ANIMATION_START_OFFSET_END);
        }
        progressAnimatorProgress.setRepeatMode(ValueAnimator.REVERSE);
        progressAnimatorProgress.setRepeatCount(ValueAnimator.INFINITE);
        progressAnimatorProgress.setDuration(PROGRESS_ANIMATION_DURATION);
        progressAnimatorProgress.addUpdateListener(animation1 ->
                network_progress.setProgress((Float)animation1.getAnimatedValue()));

        if (progressAnimatorStart == null) {
            progressAnimatorStart = ValueAnimator.ofFloat(0f, PROGRESS_ANIMATION_START_OFFSET);
        }
        progressAnimatorStart.setDuration(PROGRESS_START_ANIMATION_DURATION);
        progressAnimatorStart.addUpdateListener(animation ->
            network_progress.setProgress((Float)animation.getAnimatedValue())
        );

        progressAnimatorStart.addListener(new ProgressAnimatorListenerAdapter());

        if (!progressAnimatorProgress.isRunning()) {
            if (!progressAnimatorStart.isRunning()) {
                progressAnimatorStart.start();
            }
        }
    }

    private void setProgress(final long percent) {
        float value = percent;

        value = PROGRESS_ANIMATION_START_OFFSET + PROGRESS_ANIMATION_END_OFFSET * (value / PERCENTAGE_100);

        if (!progressAnimatorStart.isRunning()) {
            if (value < PROGRESS_ANIMATION_START_OFFSET_END) {
                value = PROGRESS_ANIMATION_START_OFFSET_END;
            }
            float minValue = value - PROGRESS_ANIMATION_MIN_DURATION;
            if (minValue < PROGRESS_ANIMATION_START_OFFSET) {
                minValue = PROGRESS_ANIMATION_START_OFFSET;
            }
            progressAnimatorProgress.getCurrentPlayTime();
            progressAnimatorProgress.setFloatValues(minValue, value);
        }

        final String percentStr = percent + "%";
        progress_title.setText(percentStr);
    }

    private class ProgressAnimatorListenerAdapter extends AnimatorListenerAdapter {
        @Override public void onAnimationEnd(final Animator animation) {
            progressAnimatorProgress.start();
        }
    }
}
