package allen.town.podcast.fragment.pref;

import android.app.Dialog;
import android.content.Context;
import android.graphics.Paint;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.ViewFlipper;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;

import com.google.android.material.button.MaterialButton;
import com.google.android.material.textfield.TextInputLayout;

import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import allen.town.focus_common.views.AccentMaterialDialog;
import allen.town.podcast.R;
import allen.town.podcast.core.sync.SynchronizationCredentials;
import allen.town.podcast.core.service.download.PodcastHttpClient;
import allen.town.podcast.core.sync.SyncService;
import allen.town.podcast.core.sync.SynchronizationProviderViewData;
import allen.town.podcast.core.sync.SynchronizationSettings;
import allen.town.podcast.core.util.FileNameGenerator;
import allen.town.podcast.core.util.IntentUtils;
import allen.town.podcast.sync.gpoddernet.GpodnetService;
import allen.town.podcast.sync.gpoddernet.model.GpodnetDevice;
import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;

/**
 * Guides the user through the authentication process.
 */
public class GpodderAuthFragment extends DialogFragment {
    public static final String TAG = "GpodnetAuthActivity";

    private ViewFlipper viewFlipper;

    private static final int STEP_DEFAULT = -1;
    private static final int STEP_HOSTNAME = 0;
    private static final int STEP_LOGIN = 1;
    private static final int STEP_DEVICE = 2;
    private static final int STEP_FINISH = 3;

    private int currentStep = -1;

    private GpodnetService service;
    private volatile String username;
    private volatile String password;
    private volatile GpodnetDevice selectedDevice;
    private List<GpodnetDevice> devices;

    @NonNull
    @Override
    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
        AlertDialog.Builder dialog =            new AccentMaterialDialog(
                    getContext(),
                    R.style.MaterialAlertDialogTheme
            );
        dialog.setTitle(GpodnetService.DEFAULT_BASE_HOST);
        dialog.setNegativeButton(R.string.cancel_label, null);
        dialog.setCancelable(false);
        this.setCancelable(false);

        View root = View.inflate(getContext(), R.layout.gpodnetauth_dialog, null);
        viewFlipper = root.findViewById(R.id.viewflipper);
        advance();
        dialog.setView(root);

        return dialog.create();
    }

    private void setupHostView(View view) {
        final Button selectHost = view.findViewById(R.id.chooseHostButton);
        final RadioGroup serverRadioGroup = view.findViewById(R.id.serverRadioGroup);
        final EditText serverUrlText = view.findViewById(R.id.serverUrlText);

        if (!GpodnetService.DEFAULT_BASE_HOST.equals(SynchronizationCredentials.getHosturl())) {
            serverUrlText.setText(SynchronizationCredentials.getHosturl());
        }
        final TextInputLayout serverUrlTextInput = view.findViewById(R.id.serverUrlTextInput);
        serverRadioGroup.setOnCheckedChangeListener((group, checkedId) -> {
            serverUrlTextInput.setVisibility(checkedId == R.id.customServerRadio ? View.VISIBLE : View.GONE);
        });
        selectHost.setOnClickListener(v -> {
            SynchronizationCredentials.clear(getContext());
            if (serverRadioGroup.getCheckedRadioButtonId() == R.id.customServerRadio) {
                SynchronizationCredentials.setHosturl(serverUrlText.getText().toString());
            } else {
                SynchronizationCredentials.setHosturl(GpodnetService.DEFAULT_BASE_HOST);
            }
            service = new GpodnetService(PodcastHttpClient.getHttpClient(),
                    SynchronizationCredentials.getHosturl(), SynchronizationCredentials.getDeviceID(),
                    SynchronizationCredentials.getUsername(), SynchronizationCredentials.getPassword());
            getDialog().setTitle(SynchronizationCredentials.getHosturl());
            advance();
        });
    }

    private void setupLoginView(View view) {
        final EditText username = view.findViewById(R.id.etxtUsername);
        final EditText password = view.findViewById(R.id.etxtPassword);
        final Button login = view.findViewById(R.id.butLogin);
        final TextView txtvError = view.findViewById(R.id.credentialsError);
        final ProgressBar progressBar = view.findViewById(R.id.progBarLogin);
        final TextView createAccount = view.findViewById(R.id.createAccountButton);
        final TextView createAccountWarning = view.findViewById(R.id.createAccountWarning);

        createAccount.setPaintFlags(createAccount.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
        createAccount.setOnClickListener(v -> IntentUtils.openInBrowser(getContext(), "https://gpodder.net/register/"));

        if (SynchronizationCredentials.getHosturl().startsWith("http://")) {
            createAccountWarning.setVisibility(View.VISIBLE);
        }
        password.setOnEditorActionListener((v, actionID, event) ->
                actionID == EditorInfo.IME_ACTION_GO && login.performClick());

        login.setOnClickListener(v -> {
            final String usernameStr = username.getText().toString();
            final String passwordStr = password.getText().toString();

            if (usernameHasUnwantedChars(usernameStr)) {
                txtvError.setText(R.string.gpodnetsync_username_characters_error);
                txtvError.setVisibility(View.VISIBLE);
                return;
            }

            login.setEnabled(false);
            progressBar.setVisibility(View.VISIBLE);
            txtvError.setVisibility(View.GONE);
            InputMethodManager inputManager = (InputMethodManager) getContext()
                    .getSystemService(Context.INPUT_METHOD_SERVICE);
            inputManager.hideSoftInputFromWindow(login.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);

            Completable.fromAction(() -> {
                service.setCredentials(usernameStr, passwordStr);
                service.login();
                devices = service.getDevices();
                GpodderAuthFragment.this.username = usernameStr;
                GpodderAuthFragment.this.password = passwordStr;
            })
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(() -> {
                        login.setEnabled(true);
                        progressBar.setVisibility(View.GONE);
                        advance();
                    }, error -> {
                            login.setEnabled(true);
                            progressBar.setVisibility(View.GONE);
                            txtvError.setText(error.getCause().getMessage());
                            txtvError.setVisibility(View.VISIBLE);
                        });

        });
    }

    private void setupDeviceView(View view) {
        final EditText deviceName = view.findViewById(R.id.deviceName);
        final LinearLayout devicesContainer = view.findViewById(R.id.devicesContainer);
        deviceName.setText(generateDeviceName());

        MaterialButton createDeviceButton = view.findViewById(R.id.createDeviceButton);
        createDeviceButton.setOnClickListener(v -> createDevice(view));

        for (GpodnetDevice device : devices) {
            View row = View.inflate(getContext(), R.layout.gpodnetauth_device_row, null);
            Button selectDeviceButton = row.findViewById(R.id.selectDeviceButton);
            selectDeviceButton.setOnClickListener(v -> {
                selectedDevice = device;
                advance();
            });
            selectDeviceButton.setText(device.getCaption());
            devicesContainer.addView(row);
        }
    }

    private void createDevice(View view) {
        final EditText deviceName = view.findViewById(R.id.deviceName);
        final TextView txtvError = view.findViewById(R.id.deviceSelectError);
        final ProgressBar progBarCreateDevice = view.findViewById(R.id.progbarCreateDevice);

        String deviceNameStr = deviceName.getText().toString();
        if (isDeviceInList(deviceNameStr)) {
            return;
        }
        progBarCreateDevice.setVisibility(View.VISIBLE);
        txtvError.setVisibility(View.GONE);
        deviceName.setEnabled(false);

        Observable.fromCallable(() -> {
            String deviceId = generateDeviceId(deviceNameStr);
            service.configureDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE);
            return new GpodnetDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE.toString(), 0);
        })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(device -> {
                    progBarCreateDevice.setVisibility(View.GONE);
                    selectedDevice = device;
                    advance();
                }, error -> {
                        deviceName.setEnabled(true);
                        progBarCreateDevice.setVisibility(View.GONE);
                        txtvError.setText(error.getMessage());
                        txtvError.setVisibility(View.VISIBLE);
                    });
    }

    private String generateDeviceName() {
        String baseName = getString(R.string.gpodnetauth_device_name_default, Build.MODEL);
        String name = baseName;
        int num = 1;
        while (isDeviceInList(name)) {
            name = baseName + " (" + num + ")";
            num++;
        }
        return name;
    }

    private String generateDeviceId(String name) {
        // devices names must be of a certain form:
        // https://gpoddernet.readthedocs.org/en/latest/api/reference/general.html#devices
        return FileNameGenerator.generateFileName(name).replaceAll("\\W", "_").toLowerCase(Locale.US);
    }

    private boolean isDeviceInList(String name) {
        if (devices == null) {
            return false;
        }
        String id = generateDeviceId(name);
        for (GpodnetDevice device : devices) {
            if (device.getId().equals(id) || device.getCaption().equals(name)) {
                return true;
            }
        }
        return false;
    }

    private void setupFinishView(View view) {
        final Button sync = view.findViewById(R.id.butSyncNow);

        sync.setOnClickListener(v -> {
            dismiss();
            SyncService.sync(getContext());
        });
    }

    private void advance() {
        if (currentStep < STEP_FINISH) {
            View view = viewFlipper.getChildAt(currentStep + 1);
            if (currentStep == STEP_DEFAULT) {
                setupHostView(view);
            } else if (currentStep == STEP_HOSTNAME) {
                setupLoginView(view);
            } else if (currentStep == STEP_LOGIN) {
                if (username == null || password == null) {
                    throw new IllegalStateException("Username and password must not be null here");
                } else {
                    setupDeviceView(view);
                }
            } else if (currentStep == STEP_DEVICE) {
                if (selectedDevice == null) {
                    throw new IllegalStateException("Device must not be null here");
                } else {
                    SynchronizationSettings.setSelectedSyncProvider(SynchronizationProviderViewData.GPODDER_NET);
                    SynchronizationCredentials.setUsername(username);
                    SynchronizationCredentials.setPassword(password);
                    SynchronizationCredentials.setDeviceID(selectedDevice.getId());
                    setupFinishView(view);
                }
            }
            if (currentStep != STEP_DEFAULT) {
                viewFlipper.showNext();
            }
            currentStep++;
        } else {
            dismiss();
        }
    }

    private boolean usernameHasUnwantedChars(String username) {
        Pattern special = Pattern.compile("[!@#$%&*()+=|<>?{}\\[\\]~]");
        Matcher containsUnwantedChars = special.matcher(username);
        return containsUnwantedChars.find();
    }
}
