import subprocess
from pathlib import Path
import plistlib
from typing import List, Optional

import psutil
import structlog

from nomo_e2e.models import WebViewInfo

logger = structlog.get_logger(__name__)


def get_bundle_id(pid: int) -> Optional[str]:
    """Get the bundle ID of a macOS process.

    Args:
        pid: Process ID.

    Returns:
        Bundle ID if found, else None.
    """
    try:
        proc = psutil.Process(pid)
        exe_path = proc.exe()
        if not exe_path:
            return None

        # Try to find Info.plist by traversing up the directory tree
        path = Path(exe_path)
        while path.parent != path:
            info_plist = path / "Contents" / "Info.plist"
            if info_plist.exists():
                try:
                    with open(info_plist, "rb") as f:
                        plist = plistlib.load(f)
                        bundle_id = plist.get("CFBundleIdentifier")
                        return str(bundle_id) if bundle_id else None
                except Exception as e:
                    logger.debug("Failed to read Info.plist", path=str(info_plist), error=str(e))
            path = path.parent
    except (psutil.NoSuchProcess, psutil.AccessDenied):
        pass
    except Exception as e:
        logger.debug("Error getting bundle ID", pid=pid, error=str(e))

    return None


def discover_macos_webviews() -> List[WebViewInfo]:
    """Discover WKWebView/WebView processes on macOS.

    Returns:
        List of discovered WebViewInfo objects.
    """
    webviews = []

    # First, get all listening ports to avoid running lsof for every process
    listening_ports = {}
    try:
        cmd = ["lsof", "-iTCP", "-sTCP:LISTEN", "-P", "-n"]
        output = subprocess.check_output(cmd, stderr=subprocess.DEVNULL, text=True)
        lines = output.strip().split("\n")
        if len(lines) > 1:
            for line in lines[1:]:  # Skip header
                parts = line.split()
                if len(parts) >= 9 and "(LISTEN)" in line:
                    try:
                        pid = int(parts[1])
                        address = parts[8]
                        if ":" in address:
                            port_str = address.split(":")[-1]
                            if port_str.isdigit():
                                listening_ports[pid] = int(port_str)
                    except ValueError:
                        continue
    except Exception as e:
        logger.debug("Error running global lsof", error=str(e))

    for proc in psutil.process_iter(["pid", "name"]):
        try:
            pid = proc.info["pid"]
            name = proc.info["name"]

            # Skip kernel task and self
            if pid == 0 or name is None:
                continue

            port = listening_ports.get(pid)
            if port:
                bundle_id = get_bundle_id(pid)
                ws_url = f"ws://127.0.0.1:{port}/devtools/browser"

                info = WebViewInfo(
                    pid=pid,
                    name=name,
                    bundle_id=bundle_id,
                    debug_port=port,
                    debug_ws_url=ws_url,
                    platform="macos",
                )
                webviews.append(info)
                logger.debug("Found WebView process", info=info)

        except (psutil.NoSuchProcess, psutil.AccessDenied):
            continue
        except Exception as e:
            logger.debug("Error inspecting process", pid=proc.info.get("pid"), error=str(e))

    return webviews
