package com.yorvana.regression.attachments

import android.content.Intent
import android.os.SystemClock
import android.util.Log
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithContentDescription
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.test.espresso.intent.Intents
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.Camera
import com.yorvana.testsupport.ExternalViewer
import com.yorvana.testsupport.FixtureAssetsRule
import com.yorvana.testsupport.RetryRule
import com.yorvana.testsupport.SystemPicker
import com.yorvana.testsupport.VaultFixture
import com.yorvana.testsupport.app
import com.yorvana.testsupport.awaitTagVisible
import com.yorvana.testsupport.awaitText
import com.yorvana.testsupport.installFreshVault
import com.yorvana.testsupport.killAndRelaunch
import com.yorvana.testsupport.resetYorvanaState
import com.yorvana.testsupport.seedAttachmentFile
import com.yorvana.testsupport.seedRecord
import com.yorvana.testsupport.seedVehicle
import com.yorvana.testsupport.sha256
import com.yorvana.testsupport.tiers.RegressionFull
import com.yorvana.ui.TestTags
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith

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

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

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

    @Before
    fun setup() {
        composeRule.resetYorvanaState()
        composeRule.installFreshVault()
        Intents.init()
    }

    @After
    fun tearDown() {
        runCatching { Intents.release() }
        // Restore network if changed by any test
        com.yorvana.testsupport
            .networkState()
            .restore()
    }

    @Test
    fun rA01_realSystemFilePicker_pdfPersistsAndViewIntentLaunches() {
        val vehicle = composeRule.seedVehicle(nickname = "Civic")
        val record = composeRule.seedRecord(vehicleId = vehicle.id)

        // Stage the fixture file to Downloads
        val fixture = fixtureRule.copyUniquePdfToDownloads("R-A01")

        openVehicle("Civic")
        openRecord(record)

        composeRule.onNodeWithTag(TestTags.EDIT_BUTTON).performClick()
        composeRule.awaitTagVisible(TestTags.ATTACHMENT_PICK_FILE_BUTTON, timeoutMillis = 60_000)

        composeRule.onNodeWithTag(TestTags.ATTACHMENT_PICK_FILE_BUTTON).performScrollTo().performClick()
        SystemPicker.pickFile(device, fixture.displayName)

        // Wait for the picker to fully close and app to resume
        device.waitForIdle()
        Thread.sleep(5000)

        // Wait for the attachment to appear in the UI list
        val endTime = System.currentTimeMillis() + 30_000
        var found = false
        while (System.currentTimeMillis() < endTime) {
            try {
                if (composeRule.onAllNodesWithTag(TestTags.ATTACHMENT_ITEM).fetchSemanticsNodes().isNotEmpty()) {
                    found = true
                    break
                }
            } catch (_: Exception) {
            }
            Thread.sleep(500)
        }
        if (!found) error("Timeout waiting for attachment item to appear")
        Thread.sleep(1000)

        // Ensure Save button is visible and enabled
        composeRule.awaitTagVisible(TestTags.SAVE_BUTTON, timeoutMillis = 60_000)

        // Persistent save click to handle potential event drops on slow GMD
        var saved = false
        for (i in 1..3) {
            val nodes = composeRule.onAllNodesWithTag(TestTags.SAVE_BUTTON).fetchSemanticsNodes()
            if (nodes.isEmpty()) {
                saved = true
                break
            }
            try {
                composeRule.onNodeWithTag(TestTags.SAVE_BUTTON).performClick()
                composeRule.waitForIdle()
                Thread.sleep(2000)
                // Check if we navigated back (vehicle title is visible)
                if (composeRule.onAllNodesWithText("Civic").fetchSemanticsNodes().isNotEmpty()) {
                    saved = true
                    break
                }
            } catch (_: Exception) {
            } catch (_: AssertionError) {
            }
        }

        if (!saved) {
            // Final wait attempt
            composeRule.awaitText("Civic", timeoutMillis = 120_000)
        }

        composeRule.killAndRelaunch()
        openVehicle("Civic")
        openRecord(record)

        val attachmentFile = composeRule.app().vaultStorage.resolveAttachmentDocumentFile(vehicle.id, record.id, fixture.displayName)
        assertThat(attachmentFile).isNotNull()
        val bytes =
            composeRule.activity.contentResolver
                .openInputStream(attachmentFile!!.uri)
                ?.use { it.readBytes() }
        assertThat(sha256(bytes ?: ByteArray(0))).isEqualTo(fixture.sha256)

        assertExternalViewIntentLaunches("application/pdf") {
            composeRule.onNodeWithTag(TestTags.recordDetailAttachmentThumbnail(fixture.displayName)).performClick()
        }
    }

    @Test
    fun rA02_realCameraCapture_photoPersistsAsJpgInVault() {
        val vehicle = composeRule.seedVehicle(nickname = "Camera Car")

        openAddRecord("Camera Car")
        fillMinimalRecord("22345", "Camera Shop")
        composeRule.onNodeWithTag(TestTags.ATTACHMENT_TAKE_PHOTO_BUTTON).performScrollTo().performClick()
        Camera.captureAndAccept(device)

        // Wait for the attachment to appear in the UI list
        val endTime = System.currentTimeMillis() + 30_000
        var found = false
        while (System.currentTimeMillis() < endTime) {
            try {
                if (composeRule.onAllNodesWithTag(TestTags.ATTACHMENT_ITEM).fetchSemanticsNodes().isNotEmpty()) {
                    found = true
                    break
                }
            } catch (_: Exception) {
            }
            Thread.sleep(500)
        }
        if (!found) error("Timeout waiting for attachment item to appear")

        // Ensure Save button is enabled and click it
        composeRule.onNodeWithTag(TestTags.SAVE_BUTTON).assertIsEnabled().performClick()

        // Wait for it to navigate back to Vehicle Detail to ensure save is complete.
        // Use 120s because GMD can be extremely slow during IO/navigation transitions.
        composeRule.awaitTagVisible(TestTags.ADD_RECORD_FAB, timeoutMillis = 120_000)

        composeRule.killAndRelaunch()
        openVehicle("Camera Car")
        val recordId =
            runBlocking {
                composeRule
                    .app()
                    .vaultStorage
                    .listRecordIds(vehicle.id)
                    .getOrThrow()
                    .single()
            }

        val record =
            runBlocking {
                composeRule.app().recordRepository.getRecord(vehicle.id, recordId)
            }
        assertThat(record).isNotNull()
        openRecord(record!!)
        assertThat(record.attachments.single()).endsWith(".jpg")
    }

    @Test
    fun rA04_nonImageAttachment_launchesSystemViewerIntent() {
        val vehicle = composeRule.seedVehicle(nickname = "Civic")
        val record = composeRule.seedRecord(vehicleId = vehicle.id)
        composeRule.seedAttachmentFile(vehicle.id, record.id, "test.pdf")

        openVehicle("Civic")
        openRecord(record)

        assertExternalViewIntentLaunches("application/pdf") {
            composeRule.onNodeWithTag(TestTags.recordDetailAttachmentThumbnail("test.pdf")).performClick()
        }
    }

    @Test
    fun rA05_removeAttachmentWithConfirmation_deletesFileFromVaultFixture() {
        val vehicle = composeRule.seedVehicle(nickname = "Remove Attachment Car")
        val record = composeRule.seedRecord(vehicleId = vehicle.id, attachments = listOf("delete-me.txt"))
        composeRule.seedAttachmentFile(vehicle.id, record.id, "delete-me.txt")
        assertThat(VaultFixture.active().exists("vehicles/${vehicle.id}/records/${record.id}/delete-me.txt")).isTrue()

        openVehicle("Remove Attachment Car")
        openRecord(record)
        composeRule.onNodeWithContentDescription(composeRule.activity.getString(R.string.record_edit)).performClick()
        composeRule.awaitText(composeRule.activity.getString(R.string.record_edit), timeoutMillis = 60_000)
        composeRule.onNodeWithTag(TestTags.removeAttachment("delete-me.txt")).performScrollTo().performClick()
        composeRule.awaitTagVisible(TestTags.CONFIRM_DELETE_BUTTON, timeoutMillis = 60_000)
        composeRule.onNodeWithTag(TestTags.CONFIRM_DELETE_BUTTON).performClick()
        composeRule.onNodeWithTag(TestTags.SAVE_BUTTON).performClick()

        assertThat(VaultFixture.active().exists("vehicles/${vehicle.id}/records/${record.id}/delete-me.txt")).isFalse()
    }

    @Test
    fun rA06_attachmentStillResolvableAfterVaultMoveMidSession() {
        val vehicle = composeRule.seedVehicle(nickname = "Move Attachment Car")
        val record = composeRule.seedRecord(vehicleId = vehicle.id, attachments = listOf("after-move.pdf"))
        composeRule.seedAttachmentFile(vehicle.id, record.id, "after-move.pdf", "%PDF-1.4\nfixture")

        composeRule.onNodeWithTag(TestTags.SETTINGS_BUTTON).performClick()
        composeRule.awaitText(composeRule.activity.getString(R.string.settings_title), timeoutMillis = 60_000)
        composeRule.onNodeWithTag(TestTags.SETTINGS_VAULT_CHANGE_BUTTON).performClick()
        SystemPicker.createAndPickFolder(device, "Yorvana R-A06 ${System.currentTimeMillis()}")
        composeRule.awaitText(composeRule.activity.getString(R.string.settings_vault_move_title), timeoutMillis = 60_000)
        composeRule.onNodeWithTag(TestTags.SETTINGS_VAULT_MOVE_ACTION).performClick()
        composeRule.awaitText(
            composeRule.activity.getString(R.string.settings_vault_changed_success),
            timeoutMillis = 120_000,
            substring = true,
        )
        composeRule.onNodeWithText(composeRule.activity.getString(R.string.action_ok)).performClick()
        composeRule.waitForIdle()

        // Navigate back from settings to vehicle list
        composeRule.onNodeWithContentDescription(composeRule.activity.getString(R.string.action_back)).performClick()
        composeRule.waitForIdle()

        openVehicle("Move Attachment Car")
        openRecord(record)
        assertExternalViewIntentLaunches("application/pdf") {
            composeRule.onNodeWithTag(TestTags.recordDetailAttachmentThumbnail("after-move.pdf")).performClick()
        }
    }

    @Test
    fun rA07_cancelledCameraCaptureShowsErrorLeavesNoPartialAndRetrySucceeds() {
        val vehicle = composeRule.seedVehicle(nickname = "Camera Cancel Car")
        diagnostic("rA07 seeded vehicle id=${vehicle.id}")

        openAddRecord("Camera Cancel Car")
        fillMinimalRecord("32345", "Camera Shop")
        composeRule.onNodeWithTag(TestTags.ATTACHMENT_TAKE_PHOTO_BUTTON).performScrollTo().performClick()
        Camera.captureAndCancel(device)
        composeRule.awaitText("Photo capture was cancelled", substring = true, timeoutMillis = 20_000)
        assertThat(
            VaultFixture
                .active()
                .snapshot()
                .keys
                .none { it.endsWith(".jpg") },
        ).isTrue()

        composeRule.onNodeWithTag(TestTags.ATTACHMENT_TAKE_PHOTO_BUTTON).performScrollTo().performClick()
        Camera.captureAndAccept(device)

        // Wait for the attachment to appear in the UI list
        val endTime = System.currentTimeMillis() + 30_000
        var found = false
        while (System.currentTimeMillis() < endTime) {
            try {
                if (composeRule.onAllNodesWithTag(TestTags.ATTACHMENT_ITEM).fetchSemanticsNodes().isNotEmpty()) {
                    found = true
                    break
                }
            } catch (_: Exception) {
            }
            Thread.sleep(500)
        }
        if (!found) error("Timeout waiting for attachment item to appear")

        diagnostic("rA07 before save click")
        composeRule.onNodeWithTag(TestTags.SAVE_BUTTON).performClick()
        diagnostic("rA07 after save click")
        composeRule.waitForIdle()
        diagnostic("rA07 after waitForIdle")
        val recordId =
            runBlocking {
                val recordIds =
                    composeRule
                        .app()
                        .vaultStorage
                        .listRecordIds(vehicle.id)
                        .getOrThrow()
                diagnostic("rA07 persisted record ids=$recordIds")
                recordIds.single()
            }
        val record =
            runBlocking {
                // Ensure the record is actually visible to the repository
                composeRule.app().recordRepository.getRecord(vehicle.id, recordId).also {
                    diagnostic("rA07 repository record id=$recordId present=${it != null}")
                }
            }
        assertThat(record).isNotNull()
        openRecord(record!!)
        assertThat(record.attachments.singleOrNull()).endsWith(".jpg")
    }

    private fun openVehicle(name: String) {
        composeRule.awaitText(name, timeoutMillis = 60_000)
        composeRule.onNodeWithText(name).performClick()
        composeRule.awaitTagVisible(TestTags.ADD_RECORD_FAB, timeoutMillis = 60_000)
    }

    private fun openAddRecord(vehicleName: String) {
        openVehicle(vehicleName)
        composeRule.onNodeWithTag(TestTags.ADD_RECORD_FAB).performClick()
        composeRule.awaitTagVisible(TestTags.RECORD_ODOMETER_FIELD, timeoutMillis = 60_000)
    }

    private fun openRecord(record: com.yorvana.data.model.ServiceRecord) {
        val recordTag = TestTags.recordItem(record.id)
        diagnostic("openRecord before click recordId=${record.id}")
        try {
            composeRule.onNodeWithTag(recordTag).performClick()
        } catch (error: Throwable) {
            diagnostic(
                "openRecord click failed recordId=${record.id} targetNodes=${nodeCount(recordTag)} " +
                    "${navigationState()} error=${error::class.java.simpleName}:${error.message}",
            )
            throw error
        }
        diagnostic("openRecord after click recordId=${record.id}")
        composeRule.awaitTagVisible(TestTags.RECORD_DETAIL_ODOMETER_TEXT, timeoutMillis = 60_000)
        diagnostic("openRecord detail visible recordId=${record.id}")
    }

    private fun navigationState(): String =
        "saveNodes=${nodeCount(TestTags.SAVE_BUTTON)} " +
            "addRecordNodes=${nodeCount(TestTags.ADD_RECORD_FAB)} " +
            "detailNodes=${nodeCount(TestTags.RECORD_DETAIL_ODOMETER_TEXT)}"

    private fun nodeCount(tag: String): Int =
        runCatching {
            composeRule.onAllNodesWithTag(tag).fetchSemanticsNodes().size
        }.getOrElse { error ->
            diagnostic("nodeCount failed tag=$tag error=${error::class.java.simpleName}:${error.message}")
            -1
        }

    private fun diagnostic(message: String) {
        Log.i(DIAGNOSTIC_TAG, "elapsedMs=${SystemClock.elapsedRealtime()} $message")
    }

    private fun fillMinimalRecord(
        odometer: String,
        performer: String,
    ) {
        composeRule.onNodeWithTag(TestTags.RECORD_ODOMETER_FIELD).performTextInput(odometer)
        composeRule.onNodeWithTag(TestTags.PERFORMER_FIELD).performTextInput(performer)
    }

    private fun assertExternalViewIntentLaunches(
        mimeType: String,
        block: () -> Unit,
    ) {
        val intent =
            Intent(Intent.ACTION_VIEW).apply {
                type = mimeType
            }
        assumeTrue("No external viewer resolves $mimeType", ExternalViewer.hasResolver(composeRule.activity, intent))
        block()
        ExternalViewer.assertLaunchOrSkipUi(composeRule.activity, intent)
    }

    private companion object {
        const val DIAGNOSTIC_TAG = "AttachmentRegression"
    }
}
