package solutions.tretter.mindmachine.session

import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import kotlin.math.abs

/**
 * Unit tests for TimelineViewport gesture handling logic.
 * These tests verify zoom/pan transformations without requiring a device or emulator.
 */
class TimelineViewportGestureTest {

    @Test
    fun `applyZoomPan zooms in around centroid correctly`() {
        val viewport = TimelineViewport(startSec = 0f, secondsPerScreen = 600f)
        val widthPx = 400f
        val centroidPx = widthPx / 2f // Center of screen

        // Zoom in 2x around center
        val result = viewport.applyZoomPan(
            zoomChange = 2.0f,
            panPx = 0f,
            widthPx = widthPx,
            centroidPx = centroidPx
        )

        // Seconds per screen should halve (zoom in)
        assertEquals(300f, result.secondsPerScreen, 1f)
        // Center time should stay the same (300 seconds into timeline)
        // Time at x position: startSec + (x / widthPx) * secondsPerScreen
        val centerTime = viewport.startSec + (widthPx / 2f) / widthPx * viewport.secondsPerScreen
        val resultCenterTime = result.startSec + (widthPx / 2f) / widthPx * result.secondsPerScreen
        assertEquals(centerTime, resultCenterTime, 1f)
    }

    @Test
    fun `applyZoomPan zooms out around centroid correctly`() {
        val viewport = TimelineViewport(startSec = 0f, secondsPerScreen = 300f)
        val widthPx = 400f
        val centroidPx = widthPx / 2f // Center of screen

        // Zoom out 2x around center
        val result = viewport.applyZoomPan(
            zoomChange = 0.5f,
            panPx = 0f,
            widthPx = widthPx,
            centroidPx = centroidPx
        )

        // Seconds per screen should double (zoom out)
        assertEquals(600f, result.secondsPerScreen, 1f)
    }

    @Test
    fun `applyZoomPan respects minimum zoom level`() {
        val viewport = TimelineViewport(startSec = 0f, secondsPerScreen = 10f)
        val widthPx = 400f

        // Try to zoom in beyond minimum (30 seconds per screen)
        val result = viewport.applyZoomPan(
            zoomChange = 1000f, // Extreme zoom
            panPx = 0f,
            widthPx = widthPx,
            centroidPx = widthPx / 2f
        )

        // Should clamp to minimum
        assertEquals(30f, result.secondsPerScreen, 1f)
    }

    @Test
    fun `applyZoomPan respects maximum zoom level`() {
        val viewport = TimelineViewport(startSec = 0f, secondsPerScreen = 3600f) // 1 hour
        val widthPx = 400f

        // Try to zoom out beyond maximum (24 hours)
        val result = viewport.applyZoomPan(
            zoomChange = 0.001f, // Extreme zoom out
            panPx = 0f,
            widthPx = widthPx,
            centroidPx = widthPx / 2f
        )

        // Should clamp to maximum (86400 seconds = 24 hours)
        assertEquals(86400f, result.secondsPerScreen, 1f)
    }

    @Test
    fun `applyZoomPan pans horizontally correctly`() {
        val viewport = TimelineViewport(startSec = 100f, secondsPerScreen = 600f)
        val widthPx = 400f

        // Pan right by 100 pixels
        val result = viewport.applyZoomPan(
            zoomChange = 1f,
            panPx = -100f, // Negative pan moves viewport right
            widthPx = widthPx,
            centroidPx = widthPx / 2f
        )

        // Calculate expected start time after pan
        // Pan amount in seconds: -100px * (600s / 400px) = -150s
        // Should move 150 seconds earlier
        assertEquals(250f, result.startSec, 1f)
    }

    @Test
    fun `applyZoomPan prevents negative start time`() {
        val viewport = TimelineViewport(startSec = 10f, secondsPerScreen = 600f)
        val widthPx = 400f

        // Pan left (past start of timeline)
        val result = viewport.applyZoomPan(
            zoomChange = 1f,
            panPx = 200f, // Pan left
            widthPx = widthPx,
            centroidPx = widthPx / 2f
        )

        // Should clamp to 0
        assertEquals(0f, result.startSec, 0.1f)
    }

    @Test
    fun `timeToX converts time to X coordinate correctly`() {
        val viewport = TimelineViewport(startSec = 0f, secondsPerScreen = 600f)
        val widthPx = 400f

        // 300 seconds should be at 50% of width
        val x = viewport.timeToX(300, widthPx)
        assertEquals(200f, x, 0.5f)
    }

    @Test
    fun `xToTimeSec converts X coordinate to time correctly`() {
        val viewport = TimelineViewport(startSec = 100f, secondsPerScreen = 600f)
        val widthPx = 400f

        // 200px (center) should map to start + 300 seconds
        val time = viewport.xToTimeSec(200f, widthPx)
        assertEquals(400f, time, 0.5f)
    }

    @Test
    fun `xToTimeSec and timeToX are inverses`() {
        val viewport = TimelineViewport(startSec = 50f, secondsPerScreen = 300f)
        val widthPx = 400f

        val originalTime = 200f
        val x = viewport.timeToX(originalTime.toInt(), widthPx)
        val resultTime = viewport.xToTimeSec(x, widthPx)

        assertEquals(originalTime, resultTime, 0.5f)
    }

    @Test
    fun `applyZoomPan maintains view on centroid when zooming without panning`() {
        val viewport = TimelineViewport(startSec = 0f, secondsPerScreen = 600f)
        val widthPx = 400f
        val centroidPx = 200f // Center of screen

        // Get the time at centroid before zoom
        val timeAtCentroidBefore = viewport.xToTimeSec(centroidPx, widthPx)

        // Zoom without panning
        val result = viewport.applyZoomPan(
            zoomChange = 2f,
            panPx = 0f, // No panning
            widthPx = widthPx,
            centroidPx = centroidPx
        )

        // The time at centroid should remain the same when only zooming
        val timeAtCentroidAfter = result.xToTimeSec(centroidPx, widthPx)
        assertEquals(timeAtCentroidBefore, timeAtCentroidAfter, 1f)
    }

    @Test
    fun `applyZoomPan with panning changes time at centroid appropriately`() {
        val viewport = TimelineViewport(startSec = 0f, secondsPerScreen = 600f)
        val widthPx = 400f
        val centroidPx = 200f // Center of screen

        // Get the time at centroid before panning
        val timeAtCentroidBefore = viewport.xToTimeSec(centroidPx, widthPx)

        // Pan right (negative pan moves view to later times)
        val result = viewport.applyZoomPan(
            zoomChange = 1f, // No zoom
            panPx = -50f, // Pan right (negative = moves to later times)
            widthPx = widthPx,
            centroidPx = centroidPx
        )

        // The time at centroid should change when panning
        val timeAtCentroidAfter = result.xToTimeSec(centroidPx, widthPx)
        // Pan right should move to later times, so time should increase
        assertTrue("Panning right should increase time at centroid", timeAtCentroidAfter > timeAtCentroidBefore)
    }
}
