package com.yorvana

import android.util.Log
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.hasClickAction
import androidx.compose.ui.test.hasStateDescription
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.performTextInput
import androidx.compose.ui.test.performTextReplacement
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeLeft
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.yorvana.data.model.Vehicle
import com.yorvana.data.preferences.DebugBillingOverrideMode
import com.yorvana.testsupport.awaitTagGone
import com.yorvana.testsupport.awaitTagVisible
import com.yorvana.testsupport.awaitText
import com.yorvana.testsupport.installFreshVault
import com.yorvana.testsupport.resetYorvanaState
import com.yorvana.testsupport.tiers.Smoke
import com.yorvana.ui.TestTags
import com.yorvana.util.DateFormatter
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.util.UUID

/**
 * Smoke tests for Yorvana.
 *
 * These tests run on a real Android device (or GMD) and cover the primary user journeys
 * including filesystem access (SAF), camera integration, and the paywall.
 */
@Smoke
@RunWith(AndroidJUnit4::class)
class SmokeTest {
    @get:Rule
    val composeTestRule = createAndroidComposeRule<MainActivity>()

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

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

    private fun step(
        name: String,
        description: String,
    ) {
        Log.d("SmokeTest", "STEP: $name - $description")
    }

    private fun configureVaultAndNavigateToGarage() {
        composeTestRule.installFreshVault()
    }

    private fun navigateToAddVehicle() {
        composeTestRule.onNodeWithTag(TestTags.ADD_VEHICLE_FAB).performClick()
        composeTestRule.waitForIdle()
    }

    private fun navigateToSettings() {
        composeTestRule.awaitTagVisible(TestTags.SETTINGS_BUTTON)
        composeTestRule.onNodeWithTag(TestTags.SETTINGS_BUTTON).performClick()
        composeTestRule.waitForIdle()
    }

    private fun assertScreenTitle(titleResId: Int) {
        val expectedTitle = composeTestRule.activity.getString(titleResId)
        composeTestRule.onNodeWithText(expectedTitle).assertIsDisplayed()
    }

    private fun setBillingOverride(mode: DebugBillingOverrideMode) {
        step("setBillingOverride", "setting mode to $mode")
        val app = composeTestRule.activity.application as YorvanaApplication
        runBlocking {
            app.debugBillingOverride?.setMode(mode)
        }
        composeTestRule.waitForIdle()
    }

    /**
     * Seeds a vehicle directly via repository to bypass the "Add Vehicle" UI gate.
     * Note: Does not populate denormalized fields (recordCount, lastServiceDate)
     * as they are not needed for these smoke scenarios.
     */
    private fun addVehicleForTesting(nickname: String) {
        step("addVehicleForTesting", "adding $nickname")
        val app = composeTestRule.activity.application as YorvanaApplication
        val vehicle =
            Vehicle(
                id = UUID.randomUUID().toString(),
                nickname = nickname,
                createdAt = DateFormatter.nowIso(),
            )
        runBlocking {
            app.vehicleRepository.saveVehicle(vehicle)
        }
        composeTestRule.waitForIdle()
        composeTestRule.awaitText(nickname)
    }

    @Test
    fun s01_firstLaunch_showsWelcome() {
        step("S01", "verifying welcome screen")
        composeTestRule.onNodeWithText(composeTestRule.activity.getString(R.string.setup_title)).assertIsDisplayed()
        composeTestRule.onNodeWithText(composeTestRule.activity.getString(R.string.setup_choose_folder)).assertIsDisplayed()
    }

    @Test
    fun s03_addVehicle_savesCorrectly() {
        configureVaultAndNavigateToGarage()
        step("S03", "adding new vehicle")
        navigateToAddVehicle()

        composeTestRule.onNodeWithTag(TestTags.VEHICLE_NICKNAME_FIELD).performTextInput("Test Car")
        composeTestRule.onNodeWithTag(TestTags.SAVE_BUTTON).performClick()

        step("S03", "verifying vehicle in list")
        composeTestRule.awaitText("Test Car")
        composeTestRule.onNodeWithText("Test Car").assertIsDisplayed()
    }

    @Test
    fun s05_addRecord_savesCorrectly() {
        configureVaultAndNavigateToGarage()
        addVehicleForTesting("Record Car")

        step("S05", "navigating to vehicle detail")
        composeTestRule
            .onNode(hasText("Record Car") and hasClickAction())
            .performScrollTo()
            .performClick()
        composeTestRule.awaitTagVisible(TestTags.ADD_RECORD_FAB)

        step("S05", "adding new record")
        composeTestRule.onNodeWithTag(TestTags.ADD_RECORD_FAB).performClick()
        composeTestRule.awaitTagVisible(TestTags.RECORD_ODOMETER_FIELD)

        composeTestRule.onNodeWithTag(TestTags.RECORD_ODOMETER_FIELD).performTextInput("12345")
        composeTestRule.onNodeWithTag(TestTags.PERFORMER_FIELD).performScrollTo().performTextInput("Self")
        composeTestRule.waitForIdle()
        composeTestRule.onNodeWithTag(TestTags.SAVE_BUTTON).performClick()

        step("S05", "verifying record in list")
        composeTestRule.waitUntil(15000) {
            composeTestRule.onAllNodesWithText("12345 km", substring = true).fetchSemanticsNodes().isNotEmpty()
        }
        composeTestRule.onNodeWithText("12345 km", substring = true).assertIsDisplayed()
    }

    @Test
    fun s07_addAttachment_showsPickerButton() {
        // This test is currently flaky on GMD due to intent stubbing issues with SAF
        configureVaultAndNavigateToGarage()
        addVehicleForTesting("Attachment Car")

        step("S07", "navigating to add record")
        composeTestRule
            .onNode(hasText("Attachment Car") and hasClickAction())
            .performScrollTo()
            .performClick()
        composeTestRule.awaitTagVisible(TestTags.ADD_RECORD_FAB)
        composeTestRule.onNodeWithTag(TestTags.ADD_RECORD_FAB).performClick()

        step("S07", "verifying attachment button is present")
        composeTestRule.onNodeWithText(composeTestRule.activity.getString(R.string.attachment_pick_file)).assertIsDisplayed()
    }

    @Test
    fun s11_navigateToSettings_works() {
        configureVaultAndNavigateToGarage()
        step("S11", "navigating to settings")
        navigateToSettings()
        assertScreenTitle(R.string.settings_title)
        composeTestRule.onNodeWithText(composeTestRule.activity.getString(R.string.settings_odometer_unit)).assertIsDisplayed()
    }

    @Test
    fun s13_editVehicle_updatesList() {
        configureVaultAndNavigateToGarage()
        addVehicleForTesting("Old Name")

        step("S13", "editing vehicle")
        composeTestRule.onNodeWithText("Old Name").performScrollTo().performClick()
        composeTestRule.onNodeWithTag(TestTags.MORE_OPTIONS_BUTTON).performClick()
        composeTestRule.onNodeWithText(composeTestRule.activity.getString(R.string.vehicle_edit)).performClick()

        composeTestRule.onNodeWithTag(TestTags.VEHICLE_NICKNAME_FIELD).performTextReplacement("New Name")
        composeTestRule.onNodeWithTag(TestTags.SAVE_BUTTON).performClick()

        step("S13", "verifying update")
        composeTestRule.awaitText("New Name")
        composeTestRule.onNodeWithText("New Name").assertIsDisplayed()
    }

    @Test
    fun s16_deleteVehicle_returnsToGarage() {
        configureVaultAndNavigateToGarage()
        addVehicleForTesting("To Delete")

        step("S16", "deleting vehicle")
        composeTestRule.onNodeWithText("To Delete").performScrollTo().performClick()
        composeTestRule.onNodeWithTag(TestTags.MORE_OPTIONS_BUTTON).performClick()
        composeTestRule.onNodeWithText(composeTestRule.activity.getString(R.string.vehicle_delete)).performClick()
        composeTestRule.onNodeWithTag(TestTags.CONFIRM_DELETE_BUTTON).performClick()

        step("S16", "verifying empty garage")
        assertScreenTitle(R.string.my_garage)
        composeTestRule.onNodeWithText("To Delete").assertDoesNotExist()
    }

    @Test
    fun s17_backNavigation_works() {
        configureVaultAndNavigateToGarage()
        addVehicleForTesting("Nav Car")

        step("S17", "navigating deep")
        composeTestRule.onNodeWithText("Nav Car").performScrollTo().performClick()
        composeTestRule.awaitTagVisible(TestTags.ADD_RECORD_FAB)

        step("S17", "going back")
        pressBack()
        assertScreenTitle(R.string.my_garage)
    }

    private fun pressBack() {
        InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(android.view.KeyEvent.KEYCODE_BACK)
        composeTestRule.waitForIdle()
    }

    @Test
    fun upgradeDialogOnFreeTier() {
        val activity = composeTestRule.activity
        step("S18", "configuring vault and seeding 1 vehicle directly")
        configureVaultAndNavigateToGarage()
        addVehicleForTesting("S18 Car")

        step("S18", "forcing free tier and attempting to add vehicle")
        setBillingOverride(DebugBillingOverrideMode.FORCE_FREE)
        navigateToAddVehicle()

        step("S18", "verifying paywall sheet is displayed")
        composeTestRule.awaitTagVisible(TestTags.PAYWALL_INFO_SHEET)
        composeTestRule.onNodeWithText(activity.getString(R.string.paywall_sheet_headline)).assertIsDisplayed()

        step("S18", "dismissing sheet and verifying return to list")
        composeTestRule.onNodeWithText(activity.getString(R.string.paywall_sheet_dismiss)).performClick()
        composeTestRule.awaitTagGone(TestTags.PAYWALL_INFO_SHEET)
        assertScreenTitle(R.string.my_garage)
        composeTestRule.onNodeWithText("S18 Car").assertIsDisplayed()
    }

    @Test
    fun readOnlyBannerAcrossScreens() {
        val activity = composeTestRule.activity
        step("S19", "configuring vault and seeding 2 vehicles directly")
        configureVaultAndNavigateToGarage()
        addVehicleForTesting("Car A")
        addVehicleForTesting("Car B")

        step("S19", "forcing free tier and verifying banner on VehicleList")
        setBillingOverride(DebugBillingOverrideMode.FORCE_FREE)
        composeTestRule.awaitTagVisible(TestTags.PAYWALL_STATUS_CARD)
        composeTestRule.onNodeWithText(activity.getString(R.string.paywall_card_learn_more)).assertIsDisplayed()

        step("S19", "verifying swipe-to-dismiss is disabled for Car A")
        composeTestRule.awaitText("Car A")
        composeTestRule.onNodeWithText("Car A").performTouchInput { swipeLeft() }
        composeTestRule.onNodeWithText("Car A").assertIsDisplayed()

        step("S19", "navigating to VehicleDetail and verifying FAB is disabled")
        composeTestRule.onNodeWithText("Car A").performScrollTo().performClick()
        composeTestRule.waitForIdle()
        composeTestRule
            .onNodeWithTag(TestTags.ADD_RECORD_FAB)
            .assertIsDisplayed()
            .assert(hasStateDescription(activity.getString(R.string.disabled_upgrade_description)))

        step("S19", "navigating to Settings and verifying status")
        pressBack() // Return to VehicleList
        assertScreenTitle(R.string.my_garage)
        navigateToSettings()
        // SettingsScreen doesn't show the StatusCard but has a Premium section
        composeTestRule.onNodeWithText(activity.getString(R.string.settings_premium_title)).performScrollTo().assertIsDisplayed()
        composeTestRule.onNodeWithText(activity.getString(R.string.upgrade_cta_title)).assertIsDisplayed()
    }

    @Test
    fun readOnlyLiftsOnPremium() {
        val activity = composeTestRule.activity
        step("S20", "configuring vault and seeding 2 vehicles directly")
        configureVaultAndNavigateToGarage()
        addVehicleForTesting("Car A")
        addVehicleForTesting("Car B")

        step("S20", "forcing free tier and verifying banner appears")
        setBillingOverride(DebugBillingOverrideMode.FORCE_FREE)
        composeTestRule.awaitTagVisible(TestTags.PAYWALL_STATUS_CARD)

        step("S20", "switching to premium and waiting for banner to lift")
        setBillingOverride(DebugBillingOverrideMode.FORCE_PREMIUM)
        composeTestRule.awaitTagGone(TestTags.PAYWALL_STATUS_CARD)

        step("S20", "verifying FAB now navigates to AddVehicle")
        composeTestRule.awaitTagVisible(TestTags.ADD_VEHICLE_FAB)
        navigateToAddVehicle()
        assertScreenTitle(R.string.vehicle_add)

        step("S20", "verifying banner is absent and FAB is enabled on detail")
        pressBack()
        composeTestRule.awaitText("Car A")
        composeTestRule.onNodeWithText("Car A").performScrollTo().performClick()
        composeTestRule.waitForIdle()
        composeTestRule.awaitTagGone(TestTags.PAYWALL_STATUS_CARD)
        composeTestRule
            .onNodeWithTag(TestTags.ADD_RECORD_FAB)
            .assertIsDisplayed()
            .assert(hasClickAction())
    }

    @Test
    fun s21_crashReportingOptInPersists() {
        configureVaultAndNavigateToGarage()

        step("S21", "navigating to settings")
        navigateToSettings()

        step("S21", "toggling crash reporting ON")
        composeTestRule
            .onNodeWithTag(TestTags.SETTINGS_CRASH_REPORTING_TOGGLE)
            .performScrollTo()
            .performClick()

        step("S21", "recreating activity and verifying toggle is still ON")
        composeTestRule.activityRule.scenario.recreate()
        composeTestRule.waitForIdle()
        // Nav state is preserved via rememberSaveable — app restores to SettingsScreen
        composeTestRule.awaitTagVisible(TestTags.SETTINGS_CRASH_REPORTING_TOGGLE, timeoutMillis = 15_000)
        composeTestRule
            .onNodeWithTag(TestTags.SETTINGS_CRASH_REPORTING_TOGGLE)
            .performScrollTo()
            .assertIsOn()

        step("S21", "toggling crash reporting OFF")
        composeTestRule
            .onNodeWithTag(TestTags.SETTINGS_CRASH_REPORTING_TOGGLE)
            .performClick()

        step("S21", "recreating activity and verifying toggle is still OFF")
        composeTestRule.activityRule.scenario.recreate()
        composeTestRule.waitForIdle()
        // Nav state is preserved via rememberSaveable — app restores to SettingsScreen
        composeTestRule.awaitTagVisible(TestTags.SETTINGS_CRASH_REPORTING_TOGGLE, timeoutMillis = 15_000)
        composeTestRule
            .onNodeWithTag(TestTags.SETTINGS_CRASH_REPORTING_TOGGLE)
            .performScrollTo()
            .assertIsOff()
    }

    @Test
    fun test_22_reportBugScreenRendering() {
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
        configureVaultAndNavigateToGarage()
        navigateToSettings()

        // 1. Tap on "Report a bug or suggest a feature"
        composeTestRule
            .onNodeWithText(appContext.getString(R.string.settings_report_bug))
            .performScrollTo()
            .performClick()

        // 2. Verify title and fields
        composeTestRule.onNodeWithText(appContext.getString(R.string.report_bug_title)).assertIsDisplayed()
        composeTestRule.onNodeWithTag(TestTags.REPORT_BUG_SUMMARY_FIELD).assertIsDisplayed()
        composeTestRule.onNodeWithTag(TestTags.REPORT_BUG_DESCRIPTION_FIELD).assertIsDisplayed()

        // 3. Open device info dialog
        composeTestRule
            .onNodeWithTag(TestTags.REPORT_BUG_DEVICE_INFO_CARD)
            .performScrollTo()
            .performClick()
        composeTestRule.awaitText(appContext.getString(R.string.report_bug_app_version), substring = true)
        composeTestRule
            .onNodeWithText(appContext.getString(R.string.report_bug_app_version), substring = true)
            .assertIsDisplayed()

        // Close dialog to continue
        composeTestRule.onNodeWithText(appContext.getString(R.string.action_dismiss)).performClick()
        composeTestRule.waitForIdle()

        // 4. Verify submit button state
        composeTestRule.onNodeWithTag(TestTags.REPORT_BUG_SUBMIT_BUTTON).assertIsNotEnabled()
    }
}
