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

import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.Insets;
import androidx.core.util.Pair;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.shape.MaterialShapeDrawable;
import com.google.android.material.shape.ShapeAppearanceModel;

import de.danoeh.antennapod.model.feed.FeedPreferences;
import de.danoeh.antennapod.net.download.service.episode.autodownload.EpisodeCleanupAlgorithmFactory;
import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.ui.common.ConfirmationDialog;
import de.danoeh.antennapod.ui.screen.subscriptions.FeedMenuHandler;
import org.apache.commons.lang3.StringUtils;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.ui.screen.preferences.PreferenceActivity;
import de.danoeh.antennapod.ui.MenuItemUtils;
import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.storage.database.NavDrawerData;
import de.danoeh.antennapod.ui.screen.feed.RemoveFeedDialog;
import de.danoeh.antennapod.ui.screen.feed.RenameFeedDialog;
import de.danoeh.antennapod.ui.screen.subscriptions.SubscriptionsFilterDialog;
import de.danoeh.antennapod.event.FeedListUpdateEvent;
import de.danoeh.antennapod.event.QueueEvent;
import de.danoeh.antennapod.event.UnreadItemsUpdateEvent;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
import de.danoeh.antennapod.ui.common.ThemeUtils;
import de.danoeh.antennapod.ui.screen.home.HomeFragment;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;

public class NavDrawerFragment extends Fragment implements SharedPreferences.OnSharedPreferenceChangeListener {
    @VisibleForTesting
    public static final String PREF_LAST_FRAGMENT_TAG = "prefLastFragmentTag";
    private static final String PREF_OPEN_FOLDERS = "prefOpenFolders";
    @VisibleForTesting
    public static final String PREF_NAME = "NavDrawerPrefs";
    public static final String TAG = "NavDrawerFragment";

    private NavDrawerData navDrawerData;
    private int reclaimableSpace = 0;
    private List<DrawerItem> flatItemList;
    private DrawerItem contextPressedItem = null;
    private NavListAdapter navAdapter;
    private Disposable disposable;
    private ProgressBar progressBar;
    private Set<String> openFolders = new HashSet<>();

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        View root = inflater.inflate(R.layout.nav_list, container, false);
        setupDrawerRoundBackground(root);
        ViewCompat.setOnApplyWindowInsetsListener(root, (view, insets) -> {
            Insets bars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            view.setPadding(bars.left, bars.top, bars.right, 0);
            float navigationBarHeight = 0;
            Activity activity = getActivity();
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && activity != null) {
                navigationBarHeight = getActivity().getWindow().getNavigationBarDividerColor() == Color.TRANSPARENT
                        ? 0 : 1 * getResources().getDisplayMetrics().density; // Assuming the divider is 1dp in height
            }
            float bottomInset = Math.max(0f, Math.round(bars.bottom - navigationBarHeight));
            ((ViewGroup.MarginLayoutParams) view.getLayoutParams()).bottomMargin = (int) bottomInset;
            return insets;
        });

        SharedPreferences preferences = getContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
        openFolders = new HashSet<>(preferences.getStringSet(PREF_OPEN_FOLDERS, new HashSet<>())); // Must not modify

        progressBar = root.findViewById(R.id.progressBar);
        RecyclerView navList = root.findViewById(R.id.nav_list);
        navAdapter = new NavListAdapter(itemAccess, getActivity());
        navAdapter.setHasStableIds(true);
        navList.setAdapter(navAdapter);
        navList.setLayoutManager(new LinearLayoutManager(getContext()));

        root.findViewById(R.id.nav_settings).setOnClickListener(v ->
                startActivity(new Intent(getActivity(), PreferenceActivity.class)));

        preferences.registerOnSharedPreferenceChangeListener(this);
        return root;
    }

    private void setupDrawerRoundBackground(View root) {
        // Akin to this logic:
        //   https://github.com/material-components/material-components-android/blob/8938da8c/lib/java/com/google/android/material/navigation/NavigationView.java#L405
        ShapeAppearanceModel.Builder shapeBuilder = ShapeAppearanceModel.builder();
        float cornerSize = getResources().getDimension(R.dimen.drawer_corner_size);
        boolean isRtl = getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
        if (isRtl) {
            shapeBuilder.setTopLeftCornerSize(cornerSize).setBottomLeftCornerSize(cornerSize);
        } else {
            shapeBuilder.setTopRightCornerSize(cornerSize).setBottomRightCornerSize(cornerSize);
        }
        MaterialShapeDrawable drawable = new MaterialShapeDrawable(shapeBuilder.build());
        int themeColor = ThemeUtils.getColorFromAttr(root.getContext(), android.R.attr.colorBackground);
        drawable.setFillColor(ColorStateList.valueOf(themeColor));
        root.setBackground(drawable);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        EventBus.getDefault().register(this);
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        EventBus.getDefault().unregister(this);
        if (disposable != null) {
            disposable.dispose();
        }
        getContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
                .unregisterOnSharedPreferenceChangeListener(this);
    }

    @Override
    public void onCreateContextMenu(@NonNull ContextMenu menu, @NonNull View v, ContextMenu.ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        MenuInflater inflater = getActivity().getMenuInflater();
        if (contextPressedItem.isFeed()) {
            menu.setHeaderTitle(contextPressedItem.asFeed().getTitle());
            inflater.inflate(R.menu.nav_feed_context, menu);
            // episodes are not loaded, so we cannot check if the podcast has new or unplayed ones!
        } else if (FeedPreferences.TAG_UNTAGGED.equals(contextPressedItem.asTag().getTitle())) {
            return;
        } else {
            menu.setHeaderTitle(contextPressedItem.asTag().getTitle());
            inflater.inflate(R.menu.nav_folder_context, menu);
        }
        MenuItemUtils.setOnClickListeners(menu, this::onContextItemSelected);
    }

    @Override
    public boolean onContextItemSelected(@NonNull MenuItem item) {
        DrawerItem pressedItem = contextPressedItem;
        contextPressedItem = null;
        if (pressedItem == null) {
            return false;
        }
        if (pressedItem.isFeed()) {
            return onFeedContextMenuClicked(pressedItem.asFeed(), item);
        } else {
            return onTagContextMenuClicked(pressedItem.asTag(), item);
        }
    }

    private boolean onFeedContextMenuClicked(Feed feed, MenuItem item) {
        final int itemId = item.getItemId();
        if (itemId == R.id.remove_archive_feed) {
            new RemoveFeedDialogClose(Collections.singletonList(feed)).show(getParentFragmentManager(), null);
            return true;
        }
        if (FeedMenuHandler.onMenuItemClicked(this, itemId, feed, null)) {
            return true;
        }
        return super.onContextItemSelected(item);
    }

    public static class RemoveFeedDialogClose extends RemoveFeedDialog {
        public RemoveFeedDialogClose(@NonNull List<Feed> feeds) {
            super(feeds);
        }

        public RemoveFeedDialogClose() {
            super();
        }

        @Override
        protected void onRemoveButtonPressed() {
            if (String.valueOf(feeds.get(0).getId()).equals(getLastNavFragment(getContext()))) {
                // Make sure fragment is hidden before actually starting to delete
                ((MainActivity) getActivity()).loadFragment(UserPreferences.getDefaultPage(), null);
                getActivity().getSupportFragmentManager().executePendingTransactions();
            }
            super.onRemoveButtonPressed();
        }
    }

    private boolean onTagContextMenuClicked(NavDrawerData.TagItem drawerItem, MenuItem item) {
        final int itemId = item.getItemId();
        if (itemId == R.id.rename_folder_item) {
            new RenameFeedDialog(getActivity(), drawerItem).show();
            return true;
        } else if (itemId == R.id.delete_folder_item) {
            ConfirmationDialog dialog = new ConfirmationDialog(
                    getContext(), R.string.delete_tag_label,
                    getString(R.string.delete_tag_confirmation, drawerItem.getTitle())) {

                @Override
                public void onConfirmButtonPressed(DialogInterface dialog) {
                    for (Feed feed : drawerItem.getFeeds()) {
                        FeedPreferences preferences = feed.getPreferences();
                        preferences.getTags().remove(drawerItem.getTitle());
                        DBWriter.setFeedPreferences(preferences);
                    }
                }
            };
            dialog.createNewDialog().show();

            return true;
        }
        return super.onContextItemSelected(item);
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onUnreadItemsChanged(UnreadItemsUpdateEvent event) {
        loadData();
    }


    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onFeedListChanged(FeedListUpdateEvent event) {
        loadData();
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onQueueChanged(QueueEvent event) {
        Log.d(TAG, "onQueueChanged(" + event + ")");
        // we are only interested in the number of queue items, not download status or position
        if (event.action == QueueEvent.Action.DELETED_MEDIA
                || event.action == QueueEvent.Action.SORTED
                || event.action == QueueEvent.Action.MOVED) {
            return;
        }
        loadData();
    }

    @Override
    public void onResume() {
        super.onResume();
        loadData();
    }

    private final NavListAdapter.ItemAccess itemAccess = new NavListAdapter.ItemAccess() {
        @Override
        public int getCount() {
            if (flatItemList != null) {
                return flatItemList.size();
            } else {
                return 0;
            }
        }

        @Override
        public DrawerItem getItem(int position) {
            if (flatItemList != null && 0 <= position && position < flatItemList.size()) {
                return flatItemList.get(position);
            } else {
                return null;
            }
        }

        @Override
        public boolean isSelected(int position) {
            String lastNavFragment = getLastNavFragment(getContext());
            if (position < navAdapter.getSubscriptionOffset()) {
                return navAdapter.getFragmentTags().get(position).equals(lastNavFragment);
            } else if (StringUtils.isNumeric(lastNavFragment)) { // last fragment was not a list, but a feed
                long feedId = Long.parseLong(lastNavFragment);
                if (navDrawerData != null) {
                    DrawerItem itemToCheck = flatItemList.get(
                            position - navAdapter.getSubscriptionOffset());
                    if (itemToCheck.isFeed()) {
                        // When the same feed is displayed multiple times, it should be highlighted multiple times.
                        return itemToCheck.asFeed().getId() == feedId;
                    }
                }
            }
            return false;
        }

        @Override
        public int getQueueSize() {
            return (navDrawerData != null) ? navDrawerData.queueSize : 0;
        }

        @Override
        public int getNumberOfNewItems() {
            return (navDrawerData != null) ? navDrawerData.numNewItems : 0;
        }

        @Override
        public int getNumberOfDownloadedItems() {
            return (navDrawerData != null) ? navDrawerData.numDownloadedItems : 0;
        }

        @Override
        public int getReclaimableItems() {
            return reclaimableSpace;
        }

        @Override
        public int getFeedCounterSum() {
            if (navDrawerData == null) {
                return 0;
            }
            int sum = 0;
            for (int counter : navDrawerData.feedCounters.values()) {
                sum += counter;
            }
            return sum;
        }

        @Override
        public void onItemClick(int position) {
            int viewType = navAdapter.getItemViewType(position);
            if (viewType != NavListAdapter.VIEW_TYPE_SECTION_DIVIDER) {
                if (position < navAdapter.getSubscriptionOffset()) {
                    String tag = navAdapter.getFragmentTags().get(position);
                    ((MainActivity) getActivity()).loadFragment(tag, null);
                    ((MainActivity) getActivity()).getBottomSheet().setState(BottomSheetBehavior.STATE_COLLAPSED);
                } else {
                    int pos = position - navAdapter.getSubscriptionOffset();
                    DrawerItem clickedItem = flatItemList.get(pos);

                    if (clickedItem.isFeed()) {
                        long feedId = clickedItem.asFeed().getId();
                        ((MainActivity) getActivity()).loadFeedFragmentById(feedId, null);
                        ((MainActivity) getActivity()).getBottomSheet()
                                .setState(BottomSheetBehavior.STATE_COLLAPSED);
                    } else {
                        NavDrawerData.TagItem folder = clickedItem.asTag();
                        if (openFolders.contains(folder.getTitle())) {
                            openFolders.remove(folder.getTitle());
                        } else {
                            openFolders.add(folder.getTitle());
                        }

                        getContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
                                .edit()
                                .putStringSet(PREF_OPEN_FOLDERS, openFolders)
                                .apply();

                        disposable = Observable.fromCallable(() -> makeFlatDrawerData(
                                        navDrawerData.tags, navDrawerData.feedCounters))
                                .subscribeOn(Schedulers.computation())
                                .observeOn(AndroidSchedulers.mainThread())
                                .subscribe(
                                        result -> {
                                            flatItemList = result;
                                            navAdapter.notifyDataSetChanged();
                                        }, error -> Log.e(TAG, Log.getStackTraceString(error)));
                    }
                }
            } else if (UserPreferences.getSubscriptionsFilter().isEnabled()
                    && navAdapter.showSubscriptionList) {
                new SubscriptionsFilterDialog().show(getChildFragmentManager(), "filter");
            }
        }

        @Override
        public boolean onItemLongClick(int position) {
            if (position < navAdapter.getFragmentTags().size()) {
                new DrawerPreferencesDialog(getContext(), () -> {
                    navAdapter.notifyDataSetChanged();
                    if (UserPreferences.getHiddenDrawerItems().contains(getLastNavFragment(getContext()))) {
                        new MainActivityStarter(getContext())
                                .withFragmentLoaded(UserPreferences.getDefaultPage())
                                .withClearBackStack()
                                .withDrawerOpen()
                                .start();
                    }
                }).show();
                return true;
            } else {
                contextPressedItem = flatItemList.get(position - navAdapter.getSubscriptionOffset());
                return false;
            }
        }

        @Override
        public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
            NavDrawerFragment.this.onCreateContextMenu(menu, v, menuInfo);
        }
    };

    private void loadData() {
        disposable = Observable.fromCallable(
                () -> {
                    NavDrawerData data = DBReader.getNavDrawerData(UserPreferences.getSubscriptionsFilter(),
                            UserPreferences.getFeedOrder(), UserPreferences.getFeedCounterSetting(),
                            Feed.STATE_SUBSCRIBED);
                    reclaimableSpace = EpisodeCleanupAlgorithmFactory.build().getReclaimableItems();
                    return new Pair<>(data, makeFlatDrawerData(data.tags, data.feedCounters));
                })
                .subscribeOn(Schedulers.computation())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                        result -> {
                            navDrawerData = result.first;
                            flatItemList = result.second;
                            navAdapter.notifyDataSetChanged();
                            progressBar.setVisibility(View.GONE); // Stays hidden once there is something in the list
                        }, error -> {
                            Log.e(TAG, Log.getStackTraceString(error));
                            progressBar.setVisibility(View.GONE);
                        });
    }

    private List<DrawerItem> makeFlatDrawerData(List<NavDrawerData.TagItem> tags,
                                                @Nullable java.util.Map<Long, Integer> feedCounters) {
        List<DrawerItem> flatItems = new ArrayList<>();
        for (NavDrawerData.TagItem tag : tags) {
            if (FeedPreferences.TAG_ROOT.equals(tag.getTitle())) {
                for (Feed feed : tag.getFeeds()) {
                    flatItems.add(new DrawerItem(feed, feedCounter(feed, feedCounters), 0));
                }
                break;
            }
        }
        for (NavDrawerData.TagItem tag : tags) {
            if (FeedPreferences.TAG_ROOT.equals(tag.getTitle())
                    || FeedPreferences.TAG_UNTAGGED.equals(tag.getTitle())) {
                continue;
            }
            DrawerItem tagItem = new DrawerItem(tag);
            flatItems.add(tagItem);
            int counter = 0;
            tag.setOpen(openFolders.contains(tag.getTitle()));
            for (Feed feed : tag.getFeeds()) {
                counter += feedCounter(feed, feedCounters);
                if (tag.isOpen()) {
                    flatItems.add(new DrawerItem(feed, feedCounter(feed, feedCounters), 1));
                }
            }
            tagItem.setCounter(counter);
        }
        return flatItems;
    }

    private int feedCounter(Feed feed, Map<Long, Integer> feedCounters) {
        if (navDrawerData == null || feedCounters == null) {
            return 0;
        }
        return feedCounters.containsKey(feed.getId()) ? feedCounters.get(feed.getId()) : 0;
    }

    public static void saveLastNavFragment(Context context, String tag) {
        Log.d(TAG, "saveLastNavFragment(tag: " + tag + ")");
        SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
        SharedPreferences.Editor edit = prefs.edit();
        if (tag != null) {
            edit.putString(PREF_LAST_FRAGMENT_TAG, tag);
        } else {
            edit.remove(PREF_LAST_FRAGMENT_TAG);
        }
        edit.apply();
    }

    public static String getLastNavFragment(Context context) {
        SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
        return prefs.getString(PREF_LAST_FRAGMENT_TAG, HomeFragment.TAG);
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        if (PREF_LAST_FRAGMENT_TAG.equals(key)) {
            navAdapter.notifyDataSetChanged(); // Update selection
        }
    }
}
