package browser

import (
	"net"
	"os"
	"regexp"
	"strings"
	"testing"
	"time"
)

// TestIsWSL2 verifica que isWSL2 detecta el entorno correctamente leyendo /proc/version.
func TestIsWSL2(t *testing.T) {
	b, err := os.ReadFile("/proc/version")
	if err != nil {
		t.Skip("/proc/version no disponible (no es Linux)")
	}
	lower := strings.ToLower(string(b))
	expectWSL2 := strings.Contains(lower, "microsoft") || strings.Contains(lower, "wsl")
	got := isWSL2()
	if got != expectWSL2 {
		t.Errorf("isWSL2() = %v, want %v (contenido: %q)", got, expectWSL2, string(b)[:min(120, len(b))])
	}
	t.Logf("isWSL2() = %v  (entorno: %s)", got, string(b)[:min(80, len(b))])
}

// TestTranslateUserDataDirForWindows verifica la traduccion de rutas Linux a Windows.
// Solo corre si wslpath esta disponible (WSL2).
func TestTranslateUserDataDirForWindows(t *testing.T) {
	if !isWSL2() {
		t.Skip("solo aplica en WSL2")
	}
	result, err := translateUserDataDirForWindows("/tmp/test-chrome-profile")
	if err != nil {
		t.Fatalf("translateUserDataDirForWindows: %v", err)
	}
	// El resultado debe contener backslash (ruta Windows) o empezar con [A-Z]:
	reWin := regexp.MustCompile(`(?i)^[A-Z]:\\|\\`)
	if !reWin.MatchString(result) {
		t.Errorf("resultado no parece ruta Windows: %q", result)
	}
	t.Logf("translateUserDataDirForWindows('/tmp/test-chrome-profile') = %q", result)
}

// TestIsWindowsExe verifica que isWindowsExe detecta ejecutables .exe.
func TestIsWindowsExe(t *testing.T) {
	cases := []struct {
		path string
		want bool
	}{
		{"/mnt/c/Program Files/Google/Chrome/Application/chrome.exe", true},
		{"chrome.exe", true},
		{"CHROME.EXE", true},
		{"/usr/bin/google-chrome", false},
		{"chromium", false},
		{"/mnt/c/Windows/System32/cmd.exe", true},
	}
	for _, c := range cases {
		got := isWindowsExe(c.path)
		if got != c.want {
			t.Errorf("isWindowsExe(%q) = %v, want %v", c.path, got, c.want)
		}
	}
}

// TestFindChrome verifica que el ejecutable de Chrome es localizable.
func TestFindChrome(t *testing.T) {
	path, err := findChrome()
	if err != nil {
		t.Skipf("Chrome no encontrado (entorno sin Chrome): %v", err)
	}
	if path == "" {
		t.Error("findChrome retorno path vacio")
	}
	t.Logf("Chrome encontrado en: %s", path)
}

// TestChromeLaunchAndConnect lanza Chrome, conecta CDP, navega a about:blank y cierra.
// Requiere CHROME_E2E=1 (integración real con Chrome).
func TestChromeLaunchAndConnect(t *testing.T) {
	if os.Getenv("CHROME_E2E") != "1" {
		t.Skip("skip: requiere CHROME_E2E=1 y Chrome real")
	}

	// Verificar que Chrome esta disponible
	if _, err := findChrome(); err != nil {
		t.Skipf("Chrome no disponible: %v", err)
	}

	opts := ChromeLaunchOpts{
		Port:        9223, // puerto alternativo para no interferir con sesiones existentes
		UserDataDir: t.TempDir(),
		Headless:    true,
	}

	pid, err := ChromeLaunch(opts)
	if err != nil {
		t.Fatalf("ChromeLaunch: %v", err)
	}
	t.Logf("Chrome lanzado con PID %d en puerto %d", pid, opts.Port)

	defer func() {
		if err := CdpClose(nil, pid); err != nil {
			t.Logf("CdpClose (pid only): %v", err)
		}
	}()

	// Conectar CDP
	conn, err := CdpConnect(opts.Port)
	if err != nil {
		t.Fatalf("CdpConnect: %v", err)
	}
	defer func() {
		if err := CdpClose(conn, 0); err != nil {
			t.Logf("CdpClose (conn): %v", err)
		}
	}()

	// Navegar a about:blank
	if err := CdpNavigate(conn, "about:blank"); err != nil {
		t.Fatalf("CdpNavigate about:blank: %v", err)
	}
	t.Log("Navegacion a about:blank exitosa")
}

// TestCdpEvaluate ejecuta JS simple en Chrome y verifica el resultado.
// Requiere CHROME_E2E=1.
func TestCdpEvaluate(t *testing.T) {
	if os.Getenv("CHROME_E2E") != "1" {
		t.Skip("skip: requiere CHROME_E2E=1 y Chrome real")
	}

	if _, err := findChrome(); err != nil {
		t.Skipf("Chrome no disponible: %v", err)
	}

	opts := ChromeLaunchOpts{
		Port:        9224,
		UserDataDir: t.TempDir(),
		Headless:    true,
	}

	pid, err := ChromeLaunch(opts)
	if err != nil {
		t.Fatalf("ChromeLaunch: %v", err)
	}
	defer CdpClose(nil, pid)

	conn, err := CdpConnect(opts.Port)
	if err != nil {
		t.Fatalf("CdpConnect: %v", err)
	}
	defer CdpClose(conn, 0)

	if err := CdpNavigate(conn, "about:blank"); err != nil {
		t.Fatalf("CdpNavigate: %v", err)
	}

	t.Run("expresion aritmetica simple", func(t *testing.T) {
		result, err := CdpEvaluate(conn, "1 + 2")
		if err != nil {
			t.Fatalf("CdpEvaluate: %v", err)
		}
		if result != "3" {
			t.Errorf("esperado '3', got %q", result)
		}
	})

	t.Run("string literal", func(t *testing.T) {
		result, err := CdpEvaluate(conn, `"hola mundo"`)
		if err != nil {
			t.Fatalf("CdpEvaluate: %v", err)
		}
		if result != "hola mundo" {
			t.Errorf("esperado 'hola mundo', got %q", result)
		}
	})

	t.Run("typeof window", func(t *testing.T) {
		result, err := CdpEvaluate(conn, "typeof window")
		if err != nil {
			t.Fatalf("CdpEvaluate: %v", err)
		}
		if result != "object" {
			t.Errorf("esperado 'object', got %q", result)
		}
	})
}

// TestCdpGetHTML obtiene el HTML de about:blank y verifica que contiene elementos basicos.
// Requiere CHROME_E2E=1.
func TestCdpGetHTML(t *testing.T) {
	if os.Getenv("CHROME_E2E") != "1" {
		t.Skip("skip: requiere CHROME_E2E=1 y Chrome real")
	}

	if _, err := findChrome(); err != nil {
		t.Skipf("Chrome no disponible: %v", err)
	}

	opts := ChromeLaunchOpts{
		Port:        9225,
		UserDataDir: t.TempDir(),
		Headless:    true,
	}

	pid, err := ChromeLaunch(opts)
	if err != nil {
		t.Fatalf("ChromeLaunch: %v", err)
	}
	defer CdpClose(nil, pid)

	conn, err := CdpConnect(opts.Port)
	if err != nil {
		t.Fatalf("CdpConnect: %v", err)
	}
	defer CdpClose(conn, 0)

	if err := CdpNavigate(conn, "about:blank"); err != nil {
		t.Fatalf("CdpNavigate: %v", err)
	}

	t.Run("html de about blank contiene html y body", func(t *testing.T) {
		html, err := CdpGetHTML(conn)
		if err != nil {
			t.Fatalf("CdpGetHTML: %v", err)
		}
		lower := strings.ToLower(html)
		if !strings.Contains(lower, "<html") {
			t.Errorf("HTML no contiene tag <html>: %q", html[:min(200, len(html))])
		}
		if !strings.Contains(lower, "<body") {
			t.Errorf("HTML no contiene tag <body>: %q", html[:min(200, len(html))])
		}
		t.Logf("HTML (primeros 200 chars): %s", html[:min(200, len(html))])
	})
}

// TestCdpScreenshot toma un screenshot de about:blank y verifica que se crea el archivo PNG.
// Requiere CHROME_E2E=1.
func TestCdpScreenshot(t *testing.T) {
	if os.Getenv("CHROME_E2E") != "1" {
		t.Skip("skip: requiere CHROME_E2E=1 y Chrome real")
	}

	if _, err := findChrome(); err != nil {
		t.Skipf("Chrome no disponible: %v", err)
	}

	opts := ChromeLaunchOpts{
		Port:        9226,
		UserDataDir: t.TempDir(),
		Headless:    true,
	}

	pid, err := ChromeLaunch(opts)
	if err != nil {
		t.Fatalf("ChromeLaunch: %v", err)
	}
	defer CdpClose(nil, pid)

	conn, err := CdpConnect(opts.Port)
	if err != nil {
		t.Fatalf("CdpConnect: %v", err)
	}
	defer CdpClose(conn, 0)

	if err := CdpNavigate(conn, "about:blank"); err != nil {
		t.Fatalf("CdpNavigate: %v", err)
	}

	// Esperar un momento para que la pagina cargue
	time.Sleep(500 * time.Millisecond)

	t.Run("screenshot png se crea correctamente", func(t *testing.T) {
		outputPath := t.TempDir() + "/screenshot.png"
		err := CdpScreenshot(conn, outputPath, CdpScreenshotOpts{FullPage: false})
		if err != nil {
			t.Fatalf("CdpScreenshot: %v", err)
		}

		info, err := os.Stat(outputPath)
		if err != nil {
			t.Fatalf("screenshot no encontrado: %v", err)
		}
		if info.Size() == 0 {
			t.Error("screenshot vacio (0 bytes)")
		}
		t.Logf("Screenshot creado: %s (%d bytes)", outputPath, info.Size())
	})
}

// TestCdpPortResponds verifica el sondeo TCP del puerto CDP sin Chrome real:
// un net.Listener local hace de "puerto ocupado" y, al cerrarlo, el puerto
// deja de responder.
func TestCdpPortResponds(t *testing.T) {
	ln, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("listen: %v", err)
	}
	port := ln.Addr().(*net.TCPAddr).Port

	if !cdpPortResponds(port) {
		t.Errorf("cdpPortResponds(%d) = false con listener vivo, want true", port)
	}

	if err := ln.Close(); err != nil {
		t.Fatalf("close listener: %v", err)
	}
	if cdpPortResponds(port) {
		t.Errorf("cdpPortResponds(%d) = true tras cerrar el listener, want false", port)
	}
}

// TestChromeLaunchReuseExisting verifica que con ReuseExisting=true y un puerto
// ya ocupado, ChromeLaunch NO lanza Chrome y devuelve (0, nil). No requiere
// Chrome real: el listener simula un endpoint CDP vivo. Esto es la guarda
// anti-duplicado que evita el leak de procesos chromium huerfanos.
func TestChromeLaunchReuseExisting(t *testing.T) {
	ln, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("listen: %v", err)
	}
	defer ln.Close()
	port := ln.Addr().(*net.TCPAddr).Port

	pid, err := ChromeLaunch(ChromeLaunchOpts{Port: port, ReuseExisting: true})
	if err != nil {
		t.Fatalf("ChromeLaunch(ReuseExisting): %v", err)
	}
	if pid != 0 {
		t.Errorf("pid = %d, want 0 (debe reusar el existente sin lanzar Chrome)", pid)
	}
}
