package de.danoeh.antennapod.ui.preferences.screen.synchronization;

import android.app.Dialog;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.ViewFlipper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import androidx.fragment.app.DialogFragment;
import com.google.android.material.button.MaterialButton;
import de.danoeh.antennapod.net.common.AntennapodHttpClient;
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationProvider;
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueue;
import de.danoeh.antennapod.storage.preferences.SynchronizationCredentials;
import de.danoeh.antennapod.storage.preferences.SynchronizationSettings;
import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService;
import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetDevice;
import de.danoeh.antennapod.ui.common.Keyboard;
import de.danoeh.antennapod.ui.preferences.R;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.schedulers.Schedulers;

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

/**
 * Guides the user through the authentication process.
 */
public class GpodderAuthenticationFragment 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) {
        MaterialAlertDialogBuilder dialog = new MaterialAlertDialogBuilder(getContext());
        dialog.setTitle(R.string.gpodnetauth_login_butLabel);
        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 EditText serverUrlText = view.findViewById(R.id.serverUrlText);
        selectHost.setOnClickListener(v -> {
            if (serverUrlText.getText().length() == 0) {
                return;
            }
            SynchronizationCredentials.clear();
            SynchronizationQueue.getInstance().clear();
            SynchronizationCredentials.setHosturl(serverUrlText.getText().toString());
            service = new GpodnetService(AntennapodHttpClient.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 createAccountWarning = view.findViewById(R.id.createAccountWarning);

        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);
            Keyboard.hide(getActivity());

            Completable.fromAction(() -> {
                service.setCredentials(usernameStr, passwordStr);
                service.login();
                devices = service.getDevices();
                GpodderAuthenticationFragment.this.username = usernameStr;
                GpodderAuthenticationFragment.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 name.replaceAll("[^a-zA-Z0-9]", "_").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();
            SynchronizationQueue.getInstance().syncImmediately();
        });
    }

    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(
                            SynchronizationProvider.GPODDER_NET.getIdentifier());
                    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();
    }
}
