package com.onthelevel.core.sensors

import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

class LockDetectorTest {

    private fun detector() = LockDetector(
        enterThresholdDegrees = 0.2,
        exitThresholdDegrees = 0.35,
        dwellMillis = 400,
        feedbackDebounceMillis = 3_000,
    )

    @Test
    fun `lock requires dwell time inside the enter threshold`() {
        val d = detector()
        assertFalse(d.update(0.1, 0).isLocked)
        assertFalse(d.update(0.1, 200).isLocked)
        val result = d.update(0.1, 450)
        assertTrue(result.isLocked)
        assertTrue(result.fireFeedback)
    }

    @Test
    fun `leaving the threshold before dwell completes resets the timer`() {
        val d = detector()
        d.update(0.1, 0)
        d.update(0.5, 200)   // bounced out
        d.update(0.1, 300)   // back in — dwell restarts
        assertFalse(d.update(0.1, 600).isLocked)  // only 300ms since re-entry
        assertTrue(d.update(0.1, 750).isLocked)
    }

    @Test
    fun `hysteresis holds the lock between enter and exit thresholds`() {
        val d = detector()
        d.update(0.1, 0)
        assertTrue(d.update(0.1, 500).isLocked)
        // 0.3° is above enter (0.2) but below exit (0.35): still locked.
        assertTrue(d.update(0.3, 600).isLocked)
        // 0.4° exceeds the exit threshold: unlocked.
        assertFalse(d.update(0.4, 700).isLocked)
    }

    @Test
    fun `feedback fires once per lock and respects the debounce window`() {
        val d = detector()
        d.update(0.1, 0)
        assertTrue(d.update(0.1, 500).fireFeedback)
        assertFalse(d.update(0.1, 600).fireFeedback) // still locked, no re-fire

        // Rock out and back in quickly: re-lock at ~1500ms is inside the 3s debounce.
        d.update(0.5, 900)
        d.update(0.1, 1000)
        val relock = d.update(0.1, 1500)
        assertTrue(relock.isLocked)
        assertFalse(relock.fireFeedback)

        // A re-lock after the debounce window fires again.
        d.update(0.5, 2000)
        d.update(0.1, 4000)
        assertTrue(d.update(0.1, 4500).fireFeedback)
    }

    @Test
    fun `negative readings lock on magnitude`() {
        val d = detector()
        d.update(-0.1, 0)
        assertTrue(d.update(-0.1, 500).isLocked)
    }
}
