"""在用户图形环境中打开默认浏览器或系统文件管理器（Win / macOS / Linux）。

无显示、SSH、无桌面会话时可能失败；所有入口捕获异常并返回 ``{"ok": false, ...}``，不向调用方抛错。
"""

from __future__ import annotations

import os
import shutil
import subprocess
import sys
import webbrowser
from pathlib import Path
from typing import Any, Callable
from urllib.parse import urlparse
from urllib.request import url2pathname

from aicd.network_policy import outbound_tool_url_blocked_reason


def _popen_detached(argv: list[str]) -> subprocess.Popen | None:
    """启动脱离终端的子进程；失败返回 None。"""
    try:
        kw: dict[str, Any] = {
            "stdin": subprocess.DEVNULL,
            "stdout": subprocess.DEVNULL,
            "stderr": subprocess.DEVNULL,
            "close_fds": True,
        }
        if sys.platform == "win32":
            # 避免闪出控制台窗口
            cr = getattr(subprocess, "CREATE_NO_WINDOW", 0)
            if cr:
                kw["creationflags"] = cr
        else:
            kw["start_new_session"] = True
        return subprocess.Popen([str(x) for x in argv], **kw)
    except Exception:
        return None


def _file_url_local_path(url: str) -> Path | None:
    """从 ``file://`` URL 解析本地路径；无效则 None。"""
    try:
        parsed = urlparse(url.strip())
        if (parsed.scheme or "").lower() != "file":
            return None
        path_part = parsed.path or ""
        if not path_part:
            return None
        local = url2pathname(path_part)
        return Path(local) if local else None
    except Exception:
        return None


def open_url_with_browser(
    url: str,
    *,
    policy_check: Callable[[Path], None] | None = None,
    block_external_network: bool = False,
) -> dict[str, Any]:
    """
    使用系统默认浏览器打开 ``http(s):`` 或已授权路径的 ``file:`` URL。

    - ``policy_check``: 对 ``file:`` 指向的本地路径做 PathPolicy 校验；为 None 则拒绝 file。
    """
    err_empty = "url is empty"
    raw = (url or "").strip()
    if not raw:
        return {"ok": False, "opened": False, "error": err_empty}

    try:
        parsed = urlparse(raw)
    except Exception as e:
        return {"ok": False, "opened": False, "error": f"invalid url: {e!s}"}

    scheme = (parsed.scheme or "").lower()
    if scheme not in ("http", "https", "file"):
        return {
            "ok": False,
            "opened": False,
            "error": f"unsupported url scheme (use http, https, or file): {scheme or '(missing)'}",
        }

    if scheme == "file":
        if policy_check is None:
            return {
                "ok": False,
                "opened": False,
                "error": "file URLs require path policy check — use workspace paths only",
            }
        local = _file_url_local_path(raw)
        if local is None:
            return {"ok": False, "opened": False, "error": "could not parse file:// path"}
        try:
            local = local.resolve()
        except OSError:
            pass
        try:
            policy_check(local)
        except PermissionError as e:
            return {"ok": False, "opened": False, "error": str(e)}
        except Exception as e:
            return {"ok": False, "opened": False, "error": f"path policy error: {e!s}"}

    if scheme in ("http", "https"):
        br = outbound_tool_url_blocked_reason(
            raw, block_external=block_external_network
        )
        if br:
            return {
                "ok": False,
                "opened": False,
                "error": br,
                "network_policy": "block_external_network",
            }

    try:
        opened = bool(webbrowser.open(raw, new=1))
    except Exception as e:
        return {
            "ok": False,
            "opened": False,
            "error": f"webbrowser.open failed: {e!s}",
            "headless_hint": "no desktop or no default browser",
        }

    out: dict[str, Any] = {"ok": opened, "opened": opened, "method": "webbrowser.open"}
    if not opened:
        out["error"] = "webbrowser.open returned False (common on headless/SSH)"
        out["headless_hint"] = "no DISPLAY / no GUI session / no handler registered"
    return out


def reveal_path_in_file_manager(path: Path) -> dict[str, Any]:
    """
    在系统文件管理器中显示 ``path``（文件则选中，目录则打开）。

    ``path`` 可不存在：尽量打开其父目录。
    """
    try:
        p = path.expanduser()
        try:
            p = p.resolve()
        except OSError:
            p = Path(os.path.abspath(str(p)))
    except Exception as e:
        return {"ok": False, "revealed": False, "error": f"bad path: {e!s}"}

    plat = sys.platform

    if plat == "win32":
        try:
            if p.is_file():
                proc = _popen_detached(["explorer", "/select,", str(p)])
                if proc is None:
                    return {"ok": False, "revealed": False, "error": "explorer /select failed to start"}
                return {"ok": True, "revealed": True, "method": "explorer /select"}
            if p.is_dir():
                proc = _popen_detached(["explorer", str(p)])
                if proc is None:
                    # os.startfile 无控制台
                    try:
                        os.startfile(str(p))  # type: ignore[attr-defined]
                        return {"ok": True, "revealed": True, "method": "os.startfile (dir)"}
                    except Exception as e:
                        return {"ok": False, "revealed": False, "error": str(e)}
                return {"ok": True, "revealed": True, "method": "explorer (dir)"}
            parent = p.parent
            if parent.is_dir():
                proc = _popen_detached(["explorer", str(parent)])
                if proc is None:
                    try:
                        os.startfile(str(parent))  # type: ignore[attr-defined]
                        return {"ok": True, "revealed": True, "method": "os.startfile (parent dir)"}
                    except Exception as e:
                        return {"ok": False, "revealed": False, "error": str(e)}
                return {"ok": True, "revealed": True, "method": "explorer (parent dir)"}
        except Exception as e:
            return {"ok": False, "revealed": False, "error": str(e)}
        return {"ok": False, "revealed": False, "error": "path has no existing parent folder"}

    if plat == "darwin":
        try:
            if p.exists() and p.is_file():
                proc = _popen_detached(["open", "-R", str(p)])
            elif p.exists() and p.is_dir():
                proc = _popen_detached(["open", str(p)])
            else:
                par = p.parent
                if par.is_dir():
                    proc = _popen_detached(["open", str(par)])
                else:
                    return {"ok": False, "revealed": False, "error": "parent directory missing"}
            if proc is None:
                return {"ok": False, "revealed": False, "error": "open command failed to start"}
            return {"ok": True, "revealed": True, "method": "open (Finder)"}
        except Exception as e:
            return {"ok": False, "revealed": False, "error": str(e)}

    # Linux 及其他 Unix
    try:
        if p.is_file():
            nautilus = shutil.which("nautilus")
            if nautilus:
                proc = _popen_detached([nautilus, "--select", str(p)])
                if proc is not None:
                    return {"ok": True, "revealed": True, "method": "nautilus --select"}
            dolphin = shutil.which("dolphin")
            if dolphin:
                proc = _popen_detached([dolphin, "--select", str(p)])
                if proc is not None:
                    return {"ok": True, "revealed": True, "method": "dolphin --select"}
        target = p if p.is_dir() else p.parent
        if not target.exists():
            return {"ok": False, "revealed": False, "error": "nothing to open (folder missing)"}
        xdg = shutil.which("xdg-open")
        if not xdg:
            return {
                "ok": False,
                "revealed": False,
                "error": "xdg-open not found (install xdg-utils or use a desktop session)",
            }
        proc = _popen_detached([xdg, str(target)])
        if proc is None:
            return {"ok": False, "revealed": False, "error": "xdg-open failed to start"}
        out: dict[str, Any] = {
            "ok": True,
            "revealed": True,
            "method": "xdg-open",
        }
        if p.is_file() and not (shutil.which("nautilus") or shutil.which("dolphin")):
            return {
                **out,
                "note": "opened parent folder; install nautilus or dolphin to select the file",
            }
        return out
    except Exception as e:
        return {"ok": False, "revealed": False, "error": str(e)}
