/*  Copyright (C) 2024 Arjan Schrijver

    This file is part of Gadgetbridge.

    Gadgetbridge is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Gadgetbridge is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.util;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.NotificationManager;
import android.companion.CompanionDeviceManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;
import android.widget.Toast;

import androidx.annotation.RequiresApi;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;

import com.google.android.material.dialog.MaterialAlertDialogBuilder;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;

public class PermissionsUtils {
    private static final Logger LOG = LoggerFactory.getLogger(PermissionsUtils.class);

    public static final String CUSTOM_PERM_IGNORE_BATT_OPTIM = "custom_perm_ignore_battery_optimization";
    public static final String CUSTOM_PERM_NOTIFICATION_LISTENER = "custom_perm_notifications_listener";
    public static final String CUSTOM_PERM_NOTIFICATION_SERVICE = "custom_perm_notifications_service";
    public static final String CUSTOM_PERM_DISPLAY_OVER = "custom_perm_display_over";
    public static final String CUSTOM_PERM_INTERNET_HELPER = "nodomain.freeyourgadget.internethelper.INTERNET";
    public static final String PACKAGE_INTERNET_HELPER = "nodomain.freeyourgadget.internethelper";

    public static final List<String> specialPermissions = new ArrayList<>() {{
        add(CUSTOM_PERM_IGNORE_BATT_OPTIM);
        add(CUSTOM_PERM_NOTIFICATION_LISTENER);
        add(CUSTOM_PERM_NOTIFICATION_SERVICE);
        add(CUSTOM_PERM_DISPLAY_OVER);
        add(Manifest.permission.ACCESS_FINE_LOCATION);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
        }
    }};

    public static ArrayList<PermissionDetails> getRequiredPermissionsList(Activity activity) {
        ArrayList<PermissionDetails> permissionsList = new ArrayList<>();
        int companionDevicesCount = 0;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            final CompanionDeviceManager manager = (CompanionDeviceManager) GBApplication.getContext().getSystemService(Context.COMPANION_DEVICE_SERVICE);
            companionDevicesCount = manager.getAssociations().size();
        }
        if (companionDevicesCount == 0) {
            permissionsList.add(new PermissionDetails(
                    CUSTOM_PERM_IGNORE_BATT_OPTIM,
                    activity.getString(R.string.permission_disable_doze_title),
                    activity.getString(R.string.permission_disable_doze_summary)
            ));
        } else {
            LOG.info("Not requesting explicit battery optimization exemption due to paired Companion devices");
        }
        permissionsList.add(new PermissionDetails(
                CUSTOM_PERM_NOTIFICATION_LISTENER,
                activity.getString(R.string.menuitem_notifications),
                activity.getString(R.string.permission_notifications_summary)));
        permissionsList.add(new PermissionDetails(
                CUSTOM_PERM_NOTIFICATION_SERVICE,
                activity.getString(R.string.permission_manage_dnd_title),
                activity.getString(R.string.permission_manage_dnd_summary)));
        permissionsList.add(new PermissionDetails(
                CUSTOM_PERM_DISPLAY_OVER,
                activity.getString(R.string.permission_displayover_title),
                activity.getString(R.string.permission_displayover_summary)));
        permissionsList.add(new PermissionDetails(
                Manifest.permission.ACCESS_FINE_LOCATION,
                activity.getString(R.string.permission_fine_location_title),
                activity.getString(R.string.permission_fine_location_summary)));
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            permissionsList.add(new PermissionDetails(
                    Manifest.permission.ACCESS_BACKGROUND_LOCATION,
                    activity.getString(R.string.permission_background_location_title),
                    activity.getString(R.string.permission_background_location_summary)));
        }
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
            permissionsList.add(new PermissionDetails(
                    Manifest.permission.BLUETOOTH,
                    activity.getString(R.string.permission_bluetooth_title),
                    activity.getString(R.string.permission_bluetooth_summary)));
            permissionsList.add(new PermissionDetails(
                    Manifest.permission.BLUETOOTH_ADMIN,
                    activity.getString(R.string.permission_bluetooth_admin_title),
                    activity.getString(R.string.permission_bluetooth_admin_summary)));
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            permissionsList.add(new PermissionDetails(
                    Manifest.permission.BLUETOOTH_SCAN,
                    activity.getString(R.string.permission_bluetooth_scan_title),
                    activity.getString(R.string.permission_bluetooth_scan_summary)));
            permissionsList.add(new PermissionDetails(
                    Manifest.permission.BLUETOOTH_CONNECT,
                    activity.getString(R.string.permission_bluetooth_connect_title),
                    activity.getString(R.string.permission_bluetooth_connect_summary)));
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            permissionsList.add(new PermissionDetails(
                    Manifest.permission.POST_NOTIFICATIONS,
                    activity.getString(R.string.permission_post_notification_title),
                    activity.getString(R.string.permission_post_notification_summary)));
        }
        if (isPermissionDeclared(activity, Manifest.permission.INTERNET)) {
            permissionsList.add(new PermissionDetails(
                    Manifest.permission.INTERNET,
                    activity.getString(R.string.permission_internet_access_title),
                    activity.getString(R.string.permission_internet_access_summary)));
        }
        if (!GBApplication.hasDirectInternetAccess() && AndroidUtils.isPackageInstalled(PACKAGE_INTERNET_HELPER)) {
            permissionsList.add(new PermissionDetails(
                    CUSTOM_PERM_INTERNET_HELPER,
                    activity.getString(R.string.internet_helper_permission_title),
                    activity.getString(R.string.internet_helper_permission_summary)
            ));
        }
//        permissionsList.add(new PermissionDetails(  // NOTE: can't request this, it's only allowed for system apps
//                Manifest.permission.MEDIA_CONTENT_CONTROL,
//                "Media content control",
//                "Read and control media playback"));
        permissionsList.add(new PermissionDetails(
                Manifest.permission.READ_CONTACTS,
                activity.getString(R.string.permission_contacts_title),
                activity.getString(R.string.permission_contacts_summary)));
        permissionsList.add(new PermissionDetails(
                Manifest.permission.READ_CALENDAR,
                activity.getString(R.string.permission_calendar_title),
                activity.getString(R.string.permission_calendar_summary)));
        permissionsList.add(new PermissionDetails(
                Manifest.permission.RECEIVE_SMS,
                activity.getString(R.string.permission_receive_sms_title),
                activity.getString(R.string.permission_receive_sms_summary)));
        permissionsList.add(new PermissionDetails(
                Manifest.permission.SEND_SMS,
                activity.getString(R.string.permission_send_sms_title),
                activity.getString(R.string.permission_send_sms_summary)));
        permissionsList.add(new PermissionDetails(
                Manifest.permission.READ_CALL_LOG,
                activity.getString(R.string.permission_read_call_log_title),
                activity.getString(R.string.permission_read_call_log_summary)));
        permissionsList.add(new PermissionDetails(
                Manifest.permission.READ_PHONE_STATE,
                activity.getString(R.string.permission_read_phone_state_title),
                activity.getString(R.string.permission_read_phone_state_summary)));
        permissionsList.add(new PermissionDetails(
                Manifest.permission.CALL_PHONE,
                activity.getString(R.string.permission_call_phone_title),
                activity.getString(R.string.permission_call_phone_summary)));
        permissionsList.add(new PermissionDetails(
                Manifest.permission.PROCESS_OUTGOING_CALLS,
                activity.getString(R.string.permission_process_outgoing_calls_title),
                activity.getString(R.string.permission_process_outgoing_calls_summary)));
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            permissionsList.add(new PermissionDetails(
                    Manifest.permission.ANSWER_PHONE_CALLS,
                    activity.getString(R.string.permission_answer_phone_calls_title),
                    activity.getString(R.string.permission_answer_phone_calls_summary)));
        }
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
            permissionsList.add(new PermissionDetails(
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    activity.getString(R.string.permission_external_storage_title),
                    activity.getString(R.string.permission_external_storage_summary)));
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            permissionsList.add(new PermissionDetails(
                    Manifest.permission.QUERY_ALL_PACKAGES,
                    activity.getString(R.string.permission_query_all_packages_title),
                    activity.getString(R.string.permission_query_all_packages_summary)));
        }
        return permissionsList;
    }

    public static boolean checkPermission(Context context, String permission) {
        switch (permission) {
            case CUSTOM_PERM_NOTIFICATION_LISTENER -> {
                Set<String> set = NotificationManagerCompat.getEnabledListenerPackages(context);
                return set.contains(context.getPackageName());
            }
            case CUSTOM_PERM_NOTIFICATION_SERVICE -> {
                return ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)).isNotificationPolicyAccessGranted();
            }
            case CUSTOM_PERM_DISPLAY_OVER -> {
                return Settings.canDrawOverlays(context);
            }
            case CUSTOM_PERM_IGNORE_BATT_OPTIM -> {
                PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
                return pm.isIgnoringBatteryOptimizations(context.getApplicationContext().getPackageName());
            }
            default -> {
                return ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_DENIED;
            }
        }
    }

    public static boolean checkAllPermissions(Activity activity) {
        boolean result = true;
        for (PermissionDetails permission : getRequiredPermissionsList(activity)) {
            if (!checkPermission(activity, permission.permission())) {
                result = false;
            }
        }
        return result;
    }

    public static void requestPermission(Activity activity, String permission) {
        if (permission.equals(CUSTOM_PERM_IGNORE_BATT_OPTIM)) {
            showRequestIgnoreBatteryOptimizationDialog(activity);
        } else if (permission.equals(CUSTOM_PERM_NOTIFICATION_LISTENER)) {
            showNotifyListenerPermissionsDialog(activity);
        } else if (permission.equals(CUSTOM_PERM_NOTIFICATION_SERVICE)) {
            showNotifyPolicyPermissionsDialog(activity);
        } else if (permission.equals(CUSTOM_PERM_DISPLAY_OVER)) {
            showDisplayOverOthersPermissionsDialog(activity);
        } else if (permission.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION) && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)) {
            showBackgroundLocationPermissionsDialog(activity);
        } else {
            ActivityCompat.requestPermissions(activity, new String[]{permission}, 0);
        }
    }

    public record PermissionDetails(String permission,
                                    String title,
                                    String summary) {
    }

    public static boolean isPermissionDeclared(Context context, String permission) {
        // Checks whether a permission has been declared in the (merged) manifest file.
        // This also includes permissions declared by dependencies.
        try {
            PackageManager pm = context.getPackageManager();
            PackageInfo info = pm.getPackageInfo(
                    context.getPackageName(),
                    PackageManager.GET_PERMISSIONS
            );

            String[] requestedPermissions = info.requestedPermissions;
            if (requestedPermissions != null) {
                for (String p : requestedPermissions) {
                    if (p.equals(permission)) {
                        return true;
                    }
                }
            }
        } catch (PackageManager.NameNotFoundException e) {
            // Do nothing
        }
        return false;
    }

    @SuppressLint("BatteryLife")
    private static void showRequestIgnoreBatteryOptimizationDialog(Activity activity) {
        Intent intent = new Intent();
        intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
        intent.setData(Uri.parse("package:" + activity.getApplicationContext().getPackageName()));
        activity.startActivity(intent);
    }

    private static void showNotifyListenerPermissionsDialog(Activity activity) {
        new MaterialAlertDialogBuilder(activity)
                .setMessage(activity.getString(R.string.permission_notification_listener,
                        activity.getString(R.string.app_name),
                        activity.getString(R.string.ok)))
                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int id) {
                        try {
                            Intent intent;
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                                intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS);
                                intent.putExtra(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME, new ComponentName(BuildConfig.APPLICATION_ID, NotificationListener.class.getName()).flattenToString());
                            } else {
                                intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
                            }
                            String showArgs = BuildConfig.APPLICATION_ID + "/" + NotificationListener.class.getName();
                            intent.putExtra(":settings:fragment_args_key", showArgs);
                            Bundle bundle = new Bundle();
                            bundle.putString(":settings:fragment_args_key", showArgs);
                            intent.putExtra(":settings:show_fragment_args", bundle);
                            activity.startActivity(intent);
                        } catch (ActivityNotFoundException e) {
                            GB.toast(activity, "'Notification Listener Settings' activity not found", Toast.LENGTH_LONG, GB.ERROR, e);
                            LOG.error("'Notification Listener Settings' activity not found");
                        }
                    }
                })
                .show();
    }

    private static void showNotifyPolicyPermissionsDialog(Activity activity) {
        new MaterialAlertDialogBuilder(activity)
                .setMessage(activity.getString(R.string.permission_notification_policy_access,
                        activity.getString(R.string.app_name),
                        activity.getString(R.string.ok)))
                .setPositiveButton(R.string.ok, (dialog, id) -> {
                    try {
                        activity.startActivity(new Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS));
                    } catch (ActivityNotFoundException e) {
                        GB.toast(activity, "'Notification Policy' activity not found", Toast.LENGTH_LONG, GB.ERROR, e);
                        LOG.error("'Notification Policy' activity not found");
                    }
                })
                .show();
    }

    private static void showDisplayOverOthersPermissionsDialog(Activity activity) {
        new MaterialAlertDialogBuilder(activity)
                .setMessage(activity.getString(R.string.permission_display_over_other_apps,
                        activity.getString(R.string.app_name),
                        activity.getString(R.string.ok)))
                .setPositiveButton(R.string.ok, (dialog, id) -> {
                    Intent enableIntent = new Intent(
                            Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                            Uri.parse("package:" + BuildConfig.APPLICATION_ID)
                    );
                    activity.startActivity(enableIntent);
                })
                .setNegativeButton(R.string.dismiss, (dialog, id) -> {
                })
                .show();
    }

    @RequiresApi(api = Build.VERSION_CODES.R)
    private static void showBackgroundLocationPermissionsDialog(Activity activity) {
        new MaterialAlertDialogBuilder(activity)
                .setMessage(activity.getString(R.string.permission_location,
                        activity.getString(R.string.app_name),
                        activity.getPackageManager().getBackgroundPermissionOptionLabel()))
                .setPositiveButton(R.string.ok, (dialog, id) -> {
                    ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 0);
                })
                .show();
    }
}
