"""Window-focus recovery chain for keyboard automation (P0-3 MVP)."""

from __future__ import annotations

import subprocess
import time
from typing import Callable

from .xdotool_window_activate import run_windowactivate

RecoveryStrategy = str

_MAX_RECOVERY_ATTEMPTS = 3

_BROWSER_CLASSES = (
    "google-chrome",
    "Google-chrome",
    "Chromium",
    "chromium",
    "firefox",
    "Firefox",
    "Brave-browser",
    "brave-browser",
)


def _run(cmd: list[str], *, timeout: float = 12.0) -> tuple[int, str, str]:
    if "windowactivate" in cmd:
        result = run_windowactivate(cmd, timeout=timeout)
        return result.returncode, result.stdout, result.stderr
    try:
        proc = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=timeout,
            check=False,
        )
        return int(proc.returncode), str(proc.stdout or "").strip(), str(proc.stderr or "").strip()
    except (subprocess.TimeoutExpired, FileNotFoundError) as exc:
        return 1, "", str(exc)


def _activate_window(xdotool: str, window_id: str, *, timeout: float = 12.0) -> tuple[bool, str]:
    result = run_windowactivate(
        [xdotool, "windowactivate", "--sync", window_id],
        timeout=timeout,
        window_id=window_id,
    )
    if result.ok:
        time.sleep(0.4)
        return True, window_id
    return False, result.detail


def _xdotool_window_name(xdotool: str, window_id: str) -> str:
    code, out, _ = _run([xdotool, "getwindowname", window_id], timeout=8.0)
    return out if code == 0 else ""


def _xdotool_list_class_windows(xdotool: str, class_name: str) -> list[str]:
    code, out, _ = _run([xdotool, "search", "--class", class_name], timeout=12.0)
    if code != 0:
        return []
    return [wid for wid in out.splitlines() if wid.strip()]


def _try_wmctrl_activate(*, window_name: str = "", class_hint: str = "") -> tuple[bool, str, RecoveryStrategy]:
    wmctrl = __import__("shutil").which("wmctrl")
    if not wmctrl:
        return False, "wmctrl_missing", "wmctrl"
    code, out, err = _run([wmctrl, "-l"], timeout=10.0)
    if code != 0:
        return False, err or out or "wmctrl_list_failed", "wmctrl"
    prefer = str(window_name or "").strip().lower()
    class_lower = str(class_hint or "").strip().lower()
    selected_id = ""
    for line in out.splitlines():
        parts = line.split(None, 3)
        if len(parts) < 4:
            continue
        win_id, _host, _desk, title = parts[0], parts[1], parts[2], parts[3]
        title_lower = title.lower()
        if prefer and prefer in title_lower:
            selected_id = win_id
            break
        if class_lower and class_lower in title_lower:
            selected_id = win_id
    if not selected_id and prefer:
        for line in out.splitlines():
            parts = line.split(None, 3)
            if len(parts) >= 4 and prefer in parts[3].lower():
                selected_id = parts[0]
                break
    if not selected_id:
        return False, "wmctrl_window_not_found", "wmctrl"
    code, out, err = _run([wmctrl, "-i", "-a", selected_id], timeout=10.0)
    if code == 0:
        time.sleep(0.4)
        return True, selected_id, "wmctrl"
    return False, err or out or "wmctrl_activate_failed", "wmctrl"


def _try_xdotool_pid(xdotool: str, pid: int) -> tuple[bool, str, RecoveryStrategy]:
    if pid <= 0:
        return False, "invalid_pid", "xdotool_pid"
    code, out, err = _run([xdotool, "search", "--pid", str(pid)], timeout=10.0)
    if code != 0 or not out.strip():
        return False, err or out or "xdotool_pid_search_failed", "xdotool_pid"
    window_id = out.splitlines()[-1].strip()
    ok, detail = _activate_window(xdotool, window_id)
    if ok:
        return True, window_id, "xdotool_pid"
    return False, detail or "xdotool_pid_activate_failed", "xdotool_pid"


def _try_xdotool_class(xdotool: str, class_name: str) -> tuple[bool, str, RecoveryStrategy]:
    windows = _xdotool_list_class_windows(xdotool, class_name)
    if not windows:
        return False, f"class_not_found:{class_name}", "xdotool_class"
    window_id = windows[-1]
    ok, detail = _activate_window(xdotool, window_id)
    if ok:
        return True, window_id, "xdotool_class"
    return False, detail or "xdotool_class_activate_failed", "xdotool_class"


def _try_xdotool_search_name(xdotool: str, window_name: str) -> tuple[bool, str, RecoveryStrategy]:
    if not window_name:
        return False, "missing_window_name", "xdotool_search"
    result = run_windowactivate(
        [xdotool, "search", "--name", window_name, "windowactivate", "--sync"],
        timeout=12.0,
        window_id=window_name,
    )
    if result.ok:
        time.sleep(0.4)
        return True, window_name, "xdotool_search"
    return False, result.detail or "xdotool_search_failed", "xdotool_search"


def _try_xdotool_browser_class(xdotool: str, *, window_name: str = "") -> tuple[bool, str, RecoveryStrategy]:
    prefer = str(window_name or "").strip().lower()
    for class_name in _BROWSER_CLASSES:
        for window_id in _xdotool_list_class_windows(xdotool, class_name):
            name = _xdotool_window_name(xdotool, window_id)
            if prefer and prefer not in name.lower() and "cursor" in name.lower():
                continue
            ok, detail = _activate_window(xdotool, window_id)
            if ok:
                return True, window_id, "xdotool_class"
    return False, "browser_window_not_found", "xdotool_class"


def browser_window_exists(xdotool: str, *, window_name: str = "") -> tuple[bool, str]:
    """True when a browser window is listed even if activate failed."""
    prefer = str(window_name or "").strip().lower()
    for class_name in _BROWSER_CLASSES:
        for window_id in _xdotool_list_class_windows(xdotool, class_name):
            name = _xdotool_window_name(xdotool, window_id)
            name_lower = name.lower()
            if "cursor" in name_lower:
                continue
            if prefer and prefer not in name_lower:
                continue
            return True, window_id
    for class_name in _BROWSER_CLASSES:
        windows = _xdotool_list_class_windows(xdotool, class_name)
        if windows:
            return True, windows[-1]
    return False, ""


def recover_window_focus(
    xdotool: str,
    *,
    target_app: str = "",
    window_name: str = "",
    launch_pid: int = 0,
    vision_callback: Callable[[], tuple[bool, str]] | None = None,
) -> tuple[bool, str, RecoveryStrategy]:
    """Recovery sequence: wmctrl → xdotool PID → class → search → optional vision."""
    target = str(target_app or "").strip().lower()
    strategies: list[tuple[str, Callable[[], tuple[bool, str, RecoveryStrategy]]]] = []

    strategies.append(("wmctrl", lambda: _try_wmctrl_activate(window_name=window_name, class_hint=target)))
    if launch_pid > 0:
        strategies.append(("xdotool_pid", lambda: _try_xdotool_pid(xdotool, launch_pid)))
    if target == "browser":
        strategies.append(("xdotool_class", lambda: _try_xdotool_browser_class(xdotool, window_name=window_name)))
    elif target == "cursor":
        strategies.append(("xdotool_search", lambda: _try_xdotool_search_name(xdotool, "Cursor")))
    elif window_name:
        strategies.append(("xdotool_search", lambda: _try_xdotool_search_name(xdotool, window_name)))
    strategies.append(
        (
            "xdotool_active",
            lambda: (
                (True, "active", "xdotool_active")
                if run_windowactivate(
                    [xdotool, "getactivewindow", "windowactivate", "--sync"],
                    timeout=10.0,
                ).ok
                else (False, "active_window_failed", "xdotool_active")
            ),
        )
    )

    errors: list[str] = []
    for _name, attempt in strategies[:_MAX_RECOVERY_ATTEMPTS]:
        ok, detail, strategy = attempt()
        if ok:
            return True, detail, strategy
        errors.append(f"{strategy}:{detail}")

    if vision_callback is not None:
        ok, detail = vision_callback()
        if ok:
            return True, detail, "vision"

    if target == "browser":
        visible, window_id = browser_window_exists(xdotool, window_name=window_name)
        if visible:
            return True, f"focus_degraded:{window_id}", "browser_visible"

    return False, "; ".join(errors) or "focus_recovery_exhausted", "none"
