package cash.z.ecc.android.sdk.demoapp.benchmark

import androidx.benchmark.macro.CompilationMode
import androidx.benchmark.macro.ExperimentalMetricApi
import androidx.benchmark.macro.MacrobenchmarkScope
import androidx.benchmark.macro.StartupMode
import androidx.benchmark.macro.TraceSectionMetric
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import cash.z.ecc.android.sdk.demoapp.test.UiTestPrerequisites
import cash.z.ecc.android.sdk.demoapp.test.clickAndWaitFor
import cash.z.ecc.android.sdk.demoapp.test.waitFor
import org.junit.Rule
import org.junit.Test
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds

/**
 * This benchmark class provides measurements and captured custom traces for investigating SDK syncing mechanisms
 * with restricted blockchain range. It always resets the SDK before the next sync iteration. It uses UIAutomator to
 * navigate to the Balances screen, where the whole download -> validate -> scan -> enhance process is performed and
 * thus measured by this test.
 *
 * Run this benchmark from Android Studio only against the Benchmark build type set in all modules.
 *
 * We ideally run this on a physical device with Android SDK level 29, at least, as profiling is provided by this
 * version and later on.
 */

// TODO [#809]: Enable macrobenchmark on CI
// TODO [#809]: https://github.com/zcash/zcash-android-wallet-sdk/issues/809
class SyncBlockchainBenchmark : UiTestPrerequisites() {

    companion object {
        private const val APP_TARGET_PACKAGE_NAME = "cash.z.ecc.android.sdk.demoapp.mainnet" // NON-NLS

        private const val BALANCE_SCREEN_SECTION = "BALANCE_SCREEN" // NON-NLS
        private const val BLOCKCHAIN_SYNC_SECTION = "BLOCKCHAIN_SYNC" // NON-NLS
        private const val DOWNLOAD_SECTION = "DOWNLOAD" // NON-NLS
        private const val VALIDATION_SECTION = "VALIDATION" // NON-NLS
        private const val SCAN_SECTION = "SCAN" // NON-NLS
    }

    @get:Rule
    val benchmarkRule = MacrobenchmarkRule()

    /**
     * Advanced trace events test, which starts the Demo-app on the Home screen and then navigates to the Get Balance
     * screen. SDK sync phases with restricted blockchain range are measured during the overall sync mechanism here.
     */
    @Test
    @OptIn(ExperimentalMetricApi::class)
    fun tracesSyncBlockchain() = benchmarkRule.measureRepeated(
        packageName = APP_TARGET_PACKAGE_NAME,
        metrics = listOf(
            TraceSectionMetric(sectionName = BALANCE_SCREEN_SECTION, mode = TraceSectionMetric.Mode.First),
            TraceSectionMetric(sectionName = BLOCKCHAIN_SYNC_SECTION, mode = TraceSectionMetric.Mode.First),
            TraceSectionMetric(sectionName = DOWNLOAD_SECTION, mode = TraceSectionMetric.Mode.First),
            TraceSectionMetric(sectionName = VALIDATION_SECTION, mode = TraceSectionMetric.Mode.First),
            TraceSectionMetric(sectionName = SCAN_SECTION, mode = TraceSectionMetric.Mode.First)
        ),
        compilationMode = CompilationMode.Full(),
        startupMode = StartupMode.COLD,
        iterations = 3,
        measureBlock = {
            startActivityAndWait()
            resetSDK()
            gotoBalanceScreen()
            waitForBalanceScreen()
            closeBalanceScreen()
        }
    )

    // TODO [#808]: Add demo-ui-lib module (and reference the hardcoded texts here)
    // TODO [#808]: https://github.com/zcash/zcash-android-wallet-sdk/issues/808

    private fun MacrobenchmarkScope.resetSDK() {
        // Open toolbar overflow menu
        device.findObject(By.desc("More options")).clickAndWaitFor(Until.newWindow(), 2.seconds) // NON-NLS
        // Click on the reset sdk menu item
        device.findObject(By.text("Reset SDK")).click() // NON-NLS
        device.waitForIdle()
    }

    private fun MacrobenchmarkScope.waitForBalanceScreen() {
        device.waitFor(Until.hasObject(By.text("Status: SYNCED")), 5.minutes) // NON-NLS
    }

    private fun MacrobenchmarkScope.closeBalanceScreen() {
        // To close the Balance screen and disconnect from SDK Synchronizer
        device.pressBack()
    }

    private fun MacrobenchmarkScope.gotoBalanceScreen() {
        // Open drawer menu
        device.findObject(By.desc("Open navigation drawer")).clickAndWaitFor(Until.newWindow(), 2.seconds) // NON-NLS
        // Navigate to Balances screen
        device.findObject(By.text("Get Balance")).click() // NON-NLS
    }
}
