/*
 * Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.android.libraries.mobiledatadownload.foreground;

import android.annotation.SuppressLint;
import android.app.NotificationChannel;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;

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

import com.google.common.base.Preconditions;

import javax.annotation.Nullable;

/** Utilities for creating and managing notifications. */
// TODO(b/148401016): Add UI test for NotificationUtil.
public final class NotificationUtil {
    public static final String CANCEL_ACTION_EXTRA = "cancel-action";
    public static final String KEY_EXTRA = "key";
    public static final String STOP_SERVICE_EXTRA = "stop-service";

    private NotificationUtil() {
    }

    public static final String NOTIFICATION_CHANNEL_ID = "download-notification-channel-id";

    /** Create the NotificationBuilder for the Foreground Download Service */
    public static NotificationCompat.Builder createForegroundServiceNotificationBuilder(
            Context context) {
        return getNotificationBuilder(context)
                .setContentTitle("Downloading")
                .setSmallIcon(android.R.drawable.stat_notify_sync_noanim);
    }

    /** Create a Notification.Builder. */
    public static NotificationCompat.Builder createNotificationBuilder(
            Context context, int size, String contentTitle, String contentText) {
        return getNotificationBuilder(context)
                .setContentTitle(contentTitle)
                .setContentText(contentText)
                .setSmallIcon(android.R.drawable.stat_sys_download)
                .setOngoing(true)
                .setProgress(size, 0, false);
    }

    private static NotificationCompat.Builder getNotificationBuilder(Context context) {
        return new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
                .setCategory(NotificationCompat.CATEGORY_SERVICE)
                .setOnlyAlertOnce(true);
    }

    /**
     * Create a Notification for a key.
     *
     * @param key Key to identify the download this notification is created for.
     */
    public static void cancelNotificationForKey(Context context, String key) {
        NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
        notificationManager.cancel(notificationKeyForKey(key));
    }

    /** Create the Cancel Menu Action which will be attach to the download notification. */
    // FLAG_IMMUTABLE is only for api >= 23, however framework still recommends to use this:
    // <internal>
    @SuppressLint("InlinedApi")
    public static void createCancelAction(
            Context context,
            Class<?> foregroundDownloadServiceClass,
            String key,
            NotificationCompat.Builder notification,
            int notificationKey) {
        SaferIntentUtils intentUtils = new SaferIntentUtils() {
        };

        Intent cancelIntent = new Intent(context, foregroundDownloadServiceClass);
        cancelIntent.setPackage(context.getPackageName());
        cancelIntent.putExtra(CANCEL_ACTION_EXTRA, notificationKey);
        cancelIntent.putExtra(KEY_EXTRA, key);

        // It should be safe since we are using SaferPendingIntent, setting Package and
        // Component, and
        // use PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE.
        PendingIntent pendingCancelIntent;
        if (VERSION.SDK_INT >= VERSION_CODES.O) {
            pendingCancelIntent =
                    intentUtils.getForegroundService(
                            context,
                            notificationKey,
                            cancelIntent,
                            PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
        } else {
            pendingCancelIntent =
                    intentUtils.getService(
                            context,
                            notificationKey,
                            cancelIntent,
                            PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
        }
        NotificationCompat.Action action =
                new NotificationCompat.Action.Builder(
                        android.R.drawable.stat_sys_warning,
                        "Cancel",
                        Preconditions.checkNotNull(pendingCancelIntent))
                        .build();
        notification.addAction(action);
    }

    /** Generate the Notification Key for the Key */
    public static int notificationKeyForKey(String key) {
        // Consider if we could have collision.
        // Heavier alternative is Hashing.goodFastHash(32).hashUnencodedChars(key).asInt();
        return key.hashCode();
    }

    /** Send intent to start the DownloadService in foreground. */
    public static void startForegroundDownloadService(
            Context context, Class<?> foregroundDownloadService, String key) {
        Intent intent = new Intent(context, foregroundDownloadService);
        intent.putExtra(KEY_EXTRA, key);

        // Start ForegroundDownloadService to download in the foreground.
        ContextCompat.startForegroundService(context, intent);
    }

    /** Sending the intent to stop the foreground download service */
    public static void stopForegroundDownloadService(
            Context context, Class<?> foregroundDownloadService, String key) {
        Intent intent = new Intent(context, foregroundDownloadService);
        intent.putExtra(STOP_SERVICE_EXTRA, true);
        intent.putExtra(KEY_EXTRA, key);

        // This will send the intent to stop the service.
        ContextCompat.startForegroundService(context, intent);
    }

    /**
     * Return the String message to display in Notification when the download is paused to wait for
     * network connection.
     */
    public static String getDownloadPausedMessage(Context context) {
        return "Waiting for network connection";
    }

    /**
     * Return the String message to display in Notification when the download is paused due to a
     * missing wifi connection.
     */
    public static String getDownloadPausedWifiMessage(Context context) {
        return "Waiting for WiFi connection";
    }

    /** Return the String message to display in Notification when the download is failed. */
    public static String getDownloadFailedMessage(Context context) {
        return "Download failed";
    }

    /** Return the String message to display in Notification when the download is success. */
    public static String getDownloadSuccessMessage(Context context) {
        return "Downloaded";
    }

    /** Create the Notification Channel for Downloading. */
    public static void createNotificationChannel(Context context) {
        if (VERSION.SDK_INT >= VERSION_CODES.O) {
            NotificationChannel notificationChannel =
                    new NotificationChannel(
                            NOTIFICATION_CHANNEL_ID,
                            "Data Download Notification Channel",
                            android.app.NotificationManager.IMPORTANCE_DEFAULT);

            android.app.NotificationManager manager =
                    context.getSystemService(android.app.NotificationManager.class);
            manager.createNotificationChannel(notificationChannel);
        }
    }

    /** Utilities for safely accessing PendingIntent APIs. */
    private interface SaferIntentUtils {

        @Nullable
        @RequiresApi(VERSION_CODES.O) // to match PendingIntent.getForegroundService()
        default PendingIntent getForegroundService(
                Context context, int requestCode, Intent intent, int flags) {
            return PendingIntent.getForegroundService(context, requestCode, intent, flags);
        }

        @Nullable
        default PendingIntent getService(Context context, int requestCode, Intent intent,
                int flags) {
            return PendingIntent.getService(context, requestCode, intent, flags);
        }
    }
}
