package awais.instagrabber.utils;

import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.storage.StorageManager;
import android.provider.Browser;
import android.provider.DocumentsContract;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
import android.util.TypedValue;
import android.view.Display;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.webkit.MimeTypeMap;
import android.widget.Toast;

import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.documentfile.provider.DocumentFile;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;

import com.google.android.exoplayer2.database.ExoDatabaseProvider;
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;

import org.json.JSONObject;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import awais.instagrabber.R;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.enums.FavoriteType;

public final class Utils {
    private static final String TAG = "Utils";
    private static final int VIDEO_CACHE_MAX_BYTES = 10 * 1024 * 1024;

    // public static LogCollector logCollector;
    public static SettingsHelper settingsHelper;
    public static boolean sessionVolumeFull = false;
    public static final MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
    public static final DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
    public static ClipboardManager clipboardManager;
    public static SimpleCache simpleCache;
    private static int statusBarHeight;
    private static int actionBarHeight;
    public static String cacheDir;
    private static int defaultStatusBarColor;
    private static Object[] volumes;

    public static int convertDpToPx(final float dp) {
        return Math.round((dp * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT);
    }

    public static void copyText(@NonNull final Context context, final CharSequence string) {
        if (clipboardManager == null) {
            clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
        }
        int toastMessage = R.string.clipboard_error;
        if (clipboardManager != null) {
            try {
                clipboardManager.setPrimaryClip(ClipData.newPlainText(context.getString(R.string.app_name), string));
                toastMessage = R.string.clipboard_copied;
            } catch (Exception e) {
                Log.e(TAG, "copyText: ", e);
            }
        }
        Toast.makeText(context, toastMessage, Toast.LENGTH_SHORT).show();
    }

    public static Map<String, String> sign(final Map<String, Object> form) {
        // final String signed = sign(Constants.SIGNATURE_KEY, new JSONObject(form).toString());
        // if (signed == null) {
        //     return null;
        // }
        final Map<String, String> map = new HashMap<>();
        // map.put("ig_sig_key_version", Constants.SIGNATURE_VERSION);
        // map.put("signed_body", signed);
        map.put("signed_body", "SIGNATURE." + new JSONObject(form).toString());
        return map;
    }

    // public static String sign(final String key, final String message) {
    //     try {
    //         final Mac hasher = Mac.getInstance("HmacSHA256");
    //         hasher.init(new SecretKeySpec(key.getBytes(), "HmacSHA256"));
    //         byte[] hash = hasher.doFinal(message.getBytes());
    //         final StringBuilder hexString = new StringBuilder();
    //         for (byte b : hash) {
    //             final String hex = Integer.toHexString(0xff & b);
    //             if (hex.length() == 1) hexString.append('0');
    //             hexString.append(hex);
    //         }
    //         return hexString.toString() + "." + message;
    //     } catch (Exception e) {
    //         Log.e(TAG, "Error signing", e);
    //         return null;
    //     }
    // }

    public static String getMimeType(@NonNull final Uri uri, final ContentResolver contentResolver) {
        String mimeType;
        final String scheme = uri.getScheme();
        final String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString());
        if (TextUtils.isEmpty(scheme)) {
            mimeType = mimeTypeMap.getMimeTypeFromExtension(fileExtension.toLowerCase());
        } else {
            if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
                mimeType = contentResolver.getType(uri);
            } else {
                mimeType = mimeTypeMap.getMimeTypeFromExtension(fileExtension.toLowerCase());
            }
        }
        if (mimeType == null) return null;
        return mimeType.toLowerCase();
    }

    public static SimpleCache getSimpleCacheInstance(final Context context) {
        if (context == null) {
            return null;
        }
        final ExoDatabaseProvider exoDatabaseProvider = new ExoDatabaseProvider(context);
        final File cacheDir = context.getCacheDir();
        if (simpleCache == null && cacheDir != null) {
            simpleCache = new SimpleCache(cacheDir, new LeastRecentlyUsedCacheEvictor(VIDEO_CACHE_MAX_BYTES), exoDatabaseProvider);
        }
        return simpleCache;
    }

    @Nullable
    public static Pair<FavoriteType, String> migrateOldFavQuery(final String queryText) {
        if (queryText.startsWith("@")) {
            return new Pair<>(FavoriteType.USER, queryText.substring(1));
        } else if (queryText.contains("/")) {
            return new Pair<>(FavoriteType.LOCATION, queryText.substring(0, queryText.indexOf("/")));
        } else if (queryText.startsWith("#")) {
            return new Pair<>(FavoriteType.HASHTAG, queryText.substring(1));
        }
        return null;
    }

    public static int getStatusBarHeight(final Context context) {
        if (statusBarHeight > 0) {
            return statusBarHeight;
        }
        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
        }
        return statusBarHeight;
    }

    public static int getActionBarHeight(@NonNull final Context context) {
        if (actionBarHeight > 0) {
            return actionBarHeight;
        }
        final TypedValue tv = new TypedValue();
        if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
            actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, displayMetrics);
        }
        return actionBarHeight;
    }

    public static void openURL(final Context context, final String url) {
        if (context == null || TextUtils.isEmpty(url)) {
            return;
        }
        try {
            String url1 = url;
            // add http:// if string doesn't have http:// or https://
            if (!url.startsWith("http://") && !url.startsWith("https://")) {
                url1 = "http://" + url;
            }
            final Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url1));
            i.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
            i.putExtra(Browser.EXTRA_CREATE_NEW_TAB, true);
            context.startActivity(i);
        } catch (ActivityNotFoundException e) {
            Log.e(TAG, "openURL: No activity found to handle URLs", e);
            Toast.makeText(context, context.getString(R.string.no_external_app_url), Toast.LENGTH_LONG).show();
        } catch (Exception e) {
            Log.e(TAG, "openURL", e);
        }
    }

    public static void openEmailAddress(final Context context, final String emailAddress) {
        if (context == null || TextUtils.isEmpty(emailAddress)) {
            return;
        }
        Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:" + emailAddress));
        emailIntent.putExtra(Intent.EXTRA_SUBJECT, "");
        emailIntent.putExtra(Intent.EXTRA_TEXT, "");
        context.startActivity(emailIntent);
    }

    public static void displayToastAboveView(@NonNull final Context context,
                                             @NonNull final View view,
                                             @NonNull final String text) {
        final Toast toast = Toast.makeText(context, text, Toast.LENGTH_SHORT);
        toast.setGravity(Gravity.TOP | Gravity.START,
                         view.getLeft(),
                         view.getTop());
        toast.show();
    }

    public static PostsLayoutPreferences getPostsLayoutPreferences(final String layoutPreferenceKey) {
        PostsLayoutPreferences layoutPreferences = PostsLayoutPreferences.fromJson(settingsHelper.getString(layoutPreferenceKey));
        if (layoutPreferences == null) {
            layoutPreferences = PostsLayoutPreferences.builder().build();
            settingsHelper.putString(layoutPreferenceKey, layoutPreferences.getJson());
        }
        return layoutPreferences;
    }

    private static Field mAttachInfoField;
    private static Field mStableInsetsField;

    public static int getViewInset(View view) {
        if (view == null
                || view.getHeight() == displayMetrics.heightPixels
                || view.getHeight() == displayMetrics.widthPixels - getStatusBarHeight(view.getContext())) {
            return 0;
        }
        try {
            if (mAttachInfoField == null) {
                //noinspection JavaReflectionMemberAccess
                mAttachInfoField = View.class.getDeclaredField("mAttachInfo");
                mAttachInfoField.setAccessible(true);
            }
            Object mAttachInfo = mAttachInfoField.get(view);
            if (mAttachInfo != null) {
                if (mStableInsetsField == null) {
                    mStableInsetsField = mAttachInfo.getClass().getDeclaredField("mStableInsets");
                    mStableInsetsField.setAccessible(true);
                }
                Rect insets = (Rect) mStableInsetsField.get(mAttachInfo);
                if (insets == null) {
                    return 0;
                }
                return insets.bottom;
            }
        } catch (Exception e) {
            Log.e(TAG, "getViewInset", e);
        }
        return 0;
    }

    public static int getThemeAccentColor(Context context) {
        int colorAttr;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            colorAttr = android.R.attr.colorAccent;
        } else {
            //Get colorAccent defined for AppCompat
            colorAttr = context.getResources().getIdentifier("colorAccent", "attr", context.getPackageName());
        }
        TypedValue outValue = new TypedValue();
        context.getTheme().resolveAttribute(colorAttr, outValue, true);
        return outValue.data;
    }

    public static int getAttrValue(@NonNull final Context context, final int attr) {
        final TypedValue outValue = new TypedValue();
        context.getTheme().resolveAttribute(attr, outValue, true);
        return outValue.data;
    }

    public static int getAttrResId(@NonNull final Context context, final int attr) {
        final TypedValue outValue = new TypedValue();
        context.getTheme().resolveAttribute(attr, outValue, true);
        return outValue.resourceId;
    }

    public static void transparentStatusBar(final Activity activity,
                                            final boolean enable,
                                            final boolean fullscreen) {
        if (activity == null) return;
        final ActionBar actionBar = ((AppCompatActivity) activity).getSupportActionBar();
        final Window window = activity.getWindow();
        final View decorView = window.getDecorView();
        if (enable) {
            decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
            if (actionBar != null) {
                actionBar.hide();
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                defaultStatusBarColor = window.getStatusBarColor();
                window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
                window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
                // FOR TRANSPARENT NAVIGATION BAR
                window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
                window.setStatusBarColor(Color.TRANSPARENT);
                Log.d(TAG, "Setting Color Transparent " + Color.TRANSPARENT + " Default Color " + defaultStatusBarColor);
                return;
            }
            Log.d(TAG, "Setting Color Trans " + Color.TRANSPARENT);
            window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            return;
        }
        if (fullscreen) {
            int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN;
            decorView.setSystemUiVisibility(uiOptions);
            return;
        }
        if (actionBar != null) {
            actionBar.show();
        }
        decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
            window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
            window.setStatusBarColor(defaultStatusBarColor);
            return;
        }
        window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
    }

    // public static void mediaScanFile(@NonNull final Context context,
    //                                  @NonNull File file,
    //                                  @NonNull final OnScanCompletedListener callback) {
    //     //noinspection UnstableApiUsage
    //     final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(Files.getFileExtension(file.getName()));
    //     MediaScannerConnection.scanFile(
    //             context,
    //             new String[]{file.getAbsolutePath()},
    //             new String[]{mimeType},
    //             callback
    //     );
    // }

    public static void showKeyboard(@NonNull final View view) {
        try {
            final Context context = view.getContext();
            if (context == null) return;
            final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
            if (imm == null) return;
            view.requestFocus();
            final boolean shown = imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
            if (!shown) {
                Log.e(TAG, "showKeyboard: System did not display the keyboard");
            }
        } catch (Exception e) {
            Log.e(TAG, "showKeyboard: ", e);
        }
    }

    public static void hideKeyboard(final View view) {
        if (view == null) return;
        final Context context = view.getContext();
        if (context == null) return;
        try {
            final InputMethodManager manager = (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE);
            if (manager == null) return;
            manager.hideSoftInputFromWindow(view.getWindowToken(), 0);
        } catch (Exception e) {
            Log.e(TAG, "hideKeyboard: ", e);
        }
    }

    public static Drawable getAnimatableDrawable(@NonNull final Context context,
                                                 @DrawableRes final int drawableResId) {
        final Drawable drawable;
        if (Build.VERSION.SDK_INT >= 24) {
            drawable = ContextCompat.getDrawable(context, drawableResId);
        } else {
            drawable = AnimatedVectorDrawableCompat.create(context, drawableResId);
        }
        return drawable;
    }

    public static void enabledKeepScreenOn(@NonNull final Activity activity) {
        final Window window = activity.getWindow();
        if (window == null) return;
        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    }

    public static void disableKeepScreenOn(@NonNull final Activity activity) {
        final Window window = activity.getWindow();
        if (window == null) return;
        window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    }

    public static <T> void moveItem(int sourceIndex, int targetIndex, List<T> list) {
        if (sourceIndex <= targetIndex) {
            Collections.rotate(list.subList(sourceIndex, targetIndex + 1), -1);
        } else {
            Collections.rotate(list.subList(targetIndex, sourceIndex + 1), 1);
        }
    }

    // public static void scanDocumentFile(@NonNull final Context context,
    //                                     @NonNull final DocumentFile documentFile,
    //                                     @NonNull final OnScanCompletedListener callback) {
    //     if (!documentFile.isFile() || !documentFile.exists()) {
    //         Log.d(TAG, "scanDocumentFile: " + documentFile);
    //         callback.onScanCompleted(null, null);
    //         return;
    //     }
    //     File file = null;
    //     try {
    //         file = getDocumentFileRealPath(context, documentFile);
    //     } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
    //         Log.e(TAG, "scanDocumentFile: ", e);
    //     }
    //     if (file == null) return;
    //     MediaScannerConnection.scanFile(context,
    //                                     new String[]{file.getAbsolutePath()},
    //                                     new String[]{documentFile.getType()},
    //                                     callback);
    // }

    public static File getDocumentFileRealPath(@NonNull final Context context,
                                               @NonNull final DocumentFile documentFile)
            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        final String docId = DocumentsContract.getDocumentId(documentFile.getUri());
        final String[] split = docId.split(":");
        final String type = split[0];

        if (type.equalsIgnoreCase("primary")) {
            return new File(Environment.getExternalStorageDirectory(), split[1]);
        } else if (type.equalsIgnoreCase("raw")) {
            return new File(split[1]);
        } else {
            if (volumes == null) {
                final StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
                if (sm == null) return null;
                final Method getVolumeListMethod = sm.getClass().getMethod("getVolumeList");
                volumes = (Object[]) getVolumeListMethod.invoke(sm);
            }
            if (volumes == null) return null;
            for (Object volume : volumes) {
                final Method getUuidMethod = volume.getClass().getMethod("getUuid");
                final String uuid = (String) getUuidMethod.invoke(volume);

                if (uuid != null && uuid.equalsIgnoreCase(type)) {
                    final Method getPathMethod = volume.getClass().getMethod("getPath");
                    final String path = (String) getPathMethod.invoke(volume);
                    return new File(path, split[1]);
                }
            }
        }

        return null;
    }

    public static void setupSelectedDir(@NonNull final Context context,
                                        @NonNull final Intent intent) throws DownloadUtils.ReselectDocumentTreeException {
        final Uri dirUri = intent.getData();
        Log.d(TAG, "onActivityResult: " + dirUri);
        if (dirUri == null) return;
        final int takeFlags = intent.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        context.getContentResolver().takePersistableUriPermission(dirUri, takeFlags);
        // re-init DownloadUtils
        DownloadUtils.init(context, dirUri.toString());
    }

    @NonNull
    public static Point getNavigationBarSize(@NonNull Context context) {
        Point appUsableSize = getAppUsableScreenSize(context);
        Point realScreenSize = getRealScreenSize(context);

        // navigation bar on the right
        if (appUsableSize.x < realScreenSize.x) {
            return new Point(realScreenSize.x - appUsableSize.x, appUsableSize.y);
        }

        // navigation bar at the bottom
        if (appUsableSize.y < realScreenSize.y) {
            return new Point(appUsableSize.x, realScreenSize.y - appUsableSize.y);
        }

        // navigation bar is not present
        return new Point();
    }

    @NonNull
    public static Point getAppUsableScreenSize(@NonNull Context context) {
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = windowManager.getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);
        return size;
    }

    @NonNull
    public static Point getRealScreenSize(@NonNull Context context) {
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = windowManager.getDefaultDisplay();
        Point size = new Point();
        display.getRealSize(size);
        return size;
    }

    public static <F, S> LiveData<Pair<F, S>> zipLiveData(@NonNull final LiveData<F> firstLiveData,
                                                          @NonNull final LiveData<S> secondLiveData) {
        final ZippedLiveData<F, S> zippedLiveData = new ZippedLiveData<>();
        zippedLiveData.addFirstSource(firstLiveData);
        zippedLiveData.addSecondSource(secondLiveData);
        return zippedLiveData;
    }

    public static class ZippedLiveData<F, S> extends MediatorLiveData<Pair<F, S>> {
        private F lastF;
        private S lastS;

        private void update() {
            F localLastF = lastF;
            S localLastS = lastS;
            if (localLastF != null && localLastS != null) {
                setValue(new Pair<>(localLastF, localLastS));
            }
        }

        public void addFirstSource(@NonNull final LiveData<F> firstLiveData) {
            addSource(firstLiveData, f -> {
                lastF = f;
                update();
            });
        }

        public void addSecondSource(@NonNull final LiveData<S> secondLiveData) {
            addSource(secondLiveData, s -> {
                lastS = s;
                update();
            });
        }
    }
}
