package com.yorvana.regression.lifecycle

import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.yorvana.MainActivity
import com.yorvana.R
import com.yorvana.data.model.Vehicle
import com.yorvana.testsupport.RetryRule
import com.yorvana.testsupport.awaitTagVisible
import com.yorvana.testsupport.awaitText
import com.yorvana.testsupport.installFreshVault
import com.yorvana.testsupport.lifecycle.LauncherReentry
import com.yorvana.testsupport.lifecycle.ProcessDeath
import com.yorvana.testsupport.lifecycle.TrimMemory
import com.yorvana.testsupport.resetYorvanaState
import com.yorvana.testsupport.seedRecord
import com.yorvana.testsupport.seedVehicle
import com.yorvana.testsupport.tiers.Regression
import com.yorvana.testsupport.tiers.RegressionFull
import com.yorvana.testsupport.tiers.ScenarioId
import com.yorvana.testsupport.vehicleDir
import com.yorvana.ui.TestTags
import kotlinx.serialization.json.Json
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
import java.io.File

@RunWith(AndroidJUnit4::class)
class LifecycleRobustnessRegressionTest {
    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()
        composeRule.installFreshVault()
    }

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

    /**
     * R-L01 canonical core coverage. The asserted state is ViewModel-backed form
     * draft state; transient remember-only UI such as open menus/snackbars is not
     * asserted by this scenario.
     */
    @Regression
    @Test
    fun rL01_recreateVehicleForm_preservesDraftFields() {
        composeRule.onNodeWithTag(TestTags.ADD_VEHICLE_FAB).performClick()
        composeRule.onNodeWithTag(TestTags.VEHICLE_NICKNAME_FIELD).performTextInput("Rotation Draft")
        composeRule.onNodeWithTag(TestTags.VEHICLE_MODEL_FIELD).performTextInput("Unsaved Model")

        ProcessDeath.recreate(composeRule)

        composeRule.onNodeWithTag(TestTags.VEHICLE_NICKNAME_FIELD).assertIsDisplayed()
        composeRule.onNodeWithText("Rotation Draft").assertIsDisplayed()
        composeRule.onNodeWithText("Unsaved Model").assertIsDisplayed()
    }

    /**
     * R-L01 full-tier companion for record form draft state. Picker dialogs,
     * snackbars, and bottom-sheet scroll positions are remember-only transients
     * and intentionally excluded from the contract.
     */
    @RegressionFull
    @Test
    fun rL01_recreateRecordForm_preservesDraftFields() {
        val vehicle = composeRule.seedVehicle(nickname = "Record Rotation")
        composeRule.awaitText("Record Rotation")
        composeRule.onNodeWithTag(TestTags.vehicleItem(vehicle.id)).performClick()
        composeRule.awaitTagVisible(TestTags.ADD_RECORD_FAB)
        composeRule.onNodeWithTag(TestTags.ADD_RECORD_FAB).performClick()
        composeRule.awaitTagVisible(TestTags.RECORD_ODOMETER_FIELD)
        composeRule.onNodeWithTag(TestTags.RECORD_ODOMETER_FIELD).performTextInput("45678")
        composeRule.onNodeWithTag(TestTags.PERFORMER_FIELD).performTextInput("Rotation Shop")

        ProcessDeath.recreate(composeRule)

        composeRule.onNodeWithTag(TestTags.RECORD_ODOMETER_FIELD).assertIsDisplayed()
        composeRule.onNodeWithText("45678").assertIsDisplayed()
        composeRule.onNodeWithText("Rotation Shop").assertIsDisplayed()
    }

    /**
     * R-L02 canonical hard-kill coverage. The managed image relaunches through
     * the launcher entry after `am kill`; this core check asserts that the app
     * restores the deep record-detail route and persisted data. Unsaved form
     * drafts are intentionally out of scope for hard kill.
     */
    @Regression
    @Test
    fun rL02_killOnRecordDetail_restoresRouteAndPersistedData() {
        val vehicle = composeRule.seedVehicle(nickname = "Kill Route Car")
        val record = composeRule.seedRecord(vehicleId = vehicle.id, id = "kill-record", odometer = 32109, performer = "Persisted Shop")
        composeRule.awaitTagVisible(TestTags.vehicleItem(vehicle.id))
        composeRule.onNodeWithTag(TestTags.vehicleItem(vehicle.id)).performClick()
        composeRule.awaitTagVisible(TestTags.recordItem(record.id))
        composeRule.onNodeWithTag(TestTags.recordItem(record.id)).performClick()
        composeRule.awaitTagVisible(TestTags.RECORD_DETAIL_ODOMETER_TEXT)

        ProcessDeath.kill(composeRule)

        composeRule.awaitTagVisible(TestTags.RECORD_DETAIL_ODOMETER_TEXT, timeoutMillis = 120_000)
        composeRule.onNodeWithTag(TestTags.RECORD_DETAIL_ODOMETER_TEXT).assertIsDisplayed()
        composeRule.onNodeWithText("32,109 km").assertIsDisplayed()
        composeRule.onNodeWithText("Performed by: Persisted Shop").assertIsDisplayed()
    }

    @Regression
    @Test
    fun rL03_trimMemoryOnVehicleList_doesNotCrash() {
        composeRule.seedVehicle(nickname = "Trim Car")

        TrimMemory.sendCritical()
        composeRule.waitForIdle()

        composeRule.onNodeWithText("Trim Car").assertIsDisplayed()
    }

    @Regression
    @ScenarioId("R-L04")
    @Test
    fun rL04_backPressFromRecordDetail_returnsThroughStack() {
        val vehicle = composeRule.seedVehicle(nickname = "Back Stack Car")
        val record = composeRule.seedRecord(vehicleId = vehicle.id, id = "back-record")
        composeRule.awaitTagVisible(TestTags.vehicleItem(vehicle.id))
        composeRule.onNodeWithTag(TestTags.vehicleItem(vehicle.id)).performClick()
        composeRule.onNodeWithTag(TestTags.recordItem(record.id)).performClick()
        composeRule.awaitTagVisible(TestTags.RECORD_DETAIL_ODOMETER_TEXT)

        device.pressBack()
        composeRule.awaitTagVisible(TestTags.ADD_RECORD_FAB)
        device.pressBack()
        composeRule.awaitText(composeRule.activity.getString(R.string.my_garage))
    }

    @Regression
    @Test
    fun rL05_launcherReentryOnDeepScreen_keepsExistingTaskScreen() {
        val vehicle = composeRule.seedVehicle(nickname = "Launcher Car")
        composeRule.awaitTagVisible(TestTags.vehicleItem(vehicle.id))
        composeRule.onNodeWithTag(TestTags.vehicleItem(vehicle.id)).performClick()
        composeRule.awaitTagVisible(TestTags.ADD_RECORD_FAB)

        LauncherReentry.launch(composeRule)

        composeRule.onNodeWithTag(TestTags.ADD_RECORD_FAB).assertIsDisplayed()
        composeRule.onNodeWithText("Launcher Car").assertIsDisplayed()
    }

    @Regression
    @Test
    fun rL07_backgroundedExternalEdit_refreshesOnResume() {
        val vehicle = composeRule.seedVehicle(nickname = "Before External")
        composeRule.awaitText("Before External")

        device.pressHome()
        device.waitForIdle()
        val edited = vehicle.copy(nickname = "After External")
        File(composeRule.vehicleDir(vehicle.id), "vehicle.json").writeText(Json.encodeToString(Vehicle.serializer(), edited))
        LauncherReentry.launch(composeRule)

        composeRule.awaitText("After External", timeoutMillis = 30_000)
    }
}
