package co.nano.nanowallet.ui.send;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.databinding.BindingAdapter;
import android.databinding.DataBindingUtil;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.constraint.ConstraintLayout;
import android.support.constraint.Guideline;
import android.support.v4.content.ContextCompat;
import android.text.InputType;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.github.ajalt.reprint.core.AuthenticationFailureReason;
import com.github.ajalt.reprint.core.Reprint;
import com.hwangjr.rxbus.annotation.Subscribe;

import java.math.BigInteger;
import java.text.NumberFormat;
import java.util.HashMap;

import javax.inject.Inject;

import co.nano.nanowallet.NanoUtil;
import co.nano.nanowallet.R;
import co.nano.nanowallet.analytics.AnalyticsEvents;
import co.nano.nanowallet.analytics.AnalyticsService;
import co.nano.nanowallet.bus.CreatePin;
import co.nano.nanowallet.bus.HideOverlay;
import co.nano.nanowallet.bus.PinComplete;
import co.nano.nanowallet.bus.RxBus;
import co.nano.nanowallet.bus.SendInvalidAmount;
import co.nano.nanowallet.bus.ShowOverlay;
import co.nano.nanowallet.databinding.FragmentSendBinding;
import co.nano.nanowallet.model.Address;
import co.nano.nanowallet.model.AvailableCurrency;
import co.nano.nanowallet.model.Credentials;
import co.nano.nanowallet.model.NanoWallet;
import co.nano.nanowallet.network.AccountService;
import co.nano.nanowallet.network.model.response.ErrorResponse;
import co.nano.nanowallet.network.model.response.ProcessResponse;
import co.nano.nanowallet.ui.common.ActivityWithComponent;
import co.nano.nanowallet.ui.common.BaseFragment;
import co.nano.nanowallet.ui.common.KeyboardUtil;
import co.nano.nanowallet.ui.common.UIUtil;
import co.nano.nanowallet.ui.scan.ScanActivity;
import co.nano.nanowallet.util.NumberUtil;
import co.nano.nanowallet.util.SharedPreferencesUtil;
import io.realm.Realm;

import static android.app.Activity.RESULT_OK;

/**
 * Send Screen
 */
public class SendFragment extends BaseFragment {
    private FragmentSendBinding binding;
    public static String TAG = SendFragment.class.getSimpleName();
    private boolean localCurrencyActive = false;
    private AlertDialog fingerprintDialog;
    private static final String ARG_NEW_SEED = "argNewSeed";
    private String newSeed;

    @Inject
    NanoWallet wallet;

    @Inject
    AccountService accountService;

    @Inject
    SharedPreferencesUtil sharedPreferencesUtil;

    @Inject
    Realm realm;

    @Inject
    AnalyticsService analyticsService;

    @BindingAdapter("layout_constraintGuide_percent")
    public static void setLayoutConstraintGuidePercent(Guideline guideline, float percent) {
        ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) guideline.getLayoutParams();
        params.guidePercent = percent;
        guideline.setLayoutParams(params);
    }

    /**
     * Create new instance of the fragment (handy pattern if any data needs to be passed to it)
     *
     * @return New instance of SendFragment
     */
    public static SendFragment newInstance() {
        Bundle args = new Bundle();
        SendFragment fragment = new SendFragment();
        fragment.setArguments(args);
        return fragment;
    }

    /**
     * Create new instance of the fragment (handy pattern if any data needs to be passed to it)
     *
     * @return New instance of SendFragment
     */
    public static SendFragment newInstance(String newSeed) {
        Bundle args = new Bundle();
        args.putString(ARG_NEW_SEED, newSeed);
        SendFragment fragment = new SendFragment();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);

        if (getArguments() != null) {
            newSeed = getArguments().getString(ARG_NEW_SEED);
        }
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.menu_send, menu);
        super.onCreateOptionsMenu(menu, inflater);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        switch (id) {
            case R.id.send_camera:
                analyticsService.track(AnalyticsEvents.ADDRESS_SCAN_CAMERA_VIEWED);
                startScanActivity(getString(R.string.scan_send_instruction_label), false);
                return true;
        }

        return false;
    }

    public void showSoftKeyboard() {
        //Shows the SoftKeyboard
        InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE);
        if (inputMethodManager != null && getActivity().getCurrentFocus() != null) {
            inputMethodManager.showSoftInput(binding.sendAddress, InputMethodManager.SHOW_IMPLICIT);
        }
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        // init dependency injection
        if (getActivity() instanceof ActivityWithComponent) {
            ((ActivityWithComponent) getActivity()).getActivityComponent().inject(this);
        }

        analyticsService.track(AnalyticsEvents.SEND_VIEWED);

        // subscribe to bus
        RxBus.get().register(this);

        // change keyboard mode
        getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
        KeyboardUtil.hideKeyboard(getActivity());

        // inflate the view
        binding = DataBindingUtil.inflate(
                inflater, R.layout.fragment_send, container, false);
        view = binding.getRoot();
        binding.setHandlers(new ClickHandlers());

        setStatusBarBlue();
        setBackEnabled(true);
        setTitle(getString(R.string.send_title));
        setTitleDrawable(R.drawable.ic_send);

        // hide keyboard for edittext fields
        binding.sendAmountNano.setInputType(InputType.TYPE_NULL);
        binding.sendAmountLocalcurrency.setInputType(InputType.TYPE_NULL);

        // set active and inactive states for edittext fields
        binding.sendAmountNano.setOnFocusChangeListener((view1, b) -> toggleFieldFocus((EditText) view1, b, false));
        binding.sendAmountLocalcurrency.setOnFocusChangeListener((view1, b) -> toggleFieldFocus((EditText) view1, b, true));
        binding.sendAmountLocalcurrency.setHint(NumberFormat.getCurrencyInstance(getLocalCurrency().getLocale()).format(0));
        binding.setShowAmount(true);

        binding.sendAddress.setOnFocusChangeListener((view12, hasFocus) -> binding.setShowAmount(!hasFocus));

        binding.sendAddress.setBackgroundResource(binding.sendAddress.getText().length() > 0 ? R.drawable.bg_seed_input_active : R.drawable.bg_seed_input);
        UIUtil.colorizeSpannable(binding.sendAddress.getText(), getContext());


        // updates to handle seed conversion 1.0.2
        if (newSeed != null) {
            String address = NanoUtil.publicToAddress(NanoUtil.privateToPublic(NanoUtil.seedToPrivate(newSeed)));
            binding.sendAddress.setText(address);
            setShortAddress();
        }

        return view;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        // unregister from bus
        RxBus.get().unregister(this);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        // Check to make sure we are responding to camera result
        if (requestCode == SCAN_RESULT) {
            // Make sure the request was successful
            if (resultCode == RESULT_OK) {
                Bundle res = data.getExtras();
                if (res != null) {
                    // parse address
                    Address address = new Address(res.getString(ScanActivity.QR_CODE_RESULT));

                    // set to scanned value
                    if (address.getAddress() != null) {
                        binding.sendAddress.setText(address.getAddress());
                    }

                    if (address.getAmount() != null) {
                        wallet.setSendRawAmount(address.getAmount());
                        binding.setWallet(wallet);
                    }

                    setShortAddress();
                }
            }
        }
    }


    public AvailableCurrency getLocalCurrency() {
        return sharedPreferencesUtil.getLocalCurrency();
    }

    /**
     * Event that occurs if an amount entered is invalid
     *
     * @param sendInvalidAmount Send Invalid Amount event
     */
    @Subscribe
    public void receiveInvalidAmount(SendInvalidAmount sendInvalidAmount) {
        // reset amount to max in wallet
        wallet.setSendNanoAmount(wallet.getLongerAccountBalanceNano());
        binding.setWallet(wallet);

        // show alert with a message to the user letting them know the amount they entered
        showError(R.string.send_amount_too_large_alert_title, R.string.send_amount_too_large_alert_message);
    }

    /**
     * Catch errors from the service
     *
     * @param errorResponse Error Resposne event
     */
    @Subscribe
    public void receiveServiceError(ErrorResponse errorResponse) {
        RxBus.get().post(new HideOverlay());
        // show alert with a message to the user letting them know the amount they entered
        AlertDialog.Builder builder;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            builder = new AlertDialog.Builder(getContext(), android.R.style.Theme_Material_Light_Dialog_Alert);
        } else {
            builder = new AlertDialog.Builder(getContext());
        }
        builder.setMessage(R.string.send_error_alert_message)
                .setPositiveButton(R.string.send_amount_too_large_alert_cta, (dialog, which) -> {
                    goBack();
                })
                .show();
    }

    /**
     * Received a successful send response so go back
     *
     * @param processResponse Process Response
     */
    @Subscribe
    public void receiveProcessResponse(ProcessResponse processResponse) {
        RxBus.get().post(new HideOverlay());
        accountService.requestUpdate();
        analyticsService.track(AnalyticsEvents.SEND_FINISHED);

        // updates to handle seed conversion 1.0.2
        if (newSeed != null) {
            realm.beginTransaction();
            Credentials credentials = realm.where(Credentials.class).findFirst();
            if (credentials != null) {
                credentials.setHasSentToNewSeed(true);
                credentials.setNewlyGeneratedSeed(newSeed);
            }
            realm.commitTransaction();
            AlertDialog.Builder builder;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                builder = new AlertDialog.Builder(getContext(), android.R.style.Theme_Material_Light_Dialog_Alert);
            } else {
                builder = new AlertDialog.Builder(getContext());
            }

            builder.setTitle(R.string.seed_update_send_completed_alert_title)
                    .setMessage(R.string.seed_update_send_completed_alert_message)
                    .setPositiveButton(R.string.seed_update_send_completed_alert_confirm, (dialog, which) -> goBack())
                    .show();
        } else {
            goBack();
        }
    }

    /**
     * Pin entered correctly
     * @param pinComplete PinComplete object
     */
    @Subscribe
    public void receivePinComplete(PinComplete pinComplete) {
        executeSend();
    }

    @Subscribe
    public void receiveCreatePin(CreatePin pinComplete) {
        realm.beginTransaction();
        Credentials credentials = realm.where(Credentials.class).findFirst();
        if (credentials != null) {
            credentials.setPin(pinComplete.getPin());
        }
        realm.commitTransaction();
        executeSend();
    }

    private boolean validateRequest() {
        // check for valid address
        Address destination = new Address(binding.sendAddress.getText().toString());
        if (!destination.isValidAddress()) {
            showError(R.string.send_error_alert_title, R.string.send_error_alert_message);
            return false;
        }

        // check that amount being sent is less than or equal to account balance
        if (wallet.getSendNanoAmount().isEmpty()) {
            return false;
        }
        BigInteger balance;
        if (wallet.getSendRawAmount() != null) {
            balance = new BigInteger(wallet.getSendRawAmount());
        } else {
            balance = NumberUtil.getAmountAsRawBigInteger(wallet.getSendNanoAmount());
        }
        if (balance.compareTo(wallet.getAccountBalanceNanoRaw().toBigInteger()) > 0) {
            showError(R.string.send_error_alert_title, R.string.send_error_alert_message);
            return false;
        }

        // check that we have a frontier block
        if (wallet.getFrontierBlock() == null) {
            showError(R.string.send_error_alert_title, R.string.send_error_alert_message);
            return false;
        }

        return true;
    }

    private void enableSendIfPossible() {
        boolean enableSend = true;

        // check for valid address
        Address destination = new Address(binding.sendAddress.getText().toString());
        if (!destination.isValidAddress()) {
            enableSend = false;
        }

        // check that amount being sent is less than or equal to account balance
        if (wallet.getSendNanoAmount().isEmpty()) {
            enableSend = false;
        }
        BigInteger balance = NumberUtil.getAmountAsRawBigInteger(wallet.getSendNanoAmount());
        if (balance.compareTo(new BigInteger("0")) <= 0 || balance.compareTo(wallet.getAccountBalanceNanoRaw().toBigInteger()) > 0) {
            enableSend = false;
        }


        // check that we have a frontier block
        if (wallet.getFrontierBlock() == null) {
            enableSend = false;
        }

        binding.sendSendButton.setEnabled(enableSend);
    }

    private void showError(int title, int message) {
        AlertDialog.Builder builder;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            builder = new AlertDialog.Builder(getContext(), android.R.style.Theme_Material_Light_Dialog_Alert);
        } else {
            builder = new AlertDialog.Builder(getContext());
        }
        builder.setTitle(title)
                .setMessage(message)
                .setPositiveButton(R.string.send_amount_too_large_alert_cta, (dialog, which) -> {
                })
                .show();
    }

    private void showError(int title, String message) {
        AlertDialog.Builder builder;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            builder = new AlertDialog.Builder(getContext(), android.R.style.Theme_Material_Light_Dialog_Alert);
        } else {
            builder = new AlertDialog.Builder(getContext());
        }
        builder.setTitle(title)
                .setMessage(message)
                .setPositiveButton(R.string.send_amount_too_large_alert_cta, (dialog, which) -> {
                })
                .show();
    }

    /**
     * Helper to set focus size and color on fields
     *
     * @param v               EditText view
     * @param hasFocus        Does view have focus currently?
     * @param isLocalCurrency Is this view the local currency view?
     */
    private void toggleFieldFocus(EditText v, boolean hasFocus, boolean isLocalCurrency) {
        localCurrencyActive = isLocalCurrency;

        v.setTextSize(TypedValue.COMPLEX_UNIT_SP, hasFocus ? 20f : 16f);
        binding.sendAmountNanoSymbol.setAlpha(hasFocus && !isLocalCurrency ? 1.0f : 0.5f);

        // clear amounts
        wallet.clearSendAmounts();
        binding.setWallet(wallet);

        // set local currency decimal separator if local currency is active, otherwise . for nano
        binding.sendKeyboardDecimal.setText(localCurrencyActive ? wallet.getDecimalSeparator() : ".");
    }

    /**
     * Update amount strings based on input processed
     *
     * @param value String value of character pressed
     */
    private void updateAmount(CharSequence value) {
        if (value.equals(getString(R.string.send_keyboard_delete))) {
            // delete last character
            if (localCurrencyActive) {
                if (wallet.getLocalCurrencyAmount().length() > 0) {
                    wallet.setLocalCurrencyAmount(wallet.getLocalCurrencyAmount().substring(0, wallet.getLocalCurrencyAmount().length() - 1));
                }
            } else {
                if (wallet.getSendNanoAmount().length() > 0) {
                    wallet.setSendNanoAmount(wallet.getSendNanoAmount().substring(0, wallet.getSendNanoAmount().length() - 1));
                }
            }
        } else if ((!localCurrencyActive && value.equals(getString(R.string.send_keyboard_decimal))) || (localCurrencyActive && value.equals(wallet.getDecimalSeparator()))) {
            // decimal point
            if (localCurrencyActive) {
                if (!wallet.getLocalCurrencyAmount().contains(value)) {
                    wallet.setLocalCurrencyAmount(wallet.getLocalCurrencyAmount() + value);
                }
            } else {
                if (!wallet.getSendNanoAmount().contains(value)) {
                    wallet.setSendNanoAmount(wallet.getSendNanoAmount() + value);
                }
            }
        } else {
            // digits
            if (localCurrencyActive) {
                wallet.setLocalCurrencyAmount(wallet.getLocalCurrencyAmount() + value);
            } else {
                wallet.setSendNanoAmount(wallet.getSendNanoAmount() + value);
            }
        }
        binding.setWallet(wallet);
        enableSendIfPossible();
    }

    private void setShortAddress() {
        // set short address if appropriate
        Address address = new Address(binding.sendAddress.getText().toString());
        if (address.isValidAddress()) {
            binding.sendAddressDisplay.setText(address.getColorizedShortSpannable());
            binding.sendAddressDisplay.setBackgroundResource(binding.sendAddressDisplay.length() > 0 ? R.drawable.bg_seed_input_active : R.drawable.bg_seed_input);
        } else {
            binding.sendAddressDisplay.setText("");
            binding.sendAddressDisplay.setBackgroundResource(binding.sendAddressDisplay.length() > 0 ? R.drawable.bg_seed_input_active : R.drawable.bg_seed_input);
        }
        enableSendIfPossible();
    }

    private void executeSend() {
        Address destination = new Address(binding.sendAddress.getText().toString());
        if (destination.isValidAddress()) {
            RxBus.get().post(new ShowOverlay());
            BigInteger sendAmount;
            if (wallet.getSendRawAmount() != null) {
                sendAmount = new BigInteger(wallet.getSendRawAmount());
            } else {
                sendAmount = NumberUtil.getAmountAsRawBigInteger(wallet.getSendNanoAmount());
            }

            accountService.requestSend(wallet.getFrontierBlock(), destination, sendAmount);
            analyticsService.track(AnalyticsEvents.SEND_BEGAN);
        } else {
            showError(R.string.send_error_alert_title, R.string.send_error_alert_message);
        }
    }



    public class ClickHandlers {
        /**
         * Listener for styling updates when text changes
         *
         * @param s      Character sequence
         * @param start  Starting character
         * @param before Character that came before
         * @param count  Total character count
         */
        public void onAddressTextChanged(CharSequence s, int start, int before, int count) {
            // set background to active or not
            binding.sendAddress.setBackgroundResource(s.length() > 0 ? R.drawable.bg_seed_input_active : R.drawable.bg_seed_input);

            // colorize input string
            UIUtil.colorizeSpannable(binding.sendAddress.getText(), getContext());
        }

        public void onAddressDisplayClicked(View view) {
            binding.setShowAmount(false);
            binding.sendAddress.setSelection(binding.sendAddress.getText().length());
            showSoftKeyboard();
        }

        public void onClickNanoContainer(View view) {
            binding.sendAmountNano.requestFocus();
        }

        public void onClickConfirm(View view) {
            binding.setShowAmount(true);
            setShortAddress();
            KeyboardUtil.hideKeyboard(getActivity());
            binding.sendAmountNano.requestFocus();
        }

        public void onClickSend(View view) {
            if (!validateRequest()) {
                return;
            }

            Credentials credentials = realm.where(Credentials.class).findFirst();

            if (Reprint.isHardwarePresent() && Reprint.hasFingerprintRegistered()) {
                // show fingerprint dialog
                LayoutInflater factory = LayoutInflater.from(getContext());
                @SuppressLint("InflateParams") final View viewFingerprint = factory.inflate(R.layout.view_fingerprint, null);
                showFingerprintDialog(viewFingerprint);
                com.github.ajalt.reprint.rxjava2.RxReprint.authenticate()
                        .subscribe(result -> {
                            switch (result.status) {
                                case SUCCESS:
                                    showFingerprintSuccess(viewFingerprint);
                                    break;
                                case NONFATAL_FAILURE:
                                    showFingerprintError(result.failureReason, result.errorMessage, viewFingerprint);
                                    break;
                                case FATAL_FAILURE:
                                    showFingerprintError(result.failureReason, result.errorMessage, viewFingerprint);
                                    break;
                            }
                        });
            } else if (credentials != null && credentials.getPin() != null) {
                showPinScreen(getString(R.string.send_pin_description, wallet.getSendNanoAmount()));
            } else if (credentials != null && credentials.getPin() == null) {
                showCreatePinScreen();
            }
        }

        public void onClickMax(View view) {
            analyticsService.track(AnalyticsEvents.SEND_MAX_AMOUNT_USED);
            wallet.setSendNanoAmount(wallet.getLongerAccountBalanceNano());
            binding.setWallet(wallet);
            enableSendIfPossible();
        }

        public void onClickNumKeyboard(View view) {
            updateAmount(((Button) view).getText());
        }

    }

    private void showFingerprintDialog(View view) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
        builder.setTitle(getString(R.string.send_fingerprint_title));
        builder.setMessage(getString(R.string.send_fingerprint_description,
                !wallet.getSendNanoAmountFormatted().isEmpty() ? wallet.getSendNanoAmountFormatted() : "0"));
        builder.setView(view);
        String negativeText = getString(android.R.string.cancel);
        builder.setNegativeButton(negativeText, (dialog, which) -> Reprint.cancelAuthentication());

        fingerprintDialog = builder.create();
        fingerprintDialog.setCanceledOnTouchOutside(false);
        // display dialog
        fingerprintDialog.show();
    }

    private void showFingerprintSuccess(View view) {
        if (isAdded()) {
            TextView textView = view.findViewById(R.id.fingerprint_textview);
            textView.setText(getString(R.string.send_fingerprint_success));
            if (getContext() != null) {
                textView.setTextColor(ContextCompat.getColor(getContext(), R.color.dark_sky_blue));
            }
            textView.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_fingerprint_success, 0, 0, 0);
            executeSend();

            // close dialog after 1 second
            final Handler handler = new Handler();
            final Runnable runnable = () -> {
                if (fingerprintDialog != null && fingerprintDialog.isShowing()) {
                    fingerprintDialog.dismiss();
                }
            };
            handler.postDelayed(runnable, 500);
        }
    }

    private void showFingerprintError(AuthenticationFailureReason reason, CharSequence message, View view) {
        if (isAdded()) {
            final HashMap<String, String> customData = new HashMap<>();
            customData.put("description", reason.name());
            analyticsService.track(AnalyticsEvents.SEND_AUTH_ERROR, customData);
            TextView textView = view.findViewById(R.id.fingerprint_textview);
            textView.setText(message.toString());
            if (getContext() != null) {
                textView.setTextColor(ContextCompat.getColor(getContext(), R.color.error));
            }
            textView.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_fingerprint_error, 0, 0, 0);
        }
    }
}