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

import android.app.Dialog;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Patterns;
import android.view.View;
import android.widget.ArrayAdapter;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

import de.danoeh.antennapod.R;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.net.common.AntennapodHttpClient;
import de.danoeh.antennapod.model.download.ProxyConfig;
import de.danoeh.antennapod.ui.common.ThemeUtils;
import de.danoeh.antennapod.ui.preferences.databinding.ProxySettingsBinding;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import okhttp3.Credentials;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class ProxyDialog {
    private final Context context;
    private AlertDialog dialog;
    private boolean testSuccessful = false;
    private Disposable disposable;
    private ProxySettingsBinding viewBinding;

    public ProxyDialog(Context context) {
        this.context = context;
    }

    public Dialog show() {
        viewBinding = ProxySettingsBinding.bind(View.inflate(context, R.layout.proxy_settings, null));

        dialog = new MaterialAlertDialogBuilder(context)
                .setTitle(R.string.pref_proxy_title)
                .setView(viewBinding.getRoot())
                .setNegativeButton(R.string.cancel_label, null)
                .setPositiveButton(R.string.proxy_test_label, null)
                .setNeutralButton(R.string.reset, null)
                .show();
        // To prevent cancelling the dialog on button click
        dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener((view) -> {
            if (!testSuccessful) {
                dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
                test();
                return;
            }
            setProxyConfig();
            AntennapodHttpClient.reinit();
            dialog.dismiss();
        });

        dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener((view) -> {
            viewBinding.hostText.getText().clear();
            viewBinding.portText.getText().clear();
            viewBinding.usernameText.getText().clear();
            viewBinding.passwordText.getText().clear();
            setProxyConfig();
        });

        ProxyConfig proxyConfig = UserPreferences.getProxyConfig();

        viewBinding.hostText.setText(proxyConfig.host);
        viewBinding.hostText.addTextChangedListener(requireTestOnChange);
        viewBinding.portText.setText(proxyConfig.port > 0 ? String.valueOf(proxyConfig.port) : "");
        viewBinding.portText.addTextChangedListener(requireTestOnChange);
        viewBinding.usernameText.setText(proxyConfig.username);
        viewBinding.usernameText.addTextChangedListener(requireTestOnChange);
        viewBinding.passwordText.setText(proxyConfig.password);
        viewBinding.passwordText.addTextChangedListener(requireTestOnChange);
        if (proxyConfig.type == Proxy.Type.DIRECT) {
            enableSettings(false);
            setTestRequired(false);
        }

        List<String> types = new ArrayList<>();
        types.add(Proxy.Type.DIRECT.name());
        types.add(Proxy.Type.HTTP.name());
        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            types.add(Proxy.Type.SOCKS.name());
        }

        ArrayAdapter<String> adapter = new ArrayAdapter<>(context,
                android.R.layout.simple_list_item_1, types);
        viewBinding.proxyTypeSpinner.setAdapter(adapter);
        viewBinding.proxyTypeSpinner.setThreshold(999);
        viewBinding.proxyTypeSpinner.setText(proxyConfig.type.name());
        viewBinding.proxyTypeSpinner.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }

            @Override
            public void afterTextChanged(Editable s) {
                boolean isDirect = Proxy.Type.DIRECT.name().equals(viewBinding.proxyTypeSpinner.getText().toString());
                if (isDirect) {
                    dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setVisibility(View.GONE);
                } else {
                    dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setVisibility(View.VISIBLE);
                }
                enableSettings(!isDirect);
                setTestRequired(!isDirect);
            }
        });
        checkValidity();
        return dialog;
    }

    private void setProxyConfig() {
        final String type = viewBinding.proxyTypeSpinner.getText().toString();
        final Proxy.Type typeEnum = Proxy.Type.valueOf(type);
        final String host = viewBinding.hostText.getText().toString();
        final String port = viewBinding.portText.getText().toString();

        String username = viewBinding.usernameText.getText().toString();
        if (TextUtils.isEmpty(username)) {
            username = null;
        }
        String password = viewBinding.passwordText.getText().toString();
        if (TextUtils.isEmpty(password)) {
            password = null;
        }
        int portValue = 0;
        if (!TextUtils.isEmpty(port)) {
            portValue = Integer.parseInt(port);
        }
        ProxyConfig config = new ProxyConfig(typeEnum, host, portValue, username, password);
        UserPreferences.setProxyConfig(config);
        AntennapodHttpClient.setProxyConfig(config);
    }

    private final TextWatcher requireTestOnChange = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
        }

        @Override
        public void afterTextChanged(Editable s) {
            setTestRequired(true);
        }
    };

    private void enableSettings(boolean enable) {
        viewBinding.hostText.setEnabled(enable);
        viewBinding.portText.setEnabled(enable);
        viewBinding.usernameText.setEnabled(enable);
        viewBinding.passwordText.setEnabled(enable);
    }

    private boolean checkValidity() {
        boolean valid = true;
        if (!Proxy.Type.DIRECT.name().equals(viewBinding.proxyTypeSpinner.getText().toString())) {
            valid = checkHost();
        }
        valid &= checkPort();
        return valid;
    }

    private boolean checkHost() {
        String host = viewBinding.hostText.getText().toString();
        if (host.isEmpty()) {
            viewBinding.hostText.setError(context.getString(R.string.proxy_host_empty_error));
            return false;
        }
        if (!"localhost".equals(host) && !Patterns.DOMAIN_NAME.matcher(host).matches()) {
            viewBinding.hostText.setError(context.getString(R.string.proxy_host_invalid_error));
            return false;
        }
        return true;
    }

    private boolean checkPort() {
        int port = getPort();
        if (port < 0 || port > 65535) {
            viewBinding.portText.setError(context.getString(R.string.proxy_port_invalid_error));
            return false;
        }
        return true;
    }

    private int getPort() {
        String port = viewBinding.portText.getText().toString();
        if (!port.isEmpty()) {
            try {
                return Integer.parseInt(port);
            } catch (NumberFormatException e) {
                // ignore
            }
        }
        return 0;
    }

    private void setTestRequired(boolean required) {
        if (required) {
            testSuccessful = false;
            dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.proxy_test_label);
        } else {
            testSuccessful = true;
            dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(android.R.string.ok);
        }
        dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
    }

    private void test() {
        if (disposable != null) {
            disposable.dispose();
        }
        if (!checkValidity()) {
            setTestRequired(true);
            return;
        }
        TypedArray res = context.getTheme().obtainStyledAttributes(new int[] { android.R.attr.textColorPrimary });
        int textColorPrimary = res.getColor(0, 0);
        res.recycle();
        viewBinding.infoLabel.setTextColor(textColorPrimary);
        viewBinding.infoLabel.setText(R.string.proxy_checking);
        viewBinding.infoLabel.setVisibility(View.VISIBLE);
        disposable = Completable.create(emitter -> {
            String type = viewBinding.proxyTypeSpinner.getText().toString();
            String host = viewBinding.hostText.getText().toString();
            String port = viewBinding.portText.getText().toString();
            String username = viewBinding.usernameText.getText().toString();
            String password = viewBinding.passwordText.getText().toString();
            int portValue = 8080;
            if (!TextUtils.isEmpty(port)) {
                portValue = Integer.parseInt(port);
            }
            SocketAddress address = InetSocketAddress.createUnresolved(host, portValue);
            Proxy.Type proxyType = Proxy.Type.valueOf(type.toUpperCase(Locale.US));
            OkHttpClient.Builder builder = AntennapodHttpClient.newBuilder()
                    .connectTimeout(10, TimeUnit.SECONDS)
                    .proxy(new Proxy(proxyType, address));
            if (!TextUtils.isEmpty(username)) {
                builder.proxyAuthenticator((route, response) -> {
                    String credentials = Credentials.basic(username, password);
                    return response.request().newBuilder()
                            .header("Proxy-Authorization", credentials)
                            .build();
                });
            }
            OkHttpClient client = builder.build();
            Request request = new Request.Builder().url("https://www.example.com").head().build();
            try (Response response = client.newCall(request).execute()) {
                if (response.isSuccessful()) {
                    emitter.onComplete();
                } else {
                    emitter.onError(new IOException(response.message()));
                }
            } catch (IOException e) {
                emitter.onError(e);
            }
        })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                        () -> {
                            viewBinding.infoLabel.setTextColor(
                                    ThemeUtils.getColorFromAttr(context, R.attr.icon_green));
                            viewBinding.infoLabel.setText(R.string.proxy_test_successful);
                            setTestRequired(false);
                        },
                        error -> {
                            error.printStackTrace();
                            viewBinding.infoLabel.setTextColor(
                                    ThemeUtils.getColorFromAttr(context, R.attr.icon_red));
                            String message = String.format("%s: %s",
                                    context.getString(R.string.proxy_test_failed), error.getMessage());
                            viewBinding.infoLabel.setText(message);
                            setTestRequired(true);
                        }
                );
    }

}
