package com.braintreepayments.browserswitch;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;

import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;

@SuppressWarnings("WeakerAccess")
public class BrowserSwitchClient {

    final private BrowserSwitchConfig config;
    final private ActivityFinder activityFinder;
    final private BrowserSwitchPersistentStore persistentStore;
    final private String returnUrlScheme;

    public static BrowserSwitchClient newInstance(String returnUrlScheme) {
        return new BrowserSwitchClient(
            BrowserSwitchConfig.newInstance(), ActivityFinder.newInstance(),
            BrowserSwitchPersistentStore.getInstance(), returnUrlScheme);
    }

    @VisibleForTesting
    static BrowserSwitchClient newInstance(
        BrowserSwitchConfig config, ActivityFinder activityFinder,
        BrowserSwitchPersistentStore persistentStore, String returnUrlScheme) {
        return new BrowserSwitchClient(config, activityFinder, persistentStore, returnUrlScheme);
    }

    private BrowserSwitchClient(
            BrowserSwitchConfig config, ActivityFinder activityFinder,
            BrowserSwitchPersistentStore persistentStore, String returnUrlScheme) {
        this.config = config;
        this.activityFinder = activityFinder;
        this.persistentStore = persistentStore;
        this.returnUrlScheme = returnUrlScheme;
    }

    /**
     * Open a browser or <a href="https://developer.chrome.com/multidevice/android/customtabs">Chrome Custom Tab</a>
     * with the given url from an Android fragment. The fragment must be attached to activity when invoking this method.
     *
     * @param requestCode the request code used to differentiate requests from one another.
     * @param uri the url to open.
     * @param fragment the fragment used to start browser switch
     */
    public void start(int requestCode, Uri uri, Fragment fragment) {
        if (fragment instanceof BrowserSwitchListener) {
            start(requestCode, uri, fragment, (BrowserSwitchListener) fragment);
        } else {
            throw new IllegalArgumentException("Fragment must implement BrowserSwitchListener.");
        }
    }

    /**
     * Open a browser or <a href="https://developer.chrome.com/multidevice/android/customtabs">Chrome Custom Tab</a>
     * with the given url from an Android fragment. The fragment must be attached to activity when invoking this method.
     *
     * @param requestCode the request code used to differentiate requests from one another.
     * @param uri the url to open.
     * @param fragment the fragment used to start browser switch
     * @param listener the listener that will receive browser switch callbacks
     */
    public void start(int requestCode, Uri uri, Fragment fragment, BrowserSwitchListener listener) {
        FragmentActivity activity = fragment.getActivity();
        if (activity != null) {
            start(requestCode, uri, activity, listener);
        } else {
            throw new IllegalStateException("Fragment must be attached to an activity.");
        }
    }

    /**
     * Open a browser or <a href="https://developer.chrome.com/multidevice/android/customtabs">Chrome Custom Tab</a>
     * with the given url from an Android activity.
     *
     * @param requestCode the request code used to differentiate requests from one another.
     * @param uri the url to open.
     * @param activity the activity used to start browser switch
     */
    @SuppressWarnings("SameParameterValue")
    public void start(int requestCode, Uri uri, FragmentActivity activity) {
        if (activity instanceof BrowserSwitchListener) {
            start(requestCode, uri, activity, (BrowserSwitchListener) activity);
        } else {
            throw new IllegalArgumentException("Activity must implement BrowserSwitchListener.");
        }
    }

    /**
     * Open a browser or <a href="https://developer.chrome.com/multidevice/android/customtabs">Chrome Custom Tab</a>
     * with the given url from an Android activity.
     *
     * @param requestCode the request code used to differentiate requests from one another.
     * @param uri the url to open.
     * @param activity the activity used to start browser switch
     * @param listener the listener that will receive browser switch callbacks
     */
    public void start(
        int requestCode, Uri uri, FragmentActivity activity, BrowserSwitchListener listener) {
        Context appContext = activity.getApplicationContext();
        Intent intent = config.createIntentToLaunchUriInBrowser(appContext, uri);
        start(requestCode, intent, activity, listener);
    }

    /**
     * Open a browser or <a href="https://developer.chrome.com/multidevice/android/customtabs">Chrome Custom Tab</a>
     * with the given intent from an Android fragment. The fragment must be attached to activity when invoking this method.
     *
     * @param requestCode the request code used to differentiate requests from one another.
     * @param intent the intent to use to initiate a browser switch
     * @param fragment the fragment used to start browser switch
     */
    public void start(int requestCode, Intent intent, Fragment fragment) {
        if (fragment instanceof BrowserSwitchListener) {
            start(requestCode, intent, fragment, (BrowserSwitchListener) fragment);
        } else {
            throw new IllegalArgumentException("Fragment must implement BrowserSwitchListener.");
        }
    }

    /**
     * Open a browser or <a href="https://developer.chrome.com/multidevice/android/customtabs">Chrome Custom Tab</a>
     * with the given intent from an Android fragment. The fragment must be attached to activity when invoking this method.
     *
     * @param requestCode the request code used to differentiate requests from one another.
     * @param intent the intent to use to initiate a browser switch
     * @param fragment the fragment used to start browser switch
     * @param listener the listener that will receive browser switch callbacks
     */
    public void start(
        int requestCode, Intent intent, Fragment fragment, BrowserSwitchListener listener) {
        FragmentActivity activity = fragment.getActivity();
        if (activity != null) {
            start(requestCode, intent, activity, listener);
        } else {
            throw new IllegalStateException("Fragment must be attached to an activity.");
        }
    }

    /**
     * Open a browser or <a href="https://developer.chrome.com/multidevice/android/customtabs">Chrome Custom Tab</a>
     * with the given intent from an Android activity.
     *
     * @param requestCode the request code used to differentiate requests from one another.
     * @param intent the intent to use to initiate a browser switch
     * @param activity the activity used to start browser switch
     */
    @SuppressWarnings("SameParameterValue")
    public void start(int requestCode, Intent intent, FragmentActivity activity) {
        if (activity instanceof BrowserSwitchListener) {
            start(requestCode, intent, activity, (BrowserSwitchListener) activity);
        } else {
            throw new IllegalArgumentException("Activity must implement BrowserSwitchListener.");
        }
    }

    /**
     * Open a browser or <a href="https://developer.chrome.com/multidevice/android/customtabs">Chrome Custom Tab</a>
     * with the given intent from an Android activity.
     *
     * @param requestCode the request code used to differentiate requests from one another.
     * @param intent the intent to use to initiate a browser switch
     * @param activity the activity used to start browser switch
     * @param listener the listener that will receive browser switch callbacks
     */
    public void start(
        int requestCode, Intent intent, FragmentActivity activity, BrowserSwitchListener listener) {
        Context appContext = activity.getApplicationContext();
        String errorMessage = assertCanPerformBrowserSwitch(requestCode, appContext, intent);
        if (errorMessage == null) {
            BrowserSwitchRequest request = new BrowserSwitchRequest(
                    requestCode, intent.getData(), BrowserSwitchRequest.PENDING);
            persistentStore.putActiveRequest(request, appContext);
            appContext.startActivity(intent);
        } else {
            BrowserSwitchResult result =
                new BrowserSwitchResult(BrowserSwitchResult.STATUS_ERROR, errorMessage);
            listener.onBrowserSwitchResult(requestCode, result, null);
        }
    }

    private String assertCanPerformBrowserSwitch(int requestCode, Context context, Intent intent) {
        String errorMessage = null;

        if (!isValidRequestCode(requestCode)) {
            errorMessage = "Request code cannot be Integer.MIN_VALUE";
        } else if (!isConfiguredForBrowserSwitch(context)) {
            errorMessage =
                "The return url scheme was not set up, incorrectly set up, " +
                "or more than one Activity on this device defines the same url " +
                "scheme in it's Android Manifest. See " +
                "https://github.com/braintree/browser-switch-android for more " +
                "information on setting up a return url scheme.";
        } else if (!canOpenUrl(context, intent)) {
            StringBuilder messageBuilder = new StringBuilder("No installed activities can open this URL");
            Uri uri = intent.getData();
            if (uri != null) {
                messageBuilder.append(String.format(": %s", uri.toString()));
            }
            errorMessage = messageBuilder.toString();
        }

        return errorMessage;
    }

    private boolean isValidRequestCode(int requestCode) {
        return requestCode != Integer.MIN_VALUE;
    }

    private boolean isConfiguredForBrowserSwitch(Context context) {
        Intent browserSwitchActivityIntent =
            config.createIntentForBrowserSwitchActivityQuery(returnUrlScheme);
        return activityFinder.canResolveActivityForIntent(context, browserSwitchActivityIntent);
    }

    private boolean canOpenUrl(Context context, Intent intent) {
        return activityFinder.canResolveActivityForIntent(context, intent);
    }

    /**
     * Deliver a pending browser switch result to an Android fragment that is also a BrowserSwitchListener.
     * We recommend you call this method in onResume to receive a browser switch result once your
     * app has re-entered the foreground.
     *
     * Cancel and Success results will be delivered only once. If there are no pending
     * browser switch results, this method does nothing.
     *
     * @param fragment the BrowserSwitchListener that will receive a pending browser switch result
     */
    public void deliverResult(Fragment fragment) {
        if (fragment instanceof BrowserSwitchListener) {
            deliverResult(fragment, (BrowserSwitchListener) fragment);
        } else {
            throw new IllegalArgumentException("Fragment must implement BrowserSwitchListener.");
        }
    }

    /**
     * Deliver a pending browser switch result to an Android fragment that is also a BrowserSwitchListener.
     * We recommend you call this method in onResume to receive a browser switch result once your
     * app has re-entered the foreground.
     *
     * @param fragment the BrowserSwitchListener that will receive a pending browser switch result
     * @param listener the listener that will receive browser switch callbacks
     */
    public void deliverResult(Fragment fragment, BrowserSwitchListener listener) {
        FragmentActivity activity = fragment.getActivity();
        if (activity != null) {
            deliverResult(activity, listener);
        } else {
            throw new IllegalStateException("Fragment must be attached to an activity.");
        }
    }

    /**
     * Deliver a pending browser switch result to an Android activity that is also a BrowserSwitchListener.
     * We recommend you call this method in onResume to receive a browser switch result once your
     * app has re-entered the foreground.
     *
     * Cancel and Success results will be delivered only once. If there are no pending
     * browser switch results, this method does nothing.
     *
     * @param activity the BrowserSwitchListener that will receive a pending browser switch result
     */
    public void deliverResult(FragmentActivity activity) {
        if (activity instanceof BrowserSwitchListener) {
            deliverResult(activity, (BrowserSwitchListener) activity);
        } else {
            throw new IllegalArgumentException("Activity must implement BrowserSwitchListener.");
        }
    }

    /**
     * Deliver a pending browser switch result to an Android activity that is also a BrowserSwitchListener.
     * We recommend you call this method in onResume to receive a browser switch result once your
     * app has re-entered the foreground.
     *
     * Cancel and Success results will be delivered only once. If there are no pending
     * browser switch results, this method does nothing.
     *
     * @param activity the BrowserSwitchListener that will receive a pending browser switch result
     * @param listener the listener that will receive browser switch callbacks
     */
    public void deliverResult(FragmentActivity activity, BrowserSwitchListener listener) {
        Context appContext = activity.getApplicationContext();
        BrowserSwitchRequest request = persistentStore.getActiveRequest(appContext);
        if (request != null) {
            persistentStore.clearActiveRequest(appContext);
            int requestCode = request.getRequestCode();

            Uri uri = null;
            BrowserSwitchResult result;
            if (request.getState().equalsIgnoreCase(BrowserSwitchRequest.SUCCESS)) {
                uri = request.getUri();
                result = new BrowserSwitchResult(BrowserSwitchResult.STATUS_OK);
            } else {
                result = new BrowserSwitchResult(BrowserSwitchResult.STATUS_CANCELED);
            }
            listener.onBrowserSwitchResult(requestCode, result, uri);
        }
    }

    /**
     * Capture a browser switch result that will later be delivered to the caller
     * (see {@link #deliverResult(FragmentActivity)} and {@link #deliverResult(Fragment)}).
     * @param intent intent for app link that called back into your application from browser
     * @param context Android context at time of capture
     */
    void captureResult(@Nullable Intent intent, Context context) {
        if (intent == null) {
            return;
        }

        Uri uri = intent.getData();
        BrowserSwitchRequest request = persistentStore.getActiveRequest(context);
        if (request != null && uri != null) {
            request.setUri(uri);
            request.setState(BrowserSwitchRequest.SUCCESS);
            persistentStore.putActiveRequest(request, context);
        }
    }
}