package org.schabi.newpipe.fragments.list.channel;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
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.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;

import com.google.android.material.tabs.TabLayout;

import org.schabi.newpipe.R;
import org.schabi.newpipe.database.subscription.NotificationMode;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.databinding.FragmentChannelBinding;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.detail.TabAdapter;
import org.schabi.newpipe.local.feed.notifications.NotificationHelper;
import org.schabi.newpipe.local.subscription.SubscriptionManager;
import org.schabi.newpipe.util.ChannelTabHelper;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.StateSaver;
import org.schabi.newpipe.util.external_communication.ShareUtils;

import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.Set;

import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.schedulers.Schedulers;

public class ChannelFragment extends BaseStateFragment<ChannelInfo>
        implements StateSaver.WriteRead {
    protected int serviceId = Constants.NO_SERVICE_ID;
    protected String name;
    protected String url;

    protected org.schabi.newpipe.util.SavedState savedState;

    private ChannelInfo currentInfo;
    private Disposable currentWorker;
    private Disposable subscriptionMonitor;
    private final CompositeDisposable disposables = new CompositeDisposable();
    private SubscriptionManager subscriptionManager;
    private int lastTab;

    private MenuItem menuNotifyButton;
    private MenuItem menuSearchButton;

    /*//////////////////////////////////////////////////////////////////////////
    // Views
    //////////////////////////////////////////////////////////////////////////*/

    private FragmentChannelBinding binding;
    private TabAdapter tabAdapter;

    public static ChannelFragment getInstance(final int serviceId, final String url,
                                              final String name) {
        final ChannelFragment instance = new ChannelFragment();
        instance.setInitialData(serviceId, url, name);
        return instance;
    }

    public ChannelFragment() {
        super();
    }

    protected void setInitialData(final int sid, final String u, final String title) {
        this.serviceId = sid;
        this.url = u;
        this.name = !TextUtils.isEmpty(title) ? title : "";
    }

    /*//////////////////////////////////////////////////////////////////////////
    // LifeCycle
    //////////////////////////////////////////////////////////////////////////*/

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

        if (savedInstanceState != null) {
            lastTab = savedInstanceState.getInt("LastTab");
        } else {
            lastTab = 0;
        }
    }

    @Override
    public void onAttach(final @NonNull Context context) {
        super.onAttach(context);
        subscriptionManager = new SubscriptionManager(activity);
    }

    @Override
    public View onCreateView(@NonNull final LayoutInflater inflater,
                             @Nullable final ViewGroup container,
                             @Nullable final Bundle savedInstanceState) {
        binding = FragmentChannelBinding.inflate(inflater, container, false);
        return binding.getRoot();
    }

    @Override // called from onViewCreated in {@link BaseFragment#onViewCreated}
    protected void initViews(final View rootView, final Bundle savedInstanceState) {
        super.initViews(rootView, savedInstanceState);

        tabAdapter = new TabAdapter(getChildFragmentManager());
        binding.viewPager.setAdapter(tabAdapter);
        binding.tabLayout.setupWithViewPager(binding.viewPager);
    }

    @Override
    public void onSaveInstanceState(final @NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("LastTab", binding == null ? lastTab: binding.tabLayout.getSelectedTabPosition());
        outState.putInt("serviceId", serviceId);
        outState.putString("name", name);
        outState.putString("url", url);
        savedState = StateSaver
                .tryToSave(activity.isChangingConfigurations(), savedState, outState, this);
    }

    @Override
    protected void onRestoreInstanceState(@NonNull final Bundle bundle) {
        super.onRestoreInstanceState(bundle);
        serviceId = bundle.getInt("serviceId", Constants.NO_SERVICE_ID);
        name = bundle.getString("name");
        url = bundle.getString("url");
        savedState = StateSaver.tryToRestore(bundle, this);
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        if (currentWorker != null) {
            currentWorker.dispose();
        }
        if (subscriptionMonitor != null) {
            subscriptionMonitor.dispose();
        }
        disposables.clear();
        binding = null;
    }

     /*//////////////////////////////////////////////////////////////////////////
    // Menu
    //////////////////////////////////////////////////////////////////////////*/

    @Override
    public void onCreateOptionsMenu(@NonNull final Menu menu,
                                    @NonNull final MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        inflater.inflate(R.menu.menu_channel, menu);

        if (DEBUG) {
            Log.d(TAG, "onCreateOptionsMenu() called with: "
                    + "menu = [" + menu + "], inflater = [" + inflater + "]");
        }
        menuNotifyButton = menu.findItem(R.id.menu_item_notify);
        menuSearchButton = menu.findItem(R.id.menu_item_search);
        updateSearchButton();
        monitorSubscription();
    }

    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {
        if (item.getItemId() == R.id.menu_item_notify) {
            final boolean value = !item.isChecked();
            item.setEnabled(false);
            setNotify(value);
        } else if (item.getItemId() == R.id.action_settings) {
            NavigationHelper.openSettings(requireContext());
        } else if (item.getItemId() == R.id.menu_item_search) {
            if (currentInfo != null) {
                NavigationHelper.openChannelSearchFragment(getFM(),
                        currentInfo.getServiceId(), currentInfo.getOriginalUrl(), name);
            }
        } else if (item.getItemId() == R.id.menu_item_openInBrowser) {
            if (currentInfo != null) {
                ShareUtils.openUrlInBrowser(requireContext(), currentInfo.getOriginalUrl());
            }
        } else if (item.getItemId() == R.id.menu_item_share) {
            if (currentInfo != null) {
                ShareUtils.shareText(requireContext(), name, currentInfo.getOriginalUrl(),
                        currentInfo.getAvatarUrl());
            }
        } else {
            return super.onOptionsItemSelected(item);
        }
        return true;
    }

    private void updateSearchButton() {
        if (menuSearchButton != null) {
            menuSearchButton.setVisible(currentInfo != null
                    && currentInfo.getServiceId() == ServiceList.YouTube.getServiceId());
        }
    }

    private void monitorSubscription() {
        if (currentInfo != null) {
            final Observable<List<SubscriptionEntity>> observable = subscriptionManager
                    .subscriptionTable()
                    .getSubscriptionFlowable(currentInfo.getServiceId(), currentInfo.getUrl())
                    .toObservable();

            if (subscriptionMonitor != null) {
                subscriptionMonitor.dispose();
            }
            subscriptionMonitor = observable
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(getSubscribeUpdateMonitor());
        }
    }

    private Consumer<List<SubscriptionEntity>> getSubscribeUpdateMonitor() {
        return (List<SubscriptionEntity> subscriptionEntities) -> {
            if (subscriptionEntities.isEmpty()) {
                updateNotifyButton(null);
            } else {
                final SubscriptionEntity subscription = subscriptionEntities.get(0);
                updateNotifyButton(subscription);
            }
        };
    }

    private void updateNotifyButton(@Nullable final SubscriptionEntity subscription) {
        if (menuNotifyButton == null) {
            return;
        }
        if (subscription != null) {
            menuNotifyButton.setEnabled(
                    NotificationHelper.areNewStreamsNotificationsEnabled(requireContext())
            );
            menuNotifyButton.setChecked(
                    subscription.getNotificationMode() == NotificationMode.ENABLED
            );
        }

        menuNotifyButton.setVisible(subscription != null);
    }

    private void setNotify(final boolean isEnabled) {
        disposables.add(
                subscriptionManager
                        .updateNotificationMode(
                                currentInfo.getServiceId(),
                                currentInfo.getUrl(),
                                isEnabled ? NotificationMode.ENABLED : NotificationMode.DISABLED)
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe()
        );
    }

    /*//////////////////////////////////////////////////////////////////////////
    // Init
    //////////////////////////////////////////////////////////////////////////*/

    private boolean isContentUnsupported() {
        for (final Throwable throwable : currentInfo.getErrors()) {
            if (throwable instanceof ContentNotSupportedException) {
                return true;
            }
        }
        return false;
    }

    private void updateTabs() {
        tabAdapter.clearAllItems();

        if (currentInfo != null) {
            if (isContentUnsupported()) {
                showEmptyState();
                binding.errorContentNotSupported.setVisibility(View.VISIBLE);
            } else {
                tabAdapter.addFragment(
                        ChannelVideosFragment.getInstance(currentInfo), "Videos");

                final Context context = getContext();
                final SharedPreferences preferences = PreferenceManager
                        .getDefaultSharedPreferences(context);

                for (final ListLinkHandler linkHandler : currentInfo.getTabs()) {
                    final String tab = linkHandler.getContentFilters().get(0).getName();
                    if (ChannelTabHelper.showChannelTab(context, preferences, tab)) {
                        tabAdapter.addFragment(
                                ChannelTabFragment.getInstance(serviceId, linkHandler, name),
                                context.getString(ChannelTabHelper.getTranslationKey(tab)));
                    }
                }

                final String description = currentInfo.getDescription();
                if (description != null && !description.isEmpty()
                        && ChannelTabHelper.showChannelTab(
                        context, preferences, R.string.show_channel_tabs_info)) {
                    tabAdapter.addFragment(
                            ChannelInfoFragment.getInstance(currentInfo), "Info");
                }
            }
        }

        tabAdapter.notifyDataSetUpdate();

        for (int i = 0; i < tabAdapter.getCount(); i++) {
            binding.tabLayout.getTabAt(i).setText(tabAdapter.getItemTitle(i));
        }

        binding.tabLayout.setVisibility(tabAdapter.getCount() > 1 ? View.VISIBLE : View.GONE);

        // Restore previously selected tab
        final TabLayout.Tab ltab = binding.tabLayout.getTabAt(lastTab);
        if (ltab != null) {
            binding.tabLayout.selectTab(ltab);
        }
    }

    /*//////////////////////////////////////////////////////////////////////////
    // State Saving
    //////////////////////////////////////////////////////////////////////////*/

    @Override
    public String generateSuffix() {
        return null;
    }

    @Override
    public void writeTo(final Queue<Object> objectsToSave) {
        objectsToSave.add(currentInfo);
        if (binding != null) {
            objectsToSave.add(binding.tabLayout.getSelectedTabPosition());
        } else {
            objectsToSave.add(0);
        }
    }

    @Override
    public void readFrom(@NonNull final Queue<Object> savedObjects) {
        currentInfo = (ChannelInfo) savedObjects.poll();
        lastTab = (Integer) savedObjects.poll();
    }

    @Override
    protected void doInitialLoadLogic() {
        if (currentInfo == null) {
            startLoading(false);
        } else {
            handleResult(currentInfo);
        }
    }

    @Override
    public void startLoading(final boolean forceLoad) {
        super.startLoading(forceLoad);

        currentInfo = null;
        updateTabs();
        if (currentWorker != null) {
            currentWorker.dispose();
        }

        runWorker(forceLoad);
    }

    private void runWorker(final boolean forceLoad) {
        currentWorker = ExtractorHelper.getChannelInfo(serviceId, url, forceLoad)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(result -> {
                    isLoading.set(false);
                    handleResult(result);
                }, throwable -> showError(new ErrorInfo(throwable, UserAction.REQUESTED_STREAM,
                        url == null ? "no url" : url, serviceId)));
    }

    @Override
    public void handleResult(@NonNull final ChannelInfo result) {
        super.handleResult(result);

        // Safety net: if the channel is in the block list, prevent access
        final String channelName = result.getName();
        if (channelName != null && !channelName.isEmpty()) {
            final SharedPreferences prefs = PreferenceManager
                    .getDefaultSharedPreferences(requireContext());
            final Set<String> blockedChannels = prefs.getStringSet(
                    getString(R.string.filter_by_channel_key) + "_set",
                    new HashSet<>());
            if (blockedChannels.contains(channelName)) {
                Toast.makeText(requireContext(),
                        R.string.channel_is_blocked,
                        Toast.LENGTH_SHORT).show();
                if (getParentFragmentManager().getBackStackEntryCount() > 0) {
                    getParentFragmentManager().popBackStack();
                }
                return;
            }
        }

        currentInfo = result;
        setInitialData(result.getServiceId(), result.getOriginalUrl(), result.getName());

        updateTabs();
        updateSearchButton();
        monitorSubscription();
    }
}
