"""E2E tests for terminal_panel demos in primitives_gallery.

Lanza primitives_gallery en modo --capture, captura el demo "terminal_panel"
como PNG y verifica que la region del terminal tiene fondo oscuro (fix del
issue 0132: fondo negro + prompt input).

Uso desde la raiz del registry:
    python/.venv/bin/python3 -m pytest cpp/tests/e2e/test_terminal_panel_e2e.py -v

Requisitos:
    - primitives_gallery compilado (Linux o Windows .exe).
    - WSL2 con interop habilitado para el path Windows.
    - Pillow instalado en el venv del registry (python/.venv).

En entornos sin GL (CI headless), el binario sale != 0 y el test se skipea
automaticamente (SKIP, no FAIL).
"""

import os
import subprocess
import sys
import tempfile
from pathlib import Path

import pytest

# ---------------------------------------------------------------------------
# Helpers de localizacion del binario
# ---------------------------------------------------------------------------

REGISTRY_ROOT = Path(__file__).resolve().parents[3]  # fn_registry/


def _find_binary() -> Path | None:
    """Devuelve el primer primitives_gallery encontrado (Linux o Windows)."""
    # Paths fijos conocidos primero.
    candidates = [
        REGISTRY_ROOT / "cpp" / "build" / "apps" / "primitives_gallery" / "primitives_gallery",
        REGISTRY_ROOT / "cpp" / "build" / "linux" / "apps" / "primitives_gallery" / "primitives_gallery",
        REGISTRY_ROOT / "cpp" / "build" / "windows" / "apps" / "primitives_gallery" / "primitives_gallery.exe",
        # Desktop de Windows (deploy anterior)
        Path("/mnt/c/Users/lucas/Desktop/apps/primitives_gallery/primitives_gallery.exe"),
    ]
    for p in candidates:
        if p.exists():
            return p
    # Busqueda amplia como fallback.
    for pattern in ("primitives_gallery", "primitives_gallery.exe"):
        for found in (REGISTRY_ROOT / "cpp" / "build").rglob(pattern):
            if found.is_file():
                return found
    return None


# ---------------------------------------------------------------------------
# Fixture: captura PNG del demo terminal_panel
# ---------------------------------------------------------------------------

@pytest.fixture(scope="module")
def terminal_png(tmp_path_factory) -> Path:
    """Lanza primitives_gallery --capture y devuelve el PNG generado."""
    binary = _find_binary()
    if binary is None:
        pytest.skip("primitives_gallery binary not found — build it first")

    out_dir = tmp_path_factory.mktemp("terminal_capture")

    # En WSL, un .exe Windows necesita invocarse como proceso Windows.
    # En Linux, se invoca directamente con LIBGL_ALWAYS_SOFTWARE=1.
    env = os.environ.copy()
    is_windows_exe = binary.suffix == ".exe"

    if is_windows_exe:
        # Convertir el out_dir a path Windows via wslpath.
        wslpath_result = subprocess.run(
            ["wslpath", "-w", str(out_dir)],
            capture_output=True, text=True
        )
        if wslpath_result.returncode != 0:
            pytest.skip("wslpath not available — can't convert path for Windows exe")
        win_out_dir = wslpath_result.stdout.strip()
        cmd = [str(binary), "--capture", win_out_dir]
    else:
        env["LIBGL_ALWAYS_SOFTWARE"] = "1"
        cmd = [str(binary), "--capture", str(out_dir)]

    result = subprocess.run(
        cmd,
        capture_output=True,
        text=True,
        env=env,
        cwd=str(REGISTRY_ROOT),
        timeout=60,
    )

    if result.returncode != 0:
        # Sin GL o sin display — skip en lugar de FAIL.
        pytest.skip(
            f"primitives_gallery --capture exited {result.returncode} "
            f"(no GL context?). stdout: {result.stdout[-200:]} "
            f"stderr: {result.stderr[-200:]}"
        )

    png_path = out_dir / "terminal_panel.png"
    if not png_path.exists():
        pytest.skip(f"terminal_panel.png not generated in {out_dir}. "
                    f"stdout: {result.stdout[-300:]}")

    return png_path


# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------

def test_terminal_panel_png_exists(terminal_png: Path):
    """El PNG del demo terminal_panel debe existir despues del capture."""
    assert terminal_png.exists(), f"PNG not found: {terminal_png}"
    assert terminal_png.stat().st_size > 1000, "PNG sospechosamente pequeño"


def test_terminal_panel_not_all_white(terminal_png: Path):
    """La imagen no debe ser completamente blanca (render vacio)."""
    try:
        from PIL import Image
    except ImportError:
        pytest.skip("Pillow not installed — run: pip install Pillow")

    img = Image.open(terminal_png).convert("RGB")
    px = img.load()
    w, h = img.size
    total = w * h
    white_count = sum(
        1
        for y in range(h)
        for x in range(w)
        if px[x, y][0] > 240 and px[x, y][1] > 240 and px[x, y][2] > 240  # type: ignore[index]
    )
    white_ratio = white_count / total

    assert white_ratio < 0.95, (
        f"Image is {white_ratio:.1%} white — terminal render likely failed. "
        f"({terminal_png})"
    )


def test_terminal_panel_dark_background(terminal_png: Path):
    """La region central del terminal debe ser mayormente oscura (fondo negro fix 0132)."""
    try:
        from PIL import Image
    except ImportError:
        pytest.skip("Pillow not installed — run: pip install Pillow")

    img = Image.open(terminal_png).convert("RGB")
    w, h = img.size

    # Recortar la region central-inferior (donde vive el scrollback del terminal).
    # El demo header ocupa ~15% superior; el resto deberia ser el area del terminal.
    # Ajustar: top=20%, bottom=85%, left=10%, right=90%.
    left   = int(w * 0.10)
    right  = int(w * 0.90)
    top    = int(h * 0.20)
    bottom = int(h * 0.85)

    region = img.crop((left, top, right, bottom))
    rw, rh = region.size
    total = rw * rh

    if total == 0:
        pytest.skip("Crop region empty — image too small?")

    rpx = region.load()
    # Pixel oscuro: todos los canales RGB < 60.
    dark_count = sum(
        1
        for y in range(rh)
        for x in range(rw)
        if rpx[x, y][0] < 60 and rpx[x, y][1] < 60 and rpx[x, y][2] < 60  # type: ignore[index]
    )
    dark_ratio = dark_count / total

    assert dark_ratio >= 0.30, (
        f"Terminal region has only {dark_ratio:.1%} dark pixels (expected >= 30%). "
        f"The black background fix (issue 0132) may not be active. "
        f"Region: ({left},{top})-({right},{bottom}) in {w}x{h} image. "
        f"({terminal_png})"
    )


def test_terminal_panel_has_light_text_on_dark(terminal_png: Path):
    """Debe haber pixels claros (texto/toolbar) sobre fondo oscuro — render activo.

    En modo --capture el PTY reader es async y puede no entregar output en los
    primeros frames. Verificamos que al menos la toolbar (Clear/Copy/Reset) y el
    borde del child tienen pixels no-negros (> 0.3% de la imagen total), lo que
    confirma que el panel se renderizo.
    """
    try:
        from PIL import Image
    except ImportError:
        pytest.skip("Pillow not installed — run: pip install Pillow")

    img = Image.open(terminal_png).convert("RGB")
    pixels = img.load()
    w, h = img.size
    total = w * h

    # Contar pixels con al menos un canal > 60 en toda la imagen.
    # Incluye la toolbar (botones), bordes, prompt "$ " y cualquier output.
    light_count = sum(
        1
        for y in range(h)
        for x in range(w)
        if max(pixels[x, y]) > 60  # type: ignore[index]
    )
    light_ratio = light_count / total

    # Umbral conservador: > 0.3% — basta con que la toolbar sea visible.
    # En modo interactivo con PTY output el ratio sera mucho mayor (> 5%).
    assert light_ratio >= 0.003, (
        f"Image has only {light_ratio:.2%} non-dark pixels — "
        f"terminal panel may not be rendering at all. "
        f"Check that fn_term::render is called and ImGui window is visible. "
        f"({terminal_png})"
    )
