"""
Lightweight helpers to check Chromium-based browser tab count and close current tab.
Uses pywinauto (UI Automation) when available; falls back gracefully otherwise.
"""

from __future__ import annotations

from typing import Optional, Set
import subprocess
import sys
from subprocess import DEVNULL


def _ps_get_chrome_processes() -> list[dict]:
    """Return a list of chrome.exe processes with Id and CommandLine via PowerShell CIM.
    Returns [] on failure.
    """
    try:
        ps_cmd = (
            "Get-CimInstance Win32_Process | Where-Object { $_.Name -eq 'chrome.exe' } | "
            "Select-Object ProcessId,CommandLine | ConvertTo-Json -Compress"
        )
        res = subprocess.run(
            ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", ps_cmd],
            capture_output=True, text=True, check=False
        )
        out = (res.stdout or "").strip()
        if not out:
            return []
        import json as _json
        data = _json.loads(out)
        if isinstance(data, dict):
            return [data]
        if isinstance(data, list):
            return data
        return []
    except Exception:
        return []


def get_chromium_tab_count() -> Optional[int]:
    """Return the number of visible tab items in the active Chromium window.

    Attempts UIA enumeration via pywinauto. Returns None if unavailable.
    """
    try:
        from pywinauto import Desktop  # type: ignore
        desktop = Desktop(backend="uia")
        wins = desktop.windows()
        # Pick a likely Chromium window (Chrome/Edge) that is active or topmost
        candidates = []
        for w in wins:
            try:
                title = (w.window_text() or "").lower()
                cls = (w.class_name() or "").lower()
                if "chrome" in title or "edge" in title or cls == "chrome_widgetwin_1":
                    candidates.append(w)
            except Exception:
                pass
        if not candidates:
            return None
        # Choose the first visible candidate
        main = None
        for w in candidates:
            try:
                if w.is_visible():
                    main = w
                    break
            except Exception:
                continue
        if main is None:
            main = candidates[0]
        # Count TabItem descendants
        try:
            tabs = main.descendants(control_type="TabItem")
            return len(tabs) if tabs is not None else None
        except Exception:
            return None
    except Exception:
        return None


def focus_chromium_window() -> bool:
    """Bring the foremost Chromium window (Chrome/Edge) to the foreground.
    Returns True if a window was focused.
    """
    # Linux: best-effort via wmctrl/xdotool
    if sys.platform != 'win32':
        try:
            return _linux_focus_chromium_window()
        except Exception:
            return False
    try:
        from pywinauto import Desktop  # type: ignore
        desktop = Desktop(backend="uia")
        wins = desktop.windows()
        # Order of preference: active/visible Chrome windows
        for w in wins:
            try:
                title = (w.window_text() or "").lower()
                cls = (w.class_name() or "").lower()
                if ("chrome" in title or "edge" in title or cls == "chrome_widgetwin_1") and w.is_visible():
                    try:
                        w.set_focus()
                        return True
                    except Exception:
                        continue
            except Exception:
                continue
    except Exception:
        pass
    return False


def close_current_tab_via_hotkey() -> bool:
    """Close the current browser tab using Ctrl+W hotkey.
    Returns True if the hotkey was sent without raising.
    """
    try:
        import time as _t
        import pyautogui as _pg  # type: ignore
        # Try to ensure the browser has focus first
        try:
            focused = focus_chromium_window()
            if not focused:
                # As a lightweight heuristic, send Alt+Tab once to leave our app
                try:
                    _pg.hotkey('alt', 'tab')
                except Exception:
                    pass
            _t.sleep(0.15)
        except Exception:
            pass
        try:
            _pg.hotkey('ctrl', 'w')
            return True
        except Exception:
            return False
    except Exception:
        return False


def close_extra_tabs(max_retries: int = 4, sleep_s: float = 0.25) -> bool:
    """Best-effort close of any additional Chrome/Edge tabs via Ctrl+W.

    - Ensures window focus
    - Repeats Ctrl+W up to max_retries while tab count > 1
    Returns True if tab count decreased to 1 at any point.
    """
    try:
        import time as _t
        base = get_chromium_tab_count() or 0
        # Focus first
        focus_chromium_window()
        _t.sleep(0.15)
        for _ in range(max_retries):
            cnt = get_chromium_tab_count() or 0
            if cnt <= 1:
                return True
            close_current_tab_via_hotkey()
            _t.sleep(sleep_s)
        # Final check
        cnt = get_chromium_tab_count() or 0
        return cnt <= 1 or (base and cnt < base)
    except Exception:
        return False


def switch_to_first_tab() -> bool:
    """Focus Chrome/Edge and send Ctrl+1 to switch to the first tab."""
    # Linux path: use xdotool/wmctrl
    if sys.platform != 'win32':
        try:
            return _linux_switch_to_first_tab()
        except Exception:
            return False
    try:
        import time as _t
        import pyautogui as _pg  # type: ignore

        # Step 1: Focus a Chromium window if possible
        focused = focus_chromium_window()
        _t.sleep(0.15)

        # Step 2: Try sending Ctrl+1 via pyautogui a couple of times
        for _ in range(2):
            try:
                if not focused:
                    try:
                        # Lightweight attempt to move focus away from our app to the browser
                        _pg.hotkey('alt', 'tab')
                        _t.sleep(0.15)
                    except Exception:
                        pass
                _pg.hotkey('ctrl', '1')
                return True
            except Exception:
                _t.sleep(0.10)

        # Step 3: UIA fallback: send ^1 directly to the browser window
        try:
            w = _uia_find_chromium_window()
            if w:
                did_switch = False
                try:
                    w.set_focus()
                except Exception:
                    pass
                try:
                    w.type_keys('^1')
                    did_switch = True
                except Exception:
                    pass
                # Step 3b: Try selecting/clicking the first TabItem explicitly to enforce focus
                try:
                    tabs = w.descendants(control_type="TabItem")
                    if tabs:
                        try:
                            if hasattr(tabs[0], 'select'):
                                tabs[0].select()  # type: ignore[attr-defined]
                                did_switch = True
                        except Exception:
                            pass
                        try:
                            tabs[0].wrapper_object().click_input()
                            did_switch = True
                        except Exception:
                            pass
                except Exception:
                    pass
                if did_switch:
                    return True
        except Exception:
            pass

        # Step 4: As a last resort, use WinAPI to synthesize Ctrl+1
        try:
            _send_ctrl_1_via_winapi()
            return True
        except Exception:
            pass

        return False
    except Exception:
        return False


def _send_ctrl_1_via_winapi() -> None:
    """Send Ctrl+1 using WinAPI keybd_event as a last-resort fallback."""
    import ctypes
    import time as _t
    user32 = ctypes.windll.user32
    VK_CONTROL = 0x11
    VK_1 = 0x31
    KEYEVENTF_KEYUP = 0x0002
    try:
        # Press Ctrl down
        user32.keybd_event(VK_CONTROL, 0, 0, 0)
        _t.sleep(0.01)
        # Press 1 down
        user32.keybd_event(VK_1, 0, 0, 0)
        _t.sleep(0.01)
        # Release 1
        user32.keybd_event(VK_1, 0, KEYEVENTF_KEYUP, 0)
        _t.sleep(0.01)
        # Release Ctrl
        user32.keybd_event(VK_CONTROL, 0, KEYEVENTF_KEYUP, 0)
    except Exception:
        # Best effort; ignore errors
        pass


def _linux_focus_chromium_window() -> bool:
    """Focus a visible Chromium browser window on Linux using wmctrl/xdotool."""
    try:
        def _run(*args):
            try:
                subprocess.run(args, check=False, stdout=DEVNULL, stderr=DEVNULL)
                return True
            except Exception:
                return False

        # Try to activate using wmctrl by class
        classes = ['google-chrome.Google-chrome', 'chromium.Chromium', 'microsoft-edge.Microsoft-edge']
        activated = False
        for cls in classes:
            if _run('wmctrl', '-x', '-a', cls):
                activated = True
        # If wmctrl not effective, try xdotool search and activate the largest visible Chrome window
        try:
            wins = []
            for pat in ['chrome', 'google-chrome', 'chromium', 'microsoft-edge']:
                try:
                    out = subprocess.run(('xdotool', 'search', '--onlyvisible', '--class', pat), capture_output=True, text=True, check=False)
                    ids = [w.strip() for w in (out.stdout or '').split() if w.strip().isdigit()]
                    wins.extend(ids)
                except Exception:
                    continue
            # Deduplicate while preserving order
            seen = set()
            ordered = []
            for w in wins:
                if w not in seen:
                    ordered.append(w); seen.add(w)
            for wid in ordered:
                _run('xdotool', 'windowactivate', '--sync', wid)
                activated = True
                break
        except Exception:
            pass

        return activated
    except Exception:
        return False


def _linux_switch_to_first_tab() -> bool:
    """Switch to first tab on Linux via xdotool after focusing the browser."""
    try:
        import time as _t

        def _run(*args):
            try:
                subprocess.run(args, check=False, stdout=DEVNULL, stderr=DEVNULL)
                return True
            except Exception:
                return False

        _linux_focus_chromium_window()
        _t.sleep(0.12)

        # Strategy 1: Clear modifiers and send Ctrl+1 to the active window
        if _run('xdotool', 'key', '--clearmodifiers', 'ctrl+1'):
            return True
        _t.sleep(0.08)

        # Strategy 2: Find visible Chrome windows and target the first one explicitly
        windows = []
        for pat in ['google-chrome', 'chrome', 'chromium', 'microsoft-edge']:
            try:
                out = subprocess.run(('xdotool', 'search', '--onlyvisible', '--class', pat), capture_output=True, text=True, check=False)
                ids = [w.strip() for w in (out.stdout or '').split() if w.strip().isdigit()]
                for wid in ids:
                    if wid not in windows:
                        windows.append(wid)
            except Exception:
                continue
        for wid in windows:
            _run('xdotool', 'windowactivate', '--sync', wid)
            _t.sleep(0.05)
            if _run('xdotool', 'key', '--window', wid, '--clearmodifiers', 'ctrl+1'):
                return True

        # Strategy 3: Explicit key sequence (keydown ctrl, key 1, keyup ctrl) to active window
        if _run('xdotool', 'keydown', 'ctrl') and _run('xdotool', 'key', '1') and _run('xdotool', 'keyup', 'ctrl'):
            return True

        # Strategy 4: wmctrl raise attempts then repeat Strategy 1
        for cls in ['google-chrome.Google-chrome', 'chromium.Chromium', 'microsoft-edge.Microsoft-edge']:
            _run('wmctrl', '-x', '-a', cls)
            _t.sleep(0.05)
            if _run('xdotool', 'key', '--clearmodifiers', 'ctrl+1'):
                return True

        return False
    except Exception:
        return False


def _uia_find_chromium_window():
    """Return a pywinauto WindowSpecification for the foremost Chromium window, or None."""
    try:
        from pywinauto import Desktop  # type: ignore
        desktop = Desktop(backend="uia")
        wins = desktop.windows()
        for w in wins:
            try:
                title = (w.window_text() or "").lower()
                cls = (w.class_name() or "").lower()
                if ("chrome" in title or "edge" in title or cls == "chrome_widgetwin_1") and w.is_visible():
                    return w
            except Exception:
                continue
    except Exception:
        return None
    return None


def close_tabs_via_uia(max_retries: int = 3, sleep_s: float = 0.25) -> bool:
    """Use UI Automation (pywinauto) to send ^W directly to the Chrome window.

    This avoids system-wide hotkeys being ignored if our app has focus.
    Returns True if tab count decreased to 1.
    """
    try:
        import time as _t
        w = _uia_find_chromium_window()
        if not w:
            return False
        # initial count
        try:
            tabs = w.descendants(control_type="TabItem")
            base = len(tabs)
        except Exception:
            base = 0
        for _ in range(max_retries):
            try:
                w.set_focus()
                # send ^w to this window specifically
                w.type_keys("^w")
            except Exception:
                pass
            _t.sleep(sleep_s)
            try:
                tabs = w.descendants(control_type="TabItem")
                if len(tabs) <= 1:
                    return True
            except Exception:
                # if we cannot count, still attempt a few times
                continue
        # final heuristic: success if tab count decreased
        try:
            tabs = w.descendants(control_type="TabItem")
            return (base and len(tabs) < base)
        except Exception:
            return False
    except Exception:
        return False


# --- Window-handle based helpers (covers new top-level windows) ---------------
def get_chrome_window_handles() -> Set[int]:
    """Return HWNDs of visible Chrome/Edge top-level windows (best-effort)."""
    handles: Set[int] = set()
    try:
        import win32gui  # type: ignore
        import win32con  # type: ignore

        def _enum_cb(hwnd, _param):
            try:
                if not win32gui.IsWindowVisible(hwnd):
                    return
                cls = (win32gui.GetClassName(hwnd) or "").lower()
                if cls == "chrome_widgetwin_1":
                    handles.add(int(hwnd))
            except Exception:
                pass

        win32gui.EnumWindows(_enum_cb, None)
    except Exception:
        # Fallback via pywinauto
        try:
            from pywinauto import Desktop  # type: ignore
            for w in Desktop(backend="uia").windows():
                try:
                    if w.is_visible() and (w.class_name() or "").lower() == "chrome_widgetwin_1":
                        handles.add(int(w.handle))
                except Exception:
                    pass
        except Exception:
            return set()
    return handles


def close_window_by_hwnd(hwnd: int) -> bool:
    """Request close of a top-level window via WM_CLOSE (best-effort)."""
    try:
        import win32gui  # type: ignore
        import win32con  # type: ignore
        win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
        return True
    except Exception:
        # Fallback via pywinauto
        try:
            from pywinauto.application import Application  # type: ignore
            Application(backend="uia").connect(handle=hwnd).window(handle=hwnd).close()
            return True
        except Exception:
            return False


def close_new_chrome_windows(baseline_hwnds: Set[int]) -> int:
    """Close any new top-level Chrome windows not present in baseline.

    Returns the count of windows for which a close request was sent.
    """
    try:
        current = get_chrome_window_handles()
        extra = [h for h in current if h not in (baseline_hwnds or set())]
        count = 0
        for h in extra:
            if close_window_by_hwnd(h):
                count += 1
        return count
    except Exception:
        return 0


def get_chrome_pids() -> Set[int]:
    """Return current set of chrome.exe PIDs (best effort)."""
    try:
        procs = _ps_get_chrome_processes()
        return {int(p.get("ProcessId")) for p in procs if p.get("ProcessId") is not None}
    except Exception:
        return set()


def kill_new_renderer_processes(baseline_pids: Set[int]) -> int:
    """Kill chrome.exe renderer processes not in baseline.

    Returns number of PIDs terminated. Best-effort, safe to call even if baseline is empty.
    """
    try:
        procs = _ps_get_chrome_processes()
        to_kill: list[int] = []
        for p in procs:
            try:
                pid = int(p.get("ProcessId"))
            except Exception:
                continue
            if pid in (baseline_pids or set()):
                continue
            cmd = str(p.get("CommandLine") or "").lower()
            # Only kill renderer/GPU/utility-type processes; avoid killing the browser process itself
            if "--type=renderer" in cmd or "--type=gpu-process" in cmd or "--type=utility" in cmd:
                to_kill.append(pid)
        killed = 0
        for pid in to_kill:
            try:
                subprocess.run(["taskkill", "/F", "/PID", str(pid)], check=False, capture_output=True)
                killed += 1
            except Exception:
                pass
        return killed
    except Exception:
        return 0


def kill_extra_chrome_processes(baseline_pids: Set[int]) -> int:
    """Kill all chrome.exe PIDs that are not in the baseline.

    This is a stronger approach than renderer-only termination. It may close
    entire browser windows opened after baseline capture. Returns number killed.
    """
    try:
        procs = _ps_get_chrome_processes()
        to_kill: list[int] = []
        base = baseline_pids or set()
        for p in procs:
            try:
                pid = int(p.get("ProcessId"))
            except Exception:
                continue
            if pid not in base:
                to_kill.append(pid)
        killed = 0
        for pid in to_kill:
            try:
                subprocess.run(["taskkill", "/F", "/PID", str(pid)], check=False, capture_output=True)
                killed += 1
            except Exception:
                pass
        return killed
    except Exception:
        return 0


