package io.sentry.android.core

import android.app.Activity
import android.app.Application
import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.sentry.Scopes
import io.sentry.SentryFeedbackOptions
import io.sentry.test.DeferredExecutorService
import io.sentry.test.ImmediateExecutorService
import kotlin.test.BeforeTest
import kotlin.test.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

@RunWith(AndroidJUnit4::class)
class FeedbackShakeIntegrationTest {

  private class Fixture {
    val application = mock<Application>()
    val scopes = mock<Scopes>()
    val options =
      SentryAndroidOptions().apply {
        dsn = "https://key@sentry.io/proj"
        executorService = ImmediateExecutorService()
      }
    val activity = mock<Activity>()
    val formHandler = mock<SentryFeedbackOptions.IFormHandler>()

    init {
      options.feedbackOptions.setFormHandler(formHandler)
    }

    fun getSut(useShakeGesture: Boolean = true): FeedbackShakeIntegration {
      options.feedbackOptions.isUseShakeGesture = useShakeGesture
      return FeedbackShakeIntegration(application)
    }
  }

  private val fixture = Fixture()

  @BeforeTest
  fun setup() {
    CurrentActivityHolder.getInstance().clearActivity()
  }

  @Test
  fun `when useShakeGesture is enabled registers activity lifecycle callbacks`() {
    val sut = fixture.getSut(useShakeGesture = true)
    sut.register(fixture.scopes, fixture.options)

    verify(fixture.application).registerActivityLifecycleCallbacks(any())
  }

  @Test
  fun `resolves the accelerometer sensor off the main thread`() {
    val deferredExecutor = DeferredExecutorService()
    fixture.options.executorService = deferredExecutor
    whenever(fixture.application.getSystemService(any())).thenReturn(null)

    val sut = fixture.getSut(useShakeGesture = true)
    sut.register(fixture.scopes, fixture.options)

    // Callback registration stays synchronous, but the expensive SensorManager lookup is deferred.
    verify(fixture.application).registerActivityLifecycleCallbacks(any())
    verify(fixture.application, never()).getSystemService(eq(Context.SENSOR_SERVICE))

    deferredExecutor.runAll()

    verify(fixture.application).getSystemService(eq(Context.SENSOR_SERVICE))
  }

  @Test
  fun `warm-up drained after close does not resolve the sensor`() {
    // Integrations are closed before the executor drains, so a queued warm-up can run after
    // close(). It must be a no-op rather than resolving the sensor and spinning up a HandlerThread.
    val deferredExecutor = DeferredExecutorService()
    fixture.options.executorService = deferredExecutor
    whenever(fixture.application.getSystemService(any())).thenReturn(null)

    val sut = fixture.getSut(useShakeGesture = true)
    sut.register(fixture.scopes, fixture.options)
    sut.close()

    deferredExecutor.runAll()

    verify(fixture.application, never()).getSystemService(eq(Context.SENSOR_SERVICE))
  }

  @Test
  fun `re-registering after close re-arms shake detection`() {
    // A second Sentry.init reusing the same integration must revive shake detection rather than
    // stay off because of the closed latch.
    val deferredExecutor = DeferredExecutorService()
    fixture.options.executorService = deferredExecutor
    whenever(fixture.application.getSystemService(any())).thenReturn(null)

    val sut = fixture.getSut(useShakeGesture = true)
    sut.register(fixture.scopes, fixture.options)
    sut.close()
    sut.register(fixture.scopes, fixture.options)

    deferredExecutor.runAll()

    verify(fixture.application, atLeastOnce()).getSystemService(eq(Context.SENSOR_SERVICE))
  }

  @Test
  fun `when useShakeGesture is disabled does not register activity lifecycle callbacks`() {
    val sut = fixture.getSut(useShakeGesture = false)
    sut.register(fixture.scopes, fixture.options)

    verify(fixture.application, never()).registerActivityLifecycleCallbacks(any())
  }

  @Test
  fun `close unregisters activity lifecycle callbacks`() {
    val sut = fixture.getSut()
    sut.register(fixture.scopes, fixture.options)

    sut.close()

    verify(fixture.application).unregisterActivityLifecycleCallbacks(any())
  }

  @Test
  fun `hooks into already-resumed activity on deferred init`() {
    CurrentActivityHolder.getInstance().setActivity(fixture.activity)
    whenever(fixture.activity.getSystemService(any())).thenReturn(null)

    val sut = fixture.getSut()
    sut.register(fixture.scopes, fixture.options)

    // The integration should have attempted to start shake detection
    // (it will fail gracefully because SensorManager is null in tests,
    // but the important thing is it tried)
  }

  @Test
  fun `does not crash when no activity is available on deferred init`() {
    val sut = fixture.getSut()
    sut.register(fixture.scopes, fixture.options)
    // Should not throw
  }

  @Test
  fun `onActivityPaused stops shake detection`() {
    val sut = fixture.getSut()
    sut.register(fixture.scopes, fixture.options)

    whenever(fixture.activity.getSystemService(any())).thenReturn(null)
    sut.onActivityResumed(fixture.activity)
    sut.onActivityPaused(fixture.activity)
    // Should not throw, shake detection stopped gracefully
  }

  @Test
  fun `close without register does not crash`() {
    val sut = fixture.getSut()
    sut.close()
  }
}
