package com.onthelevel.feature.level

import com.onthelevel.core.sensors.EdgeCalibration
import com.onthelevel.core.sensors.GravitySample
import com.onthelevel.core.sensors.LevelMode
import com.onthelevel.core.sensors.LockDetector
import com.onthelevel.core.sensors.SurfaceCalibration
import com.onthelevel.core.sensors.SurfacePresentation
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import kotlin.math.cos
import kotlin.math.sin

/**
 * Acceptance tests for lock/readout coherence (Codex review): whenever the pipeline
 * reports a lock, the deadbanded readout must stay within the tolerance the locked
 * label states (the lock's exit threshold). Also pins placement gating.
 */
class LevelPipelineTest {

    private val g = 9.80665

    private fun pitched(degrees: Double, tMillis: Long): GravitySample {
        val r = Math.toRadians(degrees)
        return GravitySample(0.0, g * sin(r), g * cos(r), tMillis * 1_000_000)
    }

    private fun flat(tMillis: Long) = pitched(0.0, tMillis)

    private fun surfacePipeline() = LevelPipeline(
        mode = LevelMode.SURFACE,
        surfaceCalibration = SurfaceCalibration.NONE,
        edgeCalibration = EdgeCalibration.NONE,
        lockDetector = LockDetector(),
    )

    /** Feed [degrees] steadily from t=[fromMillis] to t=[untilMillis] at 20 ms. */
    private fun run(
        pipeline: LevelPipeline,
        degrees: Double,
        fromMillis: Long,
        untilMillis: Long,
    ): List<LevelReading> =
        (fromMillis..untilMillis step 20).map { pipeline.process(pitched(degrees, it)) }

    @Test
    fun `steady near-level tilt locks and readout stays within stated tolerance`() {
        val pipeline = surfacePipeline()
        val readings = run(pipeline, degrees = 0.18, fromMillis = 0, untilMillis = 2_000)

        assertTrue("expected a lock after dwell", readings.last().isLocked)
        assertEquals(1, readings.count { it.fireFeedback })
        readings.filter { it.isLocked }.forEach {
            assertTrue(
                "locked reading displayed ${it.displayPrimaryDegrees}° above tolerance",
                it.displayPrimaryDegrees <= LockDetector.DEFAULT_EXIT_DEGREES,
            )
        }
    }

    @Test
    fun `coherence invariant holds while drifting inside hysteresis, then unlocks`() {
        val pipeline = surfacePipeline()
        run(pipeline, degrees = 0.1, fromMillis = 0, untilMillis = 1_000) // acquire lock
        // Drift to 0.30° — inside hysteresis (exit is 0.35°), so the lock holds and
        // the displayed value (0.3°) must still be within the stated tolerance.
        val drifted = run(pipeline, degrees = 0.30, fromMillis = 1_020, untilMillis = 3_000)
        assertTrue(drifted.last().isLocked)
        drifted.filter { it.isLocked }.forEach {
            assertTrue(it.displayPrimaryDegrees <= LockDetector.DEFAULT_EXIT_DEGREES)
        }
        // Past the exit threshold the lock must release.
        val tilted = run(pipeline, degrees = 0.6, fromMillis = 3_020, untilMillis = 5_000)
        assertFalse(tilted.last().isLocked)
    }

    @Test
    fun `surface magnitude remains available through near vertical and screen down`() {
        fun readingAt(degrees: Double): LevelReading =
            surfacePipeline().process(pitched(degrees, 0))

        val atThirty = readingAt(30.0)
        assertEquals(30.0, atThirty.displayPrimaryDegrees, 1e-9)
        assertTrue(atThirty.secondaryADegrees != null)
        assertTrue(atThirty.secondaryBDegrees != null)
        assertEquals(SurfacePresentation.FACE_UP, atThirty.surfacePresentation)
        assertFalse(atThirty.isLocked)

        val atEighty = readingAt(80.0)
        assertEquals(80.0, atEighty.displayPrimaryDegrees, 1e-9)
        assertEquals(null, atEighty.secondaryADegrees)
        assertEquals(null, atEighty.secondaryBDegrees)
        assertEquals(SurfacePresentation.NEAR_VERTICAL, atEighty.surfacePresentation)
        assertFalse(atEighty.isLocked)

        val screenDown = readingAt(120.0)
        assertEquals(120.0, screenDown.displayPrimaryDegrees, 1e-9)
        assertEquals(null, screenDown.secondaryADegrees)
        assertEquals(null, screenDown.secondaryBDegrees)
        assertEquals(SurfacePresentation.SCREEN_DOWN, screenDown.surfacePresentation)
        assertFalse(screenDown.isLocked)
    }

    @Test
    fun `sustained steep Surface reading never locks or fires feedback`() {
        val readings = run(surfacePipeline(), degrees = 30.0, fromMillis = 0, untilMillis = 2_000)

        assertTrue(readings.all { !it.isLocked })
        assertTrue(readings.none { it.fireFeedback })
    }

    @Test
    fun `surface reading exposes the shared settling state before strong guidance`() {
        val readings = run(surfacePipeline(), degrees = 2.0, fromMillis = 0, untilMillis = 1_000)

        assertTrue(readings.first().isSettling)
        assertFalse(readings.last().isSettling)
    }

    @Test
    fun `edge mode never locks while the phone lies flat on a table`() {
        val pipeline = LevelPipeline(
            mode = LevelMode.EDGE,
            surfaceCalibration = SurfaceCalibration.NONE,
            edgeCalibration = EdgeCalibration.NONE,
            lockDetector = LockDetector(),
        )
        // Flat on a table, Edge mode selected: edgeLevelDegrees(g) is ~0 — without
        // placement gating this would lock on a meaningless reading.
        val readings = (0L..2_000L step 20).map { pipeline.process(flat(it)) }
        readings.forEach {
            assertFalse(it.placementOk)
            assertFalse(it.isLocked)
        }
    }

    @Test
    fun `edge mode locks on a genuinely level edge placement`() {
        val pipeline = LevelPipeline(
            mode = LevelMode.EDGE,
            surfaceCalibration = SurfaceCalibration.NONE,
            edgeCalibration = EdgeCalibration.NONE,
            lockDetector = LockDetector(),
        )
        val readings = (0L..2_000L step 20).map {
            pipeline.process(GravitySample(g, 0.0, 0.0, it * 1_000_000))
        }
        assertTrue(readings.last().placementOk)
        assertTrue(readings.last().isLocked)
    }
}
