package io.sentry.android.core;

import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import io.sentry.core.Breadcrumb;
import io.sentry.core.IHub;
import io.sentry.core.SentryLevel;
import io.sentry.core.transport.CurrentDateProvider;
import io.sentry.core.transport.ICurrentDateProvider;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicBoolean;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;

final class LifecycleWatcher implements DefaultLifecycleObserver {

  private long lastStartedSession = 0L;

  private final long sessionIntervalMillis;

  private @Nullable TimerTask timerTask;
  private final @NotNull Timer timer = new Timer(true);
  private final @NotNull IHub hub;
  private final boolean enableSessionTracking;
  private final boolean enableAppLifecycleBreadcrumbs;
  private final @NotNull AtomicBoolean runningSession = new AtomicBoolean();

  private final @NotNull ICurrentDateProvider currentDateProvider;

  LifecycleWatcher(
      final @NotNull IHub hub,
      final long sessionIntervalMillis,
      final boolean enableSessionTracking,
      final boolean enableAppLifecycleBreadcrumbs) {
    this(
        hub,
        sessionIntervalMillis,
        enableSessionTracking,
        enableAppLifecycleBreadcrumbs,
        CurrentDateProvider.getInstance());
  }

  LifecycleWatcher(
      final @NotNull IHub hub,
      final long sessionIntervalMillis,
      final boolean enableSessionTracking,
      final boolean enableAppLifecycleBreadcrumbs,
      final @NotNull ICurrentDateProvider currentDateProvider) {
    this.sessionIntervalMillis = sessionIntervalMillis;
    this.enableSessionTracking = enableSessionTracking;
    this.enableAppLifecycleBreadcrumbs = enableAppLifecycleBreadcrumbs;
    this.hub = hub;
    this.currentDateProvider = currentDateProvider;
  }

  // App goes to foreground
  @Override
  public void onStart(final @NotNull LifecycleOwner owner) {
    startSession();
    addAppBreadcrumb("foreground");
  }

  private void startSession() {
    if (enableSessionTracking) {
      final long currentTimeMillis = currentDateProvider.getCurrentTimeMillis();
      cancelTask();
      if (lastStartedSession == 0L
          || (lastStartedSession + sessionIntervalMillis) <= currentTimeMillis) {
        addSessionBreadcrumb("start");
        hub.startSession();
        runningSession.set(true);
      }
      lastStartedSession = currentTimeMillis;
    }
  }

  // App went to background and triggered this callback after 700ms
  // as no new screen was shown
  @Override
  public void onStop(final @NotNull LifecycleOwner owner) {
    if (enableSessionTracking) {
      scheduleEndSession();
    }

    addAppBreadcrumb("background");
  }

  private void scheduleEndSession() {
    cancelTask();
    timerTask =
        new TimerTask() {
          @Override
          public void run() {
            addSessionBreadcrumb("end");
            hub.endSession();
            runningSession.set(false);
          }
        };

    timer.schedule(timerTask, sessionIntervalMillis);
  }

  private void cancelTask() {
    if (timerTask != null) {
      timerTask.cancel();
      timerTask = null;
    }
  }

  private void addAppBreadcrumb(final @NotNull String state) {
    if (enableAppLifecycleBreadcrumbs) {
      final Breadcrumb breadcrumb = new Breadcrumb();
      breadcrumb.setType("navigation");
      breadcrumb.setData("state", state);
      breadcrumb.setCategory("app.lifecycle");
      breadcrumb.setLevel(SentryLevel.INFO);
      hub.addBreadcrumb(breadcrumb);
    }
  }

  private void addSessionBreadcrumb(final @NotNull String state) {
    final Breadcrumb breadcrumb = new Breadcrumb();
    breadcrumb.setType("session");
    breadcrumb.setData("state", state);
    breadcrumb.setCategory("app.lifecycle");
    breadcrumb.setLevel(SentryLevel.INFO);
    hub.addBreadcrumb(breadcrumb);
  }

  @TestOnly
  @NotNull
  AtomicBoolean isRunningSession() {
    return runningSession;
  }

  @TestOnly
  @Nullable
  TimerTask getTimerTask() {
    return timerTask;
  }
}

@@ -426,16 +426,64 @@
 oolean;%0A
+import java.util.concurrent.atomic.AtomicLong;%0A%0A
 import o
@@ -685,17 +685,29 @@
 ate 
-l
+final AtomicL
 ong last
 Star
@@ -698,28 +698,28 @@
 micLong last
-Star
+Upda
 tedSession =
@@ -719,18 +719,34 @@
 ssion = 
+new AtomicLong(
 0L
+)
 ;%0A%0A  pri
@@ -2259,24 +2259,45 @@
 Tracking) %7B%0A
+      cancelTask();%0A%0A
       final 
@@ -2367,38 +2367,88 @@
 is();%0A      
-cancelTask
+final long lastUpdatedSession = this.lastUpdatedSession.get
 ();%0A
+%0A
       if (la
@@ -2445,28 +2445,28 @@
     if (last
-Star
+Upda
 tedSession =
@@ -2484,28 +2484,28 @@
     %7C%7C (last
-Star
+Upda
 tedSession +
@@ -2671,16 +2671,21 @@
     
-lastStar
+this.lastUpda
 tedS
@@ -2690,19 +2690,21 @@
 dSession
- = 
+.set(
 currentT
@@ -2708,24 +2708,25 @@
 ntTimeMillis
+)
 ;%0A    %7D%0A  %7D%0A
@@ -2894,24 +2894,24 @@
 er owner) %7B%0A
-
     if (enab
@@ -2927,24 +2927,160 @@
 Tracking) %7B%0A
+      final long currentTimeMillis = currentDateProvider.getCurrentTimeMillis();%0A      this.lastUpdatedSession.set(currentTimeMillis);%0A%0A
       schedu

update last session timestamp (getsentry/sentry-android#437)