package ch.threema.app.activities.notificationpolicy;

import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.text.format.DateUtils;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.Button;
import android.widget.RadioButton;
import android.widget.ScrollView;
import android.widget.TextView;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.core.app.NotificationChannelCompat;
import androidx.core.app.NotificationManagerCompat;

import com.google.android.material.card.MaterialCardView;
import com.google.android.material.materialswitch.MaterialSwitch;

import org.koin.java.KoinJavaComponent;
import org.slf4j.Logger;

import java.time.Instant;

import androidx.work.OneTimeWorkRequest;
import androidx.work.OutOfQuotaPolicy;
import androidx.work.WorkManager;
import ch.threema.app.AppConstants;
import ch.threema.app.R;
import ch.threema.app.activities.ThreemaActivity;
import ch.threema.app.di.DependencyContainer;
import ch.threema.app.dialogs.RingtoneSelectorDialog;
import ch.threema.app.dialogs.ShowOnceDialog;
import ch.threema.app.managers.ListenerManager;
import ch.threema.app.notifications.NotificationChannels;
import ch.threema.app.preference.SettingsActivity;
import ch.threema.app.services.ServicesConstants;
import ch.threema.app.ui.InsetSides;
import ch.threema.app.ui.ViewExtensionsKt;
import ch.threema.app.utils.AnimationUtil;
import ch.threema.app.utils.ConfigUtils;
import ch.threema.app.utils.RingtoneUtil;
import ch.threema.app.workers.ShareTargetUpdateWorker;
import ch.threema.data.datatypes.ContactNotificationTriggerPolicyOverride;
import ch.threema.data.datatypes.ContactNotificationTriggerPolicyOverridePolicy;
import ch.threema.data.datatypes.ConversationId;
import ch.threema.data.datatypes.GroupNotificationTriggerPolicyOverride;
import ch.threema.data.datatypes.GroupNotificationTriggerPolicyOverridePolicy;

import static ch.threema.base.utils.LoggingKt.getThreemaLogger;

import static ch.threema.app.di.DIJavaCompat.isSessionScopeReady;
import static ch.threema.common.JavaCompat.isNullOrEmpty;

public abstract class NotificationsActivity extends ThreemaActivity implements ShowOnceDialog.ShowOnceDialogClickListener, RingtoneSelectorDialog.RingtoneSelectorDialogClickListener {

    private static final Logger logger = getThreemaLogger("NotificationsActivity");

    private static final String BUNDLE_ANIMATION_CENTER = "animC";
    private static final String DIALOG_TAG_RINGTONE_SELECTOR = "drs";
    private static final String DIALOG_TAG_INDIVIDUAL_CONFIRM = "individual_confirm";
    protected static final int MUTE_INDEX_INDEFINITE = -1;

    @NonNull
    private final DependencyContainer dependencies = KoinJavaComponent.get(DependencyContainer.class);

    protected TextView textSoundCustom, textSoundDefault;
    protected RadioButton
        radioSoundDefault,
        radioSilentOff,
        radioSilentUnlimited,
        radioSilentLimited,
        radioSilentExceptMentions,
        radioSoundCustom,
        radioSoundNone;
    private MaterialCardView plusButton, minusButton;
    private ScrollView parentLayout;
    protected MaterialSwitch notificationSettingsSwitch;
    protected TextView individualSettingsText;
    protected Uri defaultRingtone, selectedRingtone, backupSoundCustom;
    protected int mutedTimerIndex;
    protected static final int[] muteTimerValuesInHours = {1, 2, 4, 8, 24, 144};
    private int[] animCenterLocation = {0, 0};
    protected ConversationId conversationId;
    protected String chatName;

    private final ActivityResultLauncher<Intent> ringtonePickerLauncher = registerForActivityResult(
        new ActivityResultContracts.StartActivityForResult(),
        result -> {
            if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
                final @Nullable Uri uri = result.getData().getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
                if (uri != null) {
                    onRingtoneSelected(DIALOG_TAG_RINGTONE_SELECTOR, uri);
                }
            }
        }
    );

    private final ActivityResultLauncher<Intent> ringtoneSettingsLauncher = registerForActivityResult(
        new ActivityResultContracts.StartActivityForResult(),
        result -> refreshSettings()
    );

    private final ActivityResultLauncher<Intent> channelSettingsLauncher = registerForActivityResult(
        new ActivityResultContracts.StartActivityForResult(),
        result -> {
            refreshSettings();
            WorkManager.getInstance(NotificationsActivity.this).enqueue(
                new OneTimeWorkRequest.Builder(ShareTargetUpdateWorker.class)
                    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
                    .build()
            );
        }
    );

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (!isSessionScopeReady()) {
            finish();
            return;
        }

        chatName = getIntent().getStringExtra(AppConstants.INTENT_DATA_TEXT);

        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_notifications);

        parentLayout = findViewById(R.id.parent_layout);

        ViewExtensionsKt.applyDeviceInsetsAsPadding(
            parentLayout,
            InsetSides.all()
        );

        setClickListeners();
        ViewGroup topLayout = (ViewGroup) parentLayout.getParent();
        plusButton = findViewById(R.id.duration_plus);
        minusButton = findViewById(R.id.duration_minus);
        Button doneButton = findViewById(R.id.done_button);

        if (ConfigUtils.isWorkBuild()) {
            findViewById(R.id.work_life_warning).setVisibility(dependencies.getPreferenceService().isAfterWorkDNDEnabled() ? View.VISIBLE : View.GONE);
        }

        if (ConfigUtils.supportsNotificationChannels()) {
            findViewById(R.id.individual_settings_container).setVisibility(View.VISIBLE);
            findViewById(R.id.notification_sound_container).setVisibility(View.GONE);
            notificationSettingsSwitch = findViewById(R.id.individual_settings_switch);
            individualSettingsText = findViewById(R.id.individual_settings_text);
            individualSettingsText.setOnClickListener(v -> {
                if (notificationSettingsSwitch.isChecked()) {
                    showIndividualSettings(chatName);
                }
            });

        } else {
            findViewById(R.id.individual_settings_container).setVisibility(View.GONE);
            findViewById(R.id.notification_sound_container).setVisibility(View.VISIBLE);
        }

        if (savedInstanceState == null) {
            Intent intent = getIntent();
            animCenterLocation = intent.getIntArrayExtra(AppConstants.INTENT_DATA_ANIM_CENTER);

            if (animCenterLocation != null) {
                // see http://stackoverflow.com/questions/26819429/cannot-start-this-animator-on-a-detached-view-reveal-effect
                parentLayout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
                    @Override
                    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                        v.removeOnLayoutChangeListener(this);
                        parentLayout.setVisibility(View.INVISIBLE);
                        AnimationUtil.circularReveal(parentLayout, animCenterLocation[0], animCenterLocation[1], false);
                    }
                });
            } else {
                parentLayout.setVisibility(View.VISIBLE);
            }
        } else {
            animCenterLocation = savedInstanceState.getIntArray(BUNDLE_ANIMATION_CENTER);
        }

        parentLayout.setOnClickListener(v -> {
            // ignore clicks
        });

        topLayout.setOnClickListener(v -> onDone());

        doneButton.setOnClickListener(v -> onDone());

        setupButtons();
    }

    private void showIndividualSettings(String chatName) {
        if (!ConfigUtils.supportsNotificationChannels()) {
            return;
        }
        final boolean isGroupChat = this instanceof GroupNotificationsActivity;
        final @NonNull Intent intent = NotificationChannels.getPerConversationChannelSettingsIntent(this, chatName, conversationId, isGroupChat);
        try {
            channelSettingsLauncher.launch(intent);
        } catch (ActivityNotFoundException e) {
            logger.debug("No settings activity found");
        }
    }

    private void deleteIndividualSettings() {
        NotificationChannels.deletePerConversationChannel(this, conversationId);
    }

    @Override
    public void onDestroy() {
        if (conversationId != null) {
            ListenerManager.contactSettingsListeners.handle(
                listener -> listener.onNotificationSettingChanged(conversationId)
            );
        }
        super.onDestroy();
    }

    private int findNextHigherMuteIndex(double hours) {
        for (int i = muteTimerValuesInHours.length - 1; i >= 0; i--) {
            if (muteTimerValuesInHours[i] < hours) {
                return i + 1;
            }
        }
        return 0;
    }

    protected void refreshSettings() {
        if (isMutedRightNow() && !isMutedExceptMentions()) {
            final @Nullable Instant muteOverrideUntilValue = getExpiresAt();
            if (muteOverrideUntilValue != null) {
                double hours = ((double) muteOverrideUntilValue.toEpochMilli() - System.currentTimeMillis()) / DateUtils.HOUR_IN_MILLIS;
                mutedTimerIndex = findNextHigherMuteIndex(hours);
            } else {
                mutedTimerIndex = MUTE_INDEX_INDEFINITE;
            }
        }
        updateUI();
    }

    protected void enablePlusMinus(boolean enable) {
        plusButton.setEnabled(enable);
        plusButton.setAlpha(enable ? 1.0f : 0.2f);
        minusButton.setEnabled(enable);
        minusButton.setAlpha(enable ? 1.0f : 0.2f);
    }

    @UiThread
    protected void updateUI() {
        if (backupSoundCustom != null && !isNullOrEmpty(RingtoneUtil.getRingtoneNameFromUri(this, backupSoundCustom))) {
            textSoundCustom.setText(RingtoneUtil.getRingtoneNameFromUri(this, backupSoundCustom));
        } else {
            textSoundCustom.setText("");
        }
        textSoundDefault.setText(RingtoneUtil.getRingtoneNameFromUri(this, defaultRingtone));

        enablePlusMinus(false);

        // DND
        if (isMutedRightNow()) {
            if (!isMutedExceptMentions()) {
                final @Nullable Instant muteOverrideUntilValue = getExpiresAt();

                String deadlineString = "";
                if (muteOverrideUntilValue != null) {
                    deadlineString = "\n" + String.format(
                        getString(R.string.notifications_until),
                        DateUtils.formatDateTime(this, muteOverrideUntilValue.toEpochMilli(),
                            mutedTimerIndex < 4 ?
                                DateUtils.FORMAT_SHOW_TIME :
                                DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME
                        )
                    );
                }

                String mutedForText;
                if (mutedTimerIndex >= 0) {
                    enablePlusMinus(true);
                    radioSilentLimited.setChecked(true);
                    if (mutedTimerIndex >= 5) {
                        mutedForText = getString(R.string.notifications_for_one_week);
                    } else {
                        int hours = muteTimerValuesInHours[mutedTimerIndex];
                        mutedForText = getResources().getQuantityString(R.plurals.notifications_for_x_hours, hours, hours);
                    }
                } else {
                    radioSilentUnlimited.setChecked(true);
                    int hours = muteTimerValuesInHours[0];
                    mutedForText = getResources().getQuantityString(R.plurals.notifications_for_x_hours, hours, hours);
                }
                radioSilentLimited.setText(mutedForText + deadlineString);
            } else if (isMutedExceptMentions()) {
                // mentions only
                radioSilentExceptMentions.setChecked(true);
            }
        } else {
            radioSilentOff.setChecked(true);
        }

        // SOUND
        if (dependencies.getRingtoneService().hasCustomRingtone(conversationId)) {
            if (selectedRingtone == null || selectedRingtone.toString().equals(ServicesConstants.PREFERENCES_NULL)) {
                // silent ringtone selected
                radioSoundNone.setChecked(true);
                textSoundCustom.setEnabled(true);
            } else {
                if (selectedRingtone.equals(defaultRingtone)) {
                    radioSoundDefault.setChecked(true);
                } else {
                    radioSoundCustom.setChecked(true);
                    textSoundCustom.setEnabled(true);
                    textSoundCustom.setText(RingtoneUtil.getRingtoneNameFromUri(this, selectedRingtone));
                }
            }
        } else {
            // default settings
            radioSoundDefault.setChecked(true);
        }

        if (ConfigUtils.supportsNotificationChannels()) {
            final @NonNull String channelId = conversationId.getObfuscated().value;
            NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(this);
            NotificationChannelCompat notificationChannelCompat = notificationManagerCompat.getNotificationChannelCompat(channelId);

            notificationSettingsSwitch.setOnCheckedChangeListener(null);
            notificationSettingsSwitch.setChecked(notificationChannelCompat != null);
            individualSettingsText.setEnabled(notificationSettingsSwitch.isChecked());
            notificationSettingsSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
                if (isChecked) {
                    individualSettingsText.setEnabled(true);
                    if (dependencies.getPreferenceService().isOneTimeDialogShown(DIALOG_TAG_INDIVIDUAL_CONFIRM)) {
                        onYes(DIALOG_TAG_INDIVIDUAL_CONFIRM);
                    } else {
                        ShowOnceDialog showOnceDialog = ShowOnceDialog.newInstance(
                            R.string.individual_notification_settings,
                            R.string.individual_notification_settings_warn
                        );
                        showOnceDialog.show(getSupportFragmentManager(), DIALOG_TAG_INDIVIDUAL_CONFIRM);
                    }
                } else {
                    individualSettingsText.setEnabled(false);
                    deleteIndividualSettings();
                }
            });
        }
    }

    public void setClickListeners() {
        parentLayout.findViewById(R.id.radio_sound_default).setOnClickListener(v -> {
            dependencies.getRingtoneService().removeCustomRingtone(this.conversationId);
            refreshSettings();
        });

        parentLayout.findViewById(R.id.radio_sound_custom).setOnClickListener(v -> {
            pickRingtone(this.conversationId);
            refreshSettings();
        });

        parentLayout.findViewById(R.id.text_sound).setOnClickListener(v -> {
            pickRingtone(this.conversationId);
            refreshSettings();
        });

        parentLayout.findViewById(R.id.radio_sound_none).setOnClickListener(v -> {
            dependencies.getRingtoneService().setRingtone(this.conversationId, null);
            refreshSettings();
        });

        parentLayout.findViewById(R.id.radio_silent_off).setOnClickListener(v -> {
            onContactNotificationTriggerPolicyOverrideChanged(null);
            onGroupNotificationTriggerPolicyOverrideChanged(null);
            refreshSettings();
        });

        parentLayout.findViewById(R.id.radio_silent_unlimited).setOnClickListener(v -> {
            onContactNotificationTriggerPolicyOverrideChanged(
                new ContactNotificationTriggerPolicyOverride(
                    ContactNotificationTriggerPolicyOverridePolicy.NEVER,
                    null
                )
            );
            onGroupNotificationTriggerPolicyOverrideChanged(
                new GroupNotificationTriggerPolicyOverride(
                    GroupNotificationTriggerPolicyOverridePolicy.NEVER,
                    null
                )
            );
            refreshSettings();
        });

        parentLayout.findViewById(R.id.radio_silent_limited).setOnClickListener(v -> {
            if (mutedTimerIndex < 0) {
                mutedTimerIndex = 0;
            }
            final long expiresAt = muteTimerValuesInHours[mutedTimerIndex] * DateUtils.HOUR_IN_MILLIS + System.currentTimeMillis();
            onContactNotificationTriggerPolicyOverrideChanged(
                new ContactNotificationTriggerPolicyOverride(
                    ContactNotificationTriggerPolicyOverridePolicy.NEVER,
                    Instant.ofEpochMilli(expiresAt)
                )
            );
            onGroupNotificationTriggerPolicyOverrideChanged(
                new GroupNotificationTriggerPolicyOverride(
                    GroupNotificationTriggerPolicyOverridePolicy.NEVER,
                    Instant.ofEpochMilli(expiresAt)
                )
            );
            refreshSettings();
        });

        parentLayout.findViewById(R.id.radio_silent_except_mentions).setOnClickListener(v -> {
            onGroupNotificationTriggerPolicyOverrideChanged(
                new GroupNotificationTriggerPolicyOverride(
                    GroupNotificationTriggerPolicyOverridePolicy.MENTIONED,
                    null
                )
            );
            refreshSettings();
        });

        parentLayout.findViewById(R.id.duration_plus).setOnClickListener(v -> {
            mutedTimerIndex = Math.min(mutedTimerIndex + 1, muteTimerValuesInHours.length - 1);
            final long expiresAt = muteTimerValuesInHours[mutedTimerIndex] * DateUtils.HOUR_IN_MILLIS + System.currentTimeMillis();
            onContactNotificationTriggerPolicyOverrideChanged(
                new ContactNotificationTriggerPolicyOverride(
                    ContactNotificationTriggerPolicyOverridePolicy.NEVER,
                    Instant.ofEpochMilli(expiresAt)
                )
            );
            onGroupNotificationTriggerPolicyOverrideChanged(
                new GroupNotificationTriggerPolicyOverride(
                    GroupNotificationTriggerPolicyOverridePolicy.NEVER,
                    Instant.ofEpochMilli(expiresAt)
                )
            );
            refreshSettings();
        });

        parentLayout.findViewById(R.id.duration_minus).setOnClickListener(v -> {
            mutedTimerIndex = Math.max(mutedTimerIndex - 1, 0);
            final long expiresAt = muteTimerValuesInHours[mutedTimerIndex] * DateUtils.HOUR_IN_MILLIS + System.currentTimeMillis();
            onContactNotificationTriggerPolicyOverrideChanged(
                new ContactNotificationTriggerPolicyOverride(
                    ContactNotificationTriggerPolicyOverridePolicy.NEVER,
                    Instant.ofEpochMilli(expiresAt)
                )
            );
            onGroupNotificationTriggerPolicyOverrideChanged(
                new GroupNotificationTriggerPolicyOverride(
                    GroupNotificationTriggerPolicyOverridePolicy.NEVER,
                    Instant.ofEpochMilli(expiresAt)
                )
            );
            refreshSettings();
        });

        parentLayout.findViewById(R.id.prefs_button).setOnClickListener(v -> {
            Intent intent = SettingsActivity.createIntent(this, SettingsActivity.InitialScreen.NOTIFICATIONS);
            ringtoneSettingsLauncher.launch(intent);
            refreshSettings();
        });
    }

    protected void setupButtons() {
        radioSoundDefault = this.findViewById(R.id.radio_sound_default);
        textSoundDefault = this.findViewById(R.id.text_sound_default);
        radioSoundCustom = this.findViewById(R.id.radio_sound_custom);
        radioSoundNone = this.findViewById(R.id.radio_sound_none);
        textSoundCustom = this.findViewById(R.id.text_sound);
        radioSilentOff = this.findViewById(R.id.radio_silent_off);
        radioSilentUnlimited = this.findViewById(R.id.radio_silent_unlimited);
        radioSilentLimited = this.findViewById(R.id.radio_silent_limited);
        radioSilentLimited = this.findViewById(R.id.radio_silent_limited);
        int hours = muteTimerValuesInHours[0];
        radioSilentLimited.setText(getResources().getQuantityString(R.plurals.notifications_for_x_hours, hours, hours));
        radioSilentExceptMentions = this.findViewById(R.id.radio_silent_except_mentions);
    }

    protected void pickRingtone(@NonNull ConversationId conversationId) {
        Uri existingUri = dependencies.getRingtoneService().getRingtoneByConversationId(conversationId);
        if (existingUri != null && existingUri.getPath() != null && existingUri.getPath().equals(ServicesConstants.PREFERENCES_NULL)) {
            existingUri = null;
        }
        if (existingUri == null && backupSoundCustom != null) {
            existingUri = backupSoundCustom;
        }

        Uri defaultUri = dependencies.getRingtoneService().getDefaultContactRingtone();

        try {
            Intent intent = RingtoneUtil.getRingtonePickerIntent(RingtoneManager.TYPE_NOTIFICATION, existingUri == null ? defaultUri : existingUri, defaultUri);
            ringtonePickerLauncher.launch(intent);
        } catch (ActivityNotFoundException e) {
            RingtoneSelectorDialog.newInstance(getString(R.string.prefs_notification_sound),
                RingtoneManager.TYPE_NOTIFICATION,
                existingUri,
                defaultUri,
                true,
                true).show(getSupportFragmentManager(), DIALOG_TAG_RINGTONE_SELECTOR);
        }
    }

    protected void onDone() {
        AnimationUtil.circularObscure(parentLayout, animCenterLocation[0], animCenterLocation[1], false, this::finish);
    }


    @Override
    protected boolean enableOnBackPressedCallback() {
        return true;
    }

    @Override
    protected void handleOnBackPressed() {
        onDone();
    }

    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putIntArray(BUNDLE_ANIMATION_CENTER, this.animCenterLocation);
    }

    @Override
    public void onRingtoneSelected(String tag, @NonNull Uri ringtone) {
        dependencies.getRingtoneService().setRingtone(conversationId, ringtone);
        backupSoundCustom = ringtone;
        refreshSettings();
    }

    @Override
    public void finish() {
        // used to avoid flickering of status and navigation bar when activity is closed
        super.finish();
        overridePendingTransition(R.anim.fast_fade_in, R.anim.fast_fade_out);
    }

    @Override
    public void onYes(String tag) {
        if (DIALOG_TAG_INDIVIDUAL_CONFIRM.equals(tag)) {
            individualSettingsText.setEnabled(true);
            showIndividualSettings(chatName);
        }
    }

    @Override
    public void onCancel(String tag) {
        if (DIALOG_TAG_INDIVIDUAL_CONFIRM.equals(tag)) {
            notificationSettingsSwitch.setChecked(false);
            individualSettingsText.setEnabled(false);
        }
    }

    protected abstract void onContactNotificationTriggerPolicyOverrideChanged(
        @Nullable final ContactNotificationTriggerPolicyOverride notificationTriggerPolicyOverride
    );

    protected abstract void onGroupNotificationTriggerPolicyOverrideChanged(
        @Nullable final GroupNotificationTriggerPolicyOverride notificationTriggerPolicyOverride
    );

    // Also respects current system time
    protected abstract boolean isMutedRightNow();

    protected abstract boolean isMutedExceptMentions();

    @Nullable
    protected abstract Instant getExpiresAt();
}
