package com.yorvana.regression.vault

import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.printToLog
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.google.common.truth.Truth.assertThat
import com.yorvana.MainActivity
import com.yorvana.R
import com.yorvana.testsupport.RetryRule
import com.yorvana.testsupport.SystemPicker
import com.yorvana.testsupport.VaultFixture
import com.yorvana.testsupport.awaitText
import com.yorvana.testsupport.installVaultFixture
import com.yorvana.testsupport.killAndRelaunch
import com.yorvana.testsupport.resetYorvanaState
import com.yorvana.testsupport.tiers.RegressionFull
import com.yorvana.testsupport.tiers.ScenarioId
import com.yorvana.ui.TestTags
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith

@RegressionFull
@RunWith(AndroidJUnit4::class)
class VaultRealOsRegressionTest {
    val retryRule = RetryRule()
    val composeRule = createAndroidComposeRule<MainActivity>()

    @get:Rule
    val ruleChain: RuleChain = RuleChain.outerRule(retryRule).around(composeRule)

    private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())

    @Before
    fun setUp() {
        composeRule.resetYorvanaState()
    }

    @After
    fun tearDown() {
        composeRule.resetYorvanaState()
    }

    @Test
    fun rD02_preExistingVaultFixture_loadsVehicles() {
        val vault = VaultFixture.createLocal("r-d02-${System.currentTimeMillis()}")
        vault.seedSampleVault(vehicleName = "Existing Fixture Car")

        composeRule.installVaultFixture(vault)

        composeRule.awaitText("Existing Fixture Car", timeoutMillis = 40_000)
    }

    @Test
    fun rD03_missingFileVault_doesNotCrashList() {
        val vault = VaultFixture.createLocal("r-d03-${System.currentTimeMillis()}")
        vault.seedSampleVault(vehicleName = "Missing Car")
        composeRule.installVaultFixture(vault)

        composeRule.awaitText("Missing Car")

        // Delete the entire directory to simulate a missing/deleted vault folder on the file scheme.
        val root = java.io.File(vault.uri.path!!)
        root.deleteRecursively()

        composeRule.killAndRelaunch()
    }

    @Test
    fun rD04_malformedVehicleJson_skipsVehicleWithoutCrashingList() {
        val vault = VaultFixture.createLocal("r-d04-${System.currentTimeMillis()}")
        vault.seedSampleVault(vehicleName = "Good Car", vehicleId = "good-id")
        vault.writeMalformedVehicle("bad-id")
        composeRule.installVaultFixture(vault)

        composeRule.awaitText("Good Car", timeoutMillis = 40_000)
    }

    @Test
    @ScenarioId("R-S02a")
    fun rS02_movePath_copiesBytesDeletesSourceAndReopensNewVault() {
        val source = VaultFixture.createLocal("r-s02-source-${System.currentTimeMillis()}")
        source.seedSampleVault(vehicleName = "Move Source Car")
        val sourceSnapshot = source.snapshot()
        composeRule.installVaultFixture(source)

        pickNewVaultFromSettings("Yorvana R-S02 Move ${System.currentTimeMillis()}")
        composeRule.onNodeWithTag(TestTags.SETTINGS_VAULT_MOVE_ACTION).performClick()

        try {
            // Recursive copy over SAF can be extremely slow on GMD
            composeRule.awaitText(
                composeRule.activity.getString(R.string.settings_vault_changed_success),
                timeoutMillis = 120_000,
                substring = true,
            )
        } catch (e: Exception) {
            android.util.Log.d("TEST", "Timeout waiting for success dialog")
            composeRule.onRoot(useUnmergedTree = true).printToLog("SEMANTICS")
            throw e
        }

        composeRule.onNodeWithText(composeRule.activity.getString(R.string.action_ok)).performClick()

        composeRule.waitForIdle()
        val destination = VaultFixture.active()
        destination.assertSnapshotEquals(sourceSnapshot)
        assertThat(source.snapshot()).isEmpty()

        composeRule.killAndRelaunch()
        composeRule.awaitText("Move Source Car", timeoutMillis = 120_000)
    }

    @Test
    @ScenarioId("R-S02b")
    fun rS02_startFreshPath_flipsPointerLeavesSourceUntouchedAndReopensEmptyVault() {
        val source = VaultFixture.createLocal("r-s02-fresh-source-${System.currentTimeMillis()}")
        source.seedSampleVault(vehicleName = "Fresh Source Car")
        val sourceSnapshot = source.snapshot()
        composeRule.installVaultFixture(source)

        pickNewVaultFromSettings("Yorvana R-S02 Fresh ${System.currentTimeMillis()}")
        composeRule.onNodeWithTag(TestTags.SETTINGS_VAULT_FRESH_ACTION).performClick()

        try {
            composeRule.awaitText(
                composeRule.activity.getString(R.string.settings_vault_changed_success),
                timeoutMillis = 40_000,
                substring = true,
            )
        } catch (e: Exception) {
            android.util.Log.d("TEST", "Timeout waiting for success dialog")
            composeRule.onRoot(useUnmergedTree = true).printToLog("SEMANTICS")
            throw e
        }

        composeRule.onNodeWithText(composeRule.activity.getString(R.string.action_ok)).performClick()

        composeRule.waitForIdle()
        val active = VaultFixture.active()
        val snapshot = active.snapshot()
        source.assertSnapshotEquals(sourceSnapshot)
        assertThat(snapshot).isEmpty()
        composeRule.killAndRelaunch()
    }

    @Test
    @ScenarioId("R-S02c")
    fun rS02_abortAfterCopyStarts_keepsSourceAndPointerUnchanged() {
        val source = VaultFixture.createLocal("r-s02-abort-source-${System.currentTimeMillis()}")
        source.seedSampleVault(vehicleName = "Abort Source Car")
        val sourceSnapshot = source.snapshot()
        composeRule.installVaultFixture(source)

        val folderName = "Yorvana R-S02 Abort ${System.currentTimeMillis()}"

        // Inject failure after 1 file is copied to simulate an aborted move mid-stream
        com.yorvana.data.storage.VaultStorageTestHooks.failMoveAfterCopiedFiles = 1

        try {
            pickNewVaultFromSettings(folderName)
            composeRule.onNodeWithTag(TestTags.SETTINGS_VAULT_MOVE_ACTION).performClick()

            // Wait for the error snackbar triggered by the injected failure
            composeRule.awaitText(
                composeRule.activity.getString(R.string.settings_vault_move_error, ""),
                timeoutMillis = 40_000,
                substring = true,
            )
        } finally {
            com.yorvana.data.storage.VaultStorageTestHooks.failMoveAfterCopiedFiles = null
            com.yorvana.data.storage.VaultStorageTestHooks.lastAttemptedDestinationUri?.let { uri ->
                if (uri.scheme == "file") {
                    java.io.File(uri.path!!).deleteRecursively()
                } else {
                    androidx.documentfile.provider.DocumentFile
                        .fromTreeUri(composeRule.activity, uri)
                        ?.delete()
                }
            }
            com.yorvana.data.storage.VaultStorageTestHooks.lastAttemptedDestinationUri = null
        }

        // Verify source is still exactly as it was and app is still using the source URI
        source.assertSnapshotEquals(sourceSnapshot)
        assertThat(VaultFixture.active().uri).isEqualTo(source.uri)
    }

    private fun openVaultSettings() {
        composeRule.onNodeWithTag(TestTags.SETTINGS_BUTTON).performClick()
        composeRule.awaitText(composeRule.activity.getString(R.string.settings_title))
    }

    private fun pickNewVaultFromSettings(folderName: String) {
        openVaultSettings()
        composeRule.onNodeWithTag(TestTags.SETTINGS_VAULT_CHANGE_BUTTON).performClick()
        com.yorvana.testsupport.SystemPicker
            .createAndPickFolder(device, folderName)
        composeRule.awaitText(composeRule.activity.getString(R.string.settings_vault_move_title), timeoutMillis = 40_000)
    }
}
