package com.yorvana.regression.attachments

import android.graphics.Bitmap
import android.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toPixelMap
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import com.yorvana.MainActivity
import com.yorvana.testsupport.RetryRule
import com.yorvana.testsupport.awaitTagVisible
import com.yorvana.testsupport.gestures.Direction
import com.yorvana.testsupport.gestures.Gestures.doubleTap
import com.yorvana.testsupport.gestures.Gestures.pinch
import com.yorvana.testsupport.gestures.Gestures.swipe
import com.yorvana.testsupport.installFreshVault
import com.yorvana.testsupport.resetYorvanaState
import com.yorvana.testsupport.seedAttachmentBytes
import com.yorvana.testsupport.seedRecord
import com.yorvana.testsupport.seedVehicle
import com.yorvana.testsupport.tiers.Regression
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
import java.io.ByteArrayOutputStream

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

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

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

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

    /**
     * R-A03 asserts only user-observable rendering. The viewer contract across
     * recreation/rotation is reset-to-clean-baseline: zoom state may reset, but
     * the image remains visible and interactable with no half-applied transform.
     */
    @Test
    fun rA03_imageViewerPinchDoubleTapPanAndRecreate_areVisibleAndClamped() {
        openSeededImageViewer()
        val surface = composeRule.onNodeWithTag(TestTags.IMAGE_VIEWER_SURFACE)
        val image = composeRule.onNodeWithTag(TestTags.IMAGE_VIEWER_IMAGE)
        composeRule.waitUntil(30_000) {
            image.visibleImagePixels() > 0
        }
        val baselineCoverage = image.visibleImagePixels()

        surface.pinch(1.8f)
        composeRule.waitForIdle()
        val pinchedCoverage = image.visibleImagePixels()
        assertThat(pinchedCoverage).isGreaterThan(baselineCoverage)

        surface.pinch(0.55f)
        composeRule.waitForIdle()
        val closedCoverage = image.visibleImagePixels()
        assertThat(closedCoverage).isLessThan(pinchedCoverage)
        // 25% headroom allows antialiasing/cropping variance while still catching a stuck zoom-out clamp.
        assertThat(closedCoverage).isAtMost((baselineCoverage * 1.25f).toInt())

        surface.doubleTap()
        composeRule.waitForIdle()
        var doubleTappedCoverage = image.visibleImagePixels()
        if (doubleTappedCoverage <= baselineCoverage) {
            surface.doubleTap()
            composeRule.waitForIdle()
            doubleTappedCoverage = image.visibleImagePixels()
        }
        assertThat(doubleTappedCoverage).isGreaterThan(baselineCoverage)

        surface.swipe(Direction.Left)
        surface.swipe(Direction.Right)
        surface.swipe(Direction.Up)
        surface.swipe(Direction.Down)
        composeRule.waitForIdle()
        assertThat(image.visibleImagePixels()).isGreaterThan(0)

        surface.doubleTap()
        composeRule.waitForIdle()
        assertThat(image.visibleImagePixels()).isAtMost((baselineCoverage * 1.25f).toInt())
    }

    private fun openSeededImageViewer() {
        val vehicle = composeRule.seedVehicle(nickname = "Image Gesture Car")
        val record = composeRule.seedRecord(vehicleId = vehicle.id, id = "image-record", attachments = listOf("fixture.png"))
        composeRule.seedAttachmentBytes(vehicle.id, record.id, "fixture.png", pngFixture())
        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)
        composeRule.onNodeWithTag(TestTags.recordDetailAttachmentThumbnail("fixture.png")).performClick()
        composeRule.awaitTagVisible(TestTags.IMAGE_VIEWER_SURFACE)
    }

    private fun androidx.compose.ui.test.SemanticsNodeInteraction.visibleImagePixels(): Int {
        val pixelMap = captureToImage().toPixelMap()
        assertThat(pixelMap.width * pixelMap.height).isAtMost(2_000_000)
        var count = 0
        for (x in 0 until pixelMap.width) {
            for (y in 0 until pixelMap.height) {
                if (pixelMap[x, y] != Color.Black) count += 1
            }
        }
        return count
    }

    private fun pngFixture(): ByteArray {
        val bitmap = Bitmap.createBitmap(80, 40, Bitmap.Config.ARGB_8888)
        Canvas(bitmap).drawColor(android.graphics.Color.rgb(220, 40, 40))
        return ByteArrayOutputStream().use { output ->
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, output)
            output.toByteArray()
        }
    }
}
