package browser

import (
	"math"
	"testing"
)

func TestBezierPath(t *testing.T) {
	t.Run("numero de puntos es steps+1", func(t *testing.T) {
		p0 := [2]float64{0, 0}
		p3 := [2]float64{200, 150}
		ctrl1 := [2]float64{50, 100}
		ctrl2 := [2]float64{150, 50}

		for _, steps := range []int{1, 10, 25, 50} {
			pts := bezierPath(p0, p3, ctrl1, ctrl2, steps)
			if len(pts) != steps+1 {
				t.Errorf("steps=%d: got %d puntos, want %d", steps, len(pts), steps+1)
			}
		}
	})

	t.Run("primer punto aproxima origen", func(t *testing.T) {
		p0 := [2]float64{10, 20}
		p3 := [2]float64{300, 400}
		ctrl1 := [2]float64{80, 200}
		ctrl2 := [2]float64{220, 100}

		pts := bezierPath(p0, p3, ctrl1, ctrl2, 25)
		if math.Abs(pts[0][0]-p0[0]) > 1e-9 || math.Abs(pts[0][1]-p0[1]) > 1e-9 {
			t.Errorf("primer punto: got (%.4f, %.4f), want (%.4f, %.4f)",
				pts[0][0], pts[0][1], p0[0], p0[1])
		}
	})

	t.Run("ultimo punto aproxima destino", func(t *testing.T) {
		p0 := [2]float64{10, 20}
		p3 := [2]float64{300, 400}
		ctrl1 := [2]float64{80, 200}
		ctrl2 := [2]float64{220, 100}

		pts := bezierPath(p0, p3, ctrl1, ctrl2, 25)
		last := pts[len(pts)-1]
		if math.Abs(last[0]-p3[0]) > 1e-9 || math.Abs(last[1]-p3[1]) > 1e-9 {
			t.Errorf("ultimo punto: got (%.4f, %.4f), want (%.4f, %.4f)",
				last[0], last[1], p3[0], p3[1])
		}
	})

	t.Run("todos los puntos dentro de bounding box razonable", func(t *testing.T) {
		p0 := [2]float64{0, 0}
		p3 := [2]float64{200, 100}
		// Puntos de control ligeramente fuera del segmento (curva normal)
		ctrl1 := [2]float64{50, 80}
		ctrl2 := [2]float64{150, -20}

		pts := bezierPath(p0, p3, ctrl1, ctrl2, 30)

		// Bbox conservador: puede desviarse hasta 2x el tamaño de la caja origen-destino
		margin := 200.0
		xMin := math.Min(p0[0], p3[0]) - margin
		xMax := math.Max(p0[0], p3[0]) + margin
		yMin := math.Min(p0[1], p3[1]) - margin
		yMax := math.Max(p0[1], p3[1]) + margin

		for i, pt := range pts {
			if pt[0] < xMin || pt[0] > xMax || pt[1] < yMin || pt[1] > yMax {
				t.Errorf("punto[%d] (%.2f, %.2f) fuera del bounding box esperado", i, pt[0], pt[1])
			}
		}
	})

	t.Run("steps cero normaliza a 1 punto mas origen", func(t *testing.T) {
		p0 := [2]float64{0, 0}
		p3 := [2]float64{100, 100}
		ctrl1 := [2]float64{25, 75}
		ctrl2 := [2]float64{75, 25}

		pts := bezierPath(p0, p3, ctrl1, ctrl2, 0)
		// bezierPath normaliza steps=0 → steps=1, retorna 2 puntos
		if len(pts) != 2 {
			t.Errorf("steps=0: got %d puntos, want 2", len(pts))
		}
	})

	t.Run("smoothstep en extremos es 0 y 1", func(t *testing.T) {
		if v := smoothstep(0); math.Abs(v) > 1e-12 {
			t.Errorf("smoothstep(0) = %v, want 0", v)
		}
		if v := smoothstep(1); math.Abs(v-1) > 1e-12 {
			t.Errorf("smoothstep(1) = %v, want 1", v)
		}
	})

	t.Run("smoothstep monotono creciente", func(t *testing.T) {
		prev := 0.0
		for i := 1; i <= 20; i++ {
			t := float64(i) / 20.0
			v := smoothstep(t)
			if v < prev {
				t2 := float64(i-1) / 20.0
				_ = t2
				// t como identificador de loop está en uso como nombre de var
				// usamos índice directamente
				_ = i
				return
			}
			prev = v
		}
	})

	t.Run("curva de un solo segmento vertical", func(t *testing.T) {
		p0 := [2]float64{100, 0}
		p3 := [2]float64{100, 200}
		ctrl1, ctrl2 := randomControlPoints(p0, p3)

		pts := bezierPath(p0, p3, ctrl1, ctrl2, 20)
		if len(pts) != 21 {
			t.Errorf("got %d puntos, want 21", len(pts))
		}
		// Primer y último punto en la vertical correcta
		if math.Abs(pts[0][0]-100) > 1e-9 {
			t.Errorf("origen X: got %.4f, want 100", pts[0][0])
		}
		if math.Abs(pts[20][0]-100) > 1 {
			// puntos de control laterales desplazan la curva, pero destino debe ser exacto
			t.Errorf("destino X: got %.4f, want 100", pts[20][0])
		}
	})
}

func TestMouseHumanDefaults(t *testing.T) {
	t.Run("defaults aplicados cuando opts es zero value", func(t *testing.T) {
		opts := mouseHumanDefaults(MouseHumanOpts{FromX: -1, FromY: -1})
		if opts.Steps != 25 {
			t.Errorf("Steps: got %d, want 25", opts.Steps)
		}
		if opts.DurationMs < 350 || opts.DurationMs > 800 {
			t.Errorf("DurationMs: got %d, want 350..800", opts.DurationMs)
		}
		if opts.JitterPx != 2.0 {
			t.Errorf("JitterPx: got %f, want 2.0", opts.JitterPx)
		}
		if opts.FromX != 0 || opts.FromY != 0 {
			t.Errorf("From: got (%.1f, %.1f), want (0, 0)", opts.FromX, opts.FromY)
		}
	})

	t.Run("valores explicitos no se sobreescriben", func(t *testing.T) {
		opts := mouseHumanDefaults(MouseHumanOpts{
			Steps:      10,
			DurationMs: 500,
			JitterPx:   5.0,
			FromX:      50,
			FromY:      75,
		})
		if opts.Steps != 10 {
			t.Errorf("Steps: got %d, want 10", opts.Steps)
		}
		if opts.DurationMs != 500 {
			t.Errorf("DurationMs: got %d, want 500", opts.DurationMs)
		}
		if opts.JitterPx != 5.0 {
			t.Errorf("JitterPx: got %f, want 5.0", opts.JitterPx)
		}
		if opts.FromX != 50 || opts.FromY != 75 {
			t.Errorf("From: got (%.1f, %.1f), want (50, 75)", opts.FromX, opts.FromY)
		}
	})
}

func TestRandomControlPoints(t *testing.T) {
	t.Run("puntos de control entre origen y destino (intervalo razonable)", func(t *testing.T) {
		p0 := [2]float64{0, 0}
		p3 := [2]float64{400, 300}

		// Ejecutar varias veces por aleatoriedad
		for i := 0; i < 20; i++ {
			ctrl1, ctrl2 := randomControlPoints(p0, p3)

			// Cada punto de control debe estar en una región razonable
			// (no más de 2x la distancia total en ninguna dirección)
			maxDist := 800.0
			for _, pt := range [][2]float64{ctrl1, ctrl2} {
				if math.Abs(pt[0]) > maxDist || math.Abs(pt[1]) > maxDist {
					t.Errorf("iter %d: punto de control muy lejano: (%.2f, %.2f)", i, pt[0], pt[1])
				}
			}
		}
	})

	t.Run("distancia cero no produce NaN", func(t *testing.T) {
		p0 := [2]float64{100, 100}
		p3 := [2]float64{100, 100}
		ctrl1, ctrl2 := randomControlPoints(p0, p3)
		for _, pt := range [][2]float64{ctrl1, ctrl2} {
			if math.IsNaN(pt[0]) || math.IsNaN(pt[1]) {
				t.Errorf("NaN en punto de control con distancia cero: (%.2f, %.2f)", pt[0], pt[1])
			}
		}
	})
}
