package app.notifee.core;

/*
 * Copyright (c) 2016-present Invertase Limited & Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this library 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.
 *
 */

import static app.notifee.core.event.NotificationEvent.TYPE_ACTION_PRESS;
import static app.notifee.core.event.NotificationEvent.TYPE_DISMISSED;
import static app.notifee.core.event.NotificationEvent.TYPE_PRESS;

import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.app.RemoteInput;
import app.notifee.core.event.InitialNotificationEvent;
import app.notifee.core.event.MainComponentEvent;
import app.notifee.core.event.NotificationEvent;
import app.notifee.core.model.NotificationAndroidModel;
import app.notifee.core.model.NotificationAndroidPressActionModel;
import app.notifee.core.model.NotificationModel;
import app.notifee.core.utility.IntentUtils;

public class ReceiverService extends Service {
  private static final String TAG = "ReceiverService";
  public static final String REMOTE_INPUT_RECEIVER_KEY =
      "app.notifee.core.ReceiverService.REMOTE_INPUT_RECEIVER_KEY";

  private static final int PENDING_INTENT_REQUEST_CODE = 0;
  private static final String PENDING_INTENT_URI_SCHEME = "notifee";
  private static final String PENDING_INTENT_URI_AUTHORITY = "receiver-service";
  private static final String PENDING_INTENT_URI_PATH = "pending-intent";

  static final String DELETE_INTENT = "app.notifee.core.ReceiverService.DELETE_INTENT";
  static final String PRESS_INTENT = "app.notifee.core.ReceiverService.PRESS_INTENT";
  static final String ACTION_PRESS_INTENT = "app.notifee.core.ReceiverService.ACTION_PRESS_INTENT";

  /**
   * Creates a PendingIntent, which when sent triggers this class.
   *
   * @param action An Action - matches up with the JS EventType Enum.
   * @param extraKeys Array of strings
   * @param extraBundles One or more bundles
   */
  public static PendingIntent createIntent(
      String action, String[] extraKeys, Bundle... extraBundles) {
    Context context = ContextHolder.getApplicationContext();
    Intent intent = new Intent(context, ReceiverService.class);
    intent.setAction(action);

    for (int i = 0; i < extraKeys.length; i++) {
      String key = extraKeys[i];

      if (i <= extraBundles.length - 1) {
        Bundle bundle = extraBundles[i];
        intent.putExtra(key, bundle);
      } else {
        intent.putExtra(key, (String) null);
      }
    }

    intent.setData(createPendingIntentData(intent));

    return PendingIntent.getService(
        context,
        PENDING_INTENT_REQUEST_CODE,
        intent,
        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
  }

  private static Uri createPendingIntentData(Intent intent) {
    Bundle notification = intent.getBundleExtra("notification");
    Bundle android = notification == null ? null : notification.getBundle("android");
    Bundle pressAction = intent.getBundleExtra("pressAction");

    String notificationId = notification == null ? null : notification.getString("id");
    String notificationTag = android == null ? null : android.getString("tag");
    String pressActionId = pressAction == null ? null : pressAction.getString("id");

    Uri.Builder builder =
        new Uri.Builder()
            .scheme(PENDING_INTENT_URI_SCHEME)
            .authority(PENDING_INTENT_URI_AUTHORITY)
            .appendPath(PENDING_INTENT_URI_PATH);

    appendNullableIdentityValue(builder, "interactionType", intent.getAction());
    appendNullableIdentityValue(builder, "notificationTag", notificationTag);
    appendNullableIdentityValue(builder, "notificationId", notificationId);
    appendNullableIdentityValue(builder, "pressActionId", pressActionId);
    return builder.build();
  }

  private static void appendNullableIdentityValue(
      Uri.Builder builder, String name, @Nullable String value) {
    builder.appendQueryParameter(name + "Present", value == null ? "0" : "1");
    if (value != null) {
      builder.appendQueryParameter(name, value);
    }
  }

  @Nullable
  @Override
  public IBinder onBind(Intent intent) {
    return null;
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    String action = intent.getAction();

    if (action == null) {
      return START_NOT_STICKY;
    }

    switch (action) {
      case DELETE_INTENT:
        onDeleteIntent(intent);
        break;
      case PRESS_INTENT:
        onPressIntent(intent);
        break;
      case ACTION_PRESS_INTENT:
        onActionPressIntent(intent);
        break;
    }

    return START_NOT_STICKY;
  }

  /** Handle users delete/dismiss intents */
  private void onDeleteIntent(Intent intent) {
    Bundle notification = intent.getBundleExtra("notification");

    if (notification == null) {
      return;
    }

    NotificationModel notificationModel = NotificationModel.fromBundle(notification);

    // On Android 14+, users can dismiss foreground service notifications even with ongoing: true.
    // If the dismissed notification belongs to the active foreground service, re-post it
    // so the user remains aware of the running service.
    if (ForegroundService.repostIfActive(notificationModel.getId())) {
      return;
    }

    EventBus.post(new NotificationEvent(TYPE_DISMISSED, notificationModel));
  }

  /** Handle user notification press */
  private void onPressIntent(Intent intent) {
    Bundle notification = intent.getBundleExtra("notification");

    if (notification == null) {
      return;
    }

    NotificationModel notificationModel = NotificationModel.fromBundle(notification);

    Bundle pressAction = intent.getBundleExtra("pressAction");
    NotificationAndroidPressActionModel pressActionBundle = null;

    Bundle extras = new Bundle();

    if (pressAction != null) {
      pressActionBundle = NotificationAndroidPressActionModel.fromBundle(pressAction);
      extras.putBundle("pressAction", pressActionBundle.toBundle());
    }

    EventBus.post(new NotificationEvent(TYPE_PRESS, notificationModel, extras));

    InitialNotificationEvent initialNotificationEvent =
        new InitialNotificationEvent(notificationModel, extras);
    EventBus.postSticky(initialNotificationEvent);

    if (pressActionBundle == null) {
      return;
    }

    String launchActivity = pressActionBundle.getLaunchActivity();
    String mainComponent = pressActionBundle.getMainComponent();

    if (launchActivity != null || mainComponent != null) {
      launchPendingIntentActivity(
          initialNotificationEvent,
          launchActivity,
          mainComponent,
          pressActionBundle.getLaunchActivityFlags());
    }
  }

  private void onActionPressIntent(Intent intent) {
    Bundle notification = intent.getBundleExtra("notification");
    Bundle pressAction = intent.getBundleExtra("pressAction");

    if (notification == null || pressAction == null) {
      return;
    }

    NotificationModel notificationModel = NotificationModel.fromBundle(notification);
    NotificationAndroidModel notificationAndroidModel = notificationModel.getAndroid();
    NotificationAndroidPressActionModel pressActionBundle =
        NotificationAndroidPressActionModel.fromBundle(pressAction);

    Bundle extras = new Bundle();
    extras.putBundle("pressAction", pressActionBundle.toBundle());

    Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
    if (remoteInput != null) {
      CharSequence input = remoteInput.getCharSequence(REMOTE_INPUT_RECEIVER_KEY);
      if (input != null) {
        extras.putString("input", input.toString());
      }
    }

    EventBus.post(new NotificationEvent(TYPE_ACTION_PRESS, notificationModel, extras));

    if (notificationModel.getAndroid().getAutoCancel()) {
      NotificationManagerCompat notificationManagerCompat =
          NotificationManagerCompat.from(getApplicationContext());

      notificationManagerCompat.cancel(
          notificationAndroidModel.getTag(), notificationModel.getId().hashCode());
    }

    String launchActivity = pressActionBundle.getLaunchActivity();
    String mainComponent = pressActionBundle.getMainComponent();

    if (launchActivity != null || mainComponent != null) {
      InitialNotificationEvent initialNotificationEvent =
          new InitialNotificationEvent(notificationModel, extras);
      launchPendingIntentActivity(
          initialNotificationEvent,
          launchActivity,
          mainComponent,
          pressActionBundle.getLaunchActivityFlags());

      closeSystemDialogsBestEffort();
    }
  }

  @SuppressWarnings("deprecation")
  @SuppressLint("MissingPermission")
  private void closeSystemDialogsBestEffort() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
      return;
    }

    // BROADCAST_CLOSE_SYSTEM_DIALOGS is protected/system-only, so the library intentionally does
    // not declare it. Keep this pre-Android 12 legacy broadcast best-effort; Android 12+ skips it.
    try {
      ContextHolder.getApplicationContext()
          .sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
    } catch (SecurityException e) {
      Logger.w(TAG, "Unable to close system dialogs with legacy broadcast", e);
    }
  }

  private void launchPendingIntentActivity(
      InitialNotificationEvent initialNotificationEvent,
      @Nullable String launchActivity,
      @Nullable String mainComponent,
      int launchActivityFlags) {
    Class<?> launchActivityClass = IntentUtils.getLaunchActivity(launchActivity);
    if (launchActivityClass == null) {
      Logger.e(TAG, "Failed to get launch activity");
      return;
    }

    Intent launchIntent = new Intent(getApplicationContext(), launchActivityClass);

    if (launchActivityFlags != -1) {
      launchIntent.addFlags(launchActivityFlags);
    }

    if (mainComponent != null) {
      launchIntent.putExtra("mainComponent", mainComponent);
    }

    PendingIntent pendingContentIntent =
        PendingIntent.getActivity(
            getApplicationContext(),
            initialNotificationEvent.getNotificationModel().getHashCode(),
            launchIntent,
            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);

    try {
      pendingContentIntent.send();
      EventBus.postSticky(initialNotificationEvent);

      // Send sticky event to save the mainComponent
      if (mainComponent != null) {
        EventBus.postSticky(new MainComponentEvent(mainComponent));
      }
    } catch (Exception e) {
      Logger.e(
          "ReceiverService",
          "Failed to send PendingIntent from launchPendingIntentActivity for notification "
              + initialNotificationEvent.getNotificationModel().getId(),
          e);
    }
  }
}
