"""Handler for close_fusion command — macOS.

Closes all open documents cleanly via the add-in (avoiding the recovered
documents dialog on next launch), then closes every Fusion window (native
Quit for the main window, AX close for dialogs), with optional force-kill.

IMPORTANT: Always close documents via the add-in BEFORE killing Fusion.
A force-kill causes unsaved document recovery on next launch, which shows a
blocking modal dialog that prevents automation.
"""

import json
import time
import urllib.request
import urllib.error

from handlers import mac_ui as _mac


def _find_windows_by_title(substring: str) -> list:
    """(winid, title) pairs of visible windows whose title contains the substring.

    mac window titles often omit the app name, so ALSO include every normal
    window owned by a Fusion process when searching for Fusion."""
    found = dict(_mac.find_windows_by_title(substring))
    if "autodesk fusion" in substring.lower():
        for w in _mac._fusion_windows():
            found.setdefault(w["id"], w["title"])
    return list(found.items())


def _close_and_wait(winid: int, title: str, timeout: float = 15.0) -> bool:
    """Close a window and wait for it to disappear.

    Prefer the app-level native quit for a MAIN Fusion window (macOS apps
    close cleanly via Quit, not per-window close); fall back to the AX close
    button for dialogs/secondary windows. Fusion may take longer to close
    than typical apps, so the default timeout is 15s."""
    if winid == _mac.find_fusion_main_winid():
        _mac.quit_fusion_native()
        deadline = time.monotonic() + timeout
        while time.monotonic() < deadline:
            time.sleep(0.3)
            if not _mac.window_valid(winid):
                return True
        return False
    return _mac.close_and_wait(winid, timeout)


def _force_kill_fusion() -> int:
    """Force kill all Fusion processes. Returns number of processes killed."""
    return _mac.kill_fusion()


def _has_fusion_process() -> bool:
    """Check if any Fusion process is still running."""
    return _mac.has_fusion_process()


ADDIN_PORT = 8774


def _close_all_documents_via_addin() -> dict:
    """Ask the add-in to close all open documents without saving.

    This prevents the 'Recovered Documents' dialog on next launch.
    Returns {"closed": [...], "errors": [...], "count": N}.
    """
    result = {"closed": [], "errors": [], "count": 0}

    # First, get list of open documents
    try:
        body = json.dumps({"command": "document_info", "args": {}}).encode("utf-8")
        req = urllib.request.Request(
            f"http://127.0.0.1:{ADDIN_PORT}/command",
            data=body,
            headers={"Content-Type": "application/json"},
            method="POST",
        )
        with urllib.request.urlopen(req, timeout=5) as resp:
            resp_data = json.loads(resp.read())
    except Exception:
        return result  # Add-in not reachable, skip clean close

    if not resp_data.get("success"):
        return result

    docs = resp_data.get("data", {}).get("openDocuments", [])
    result["count"] = len(docs)

    if not docs:
        return result

    # Close each document (close active last to avoid switching issues)
    # Reverse order so we close non-active docs first
    for doc in reversed(docs):
        doc_name = doc.get("name", "")
        if not doc_name:
            continue
        try:
            body = json.dumps({
                "command": "close_document",
                "args": {"name": doc_name, "save": False},
            }).encode("utf-8")
            req = urllib.request.Request(
                f"http://127.0.0.1:{ADDIN_PORT}/command",
                data=body,
                headers={"Content-Type": "application/json"},
                method="POST",
            )
            with urllib.request.urlopen(req, timeout=10) as resp:
                close_resp = json.loads(resp.read())
            if close_resp.get("success"):
                result["closed"].append(doc_name)
            else:
                result["errors"].append(f"{doc_name}: {close_resp.get('error', '?')}")
        except Exception as e:
            result["errors"].append(f"{doc_name}: {e}")

    return result


def handle_close_fusion(fusion_info: dict, args: dict) -> dict:
    """Close all Fusion 360 windows, with optional force-kill.

    Always attempts to close documents cleanly via the add-in first,
    to prevent the 'Recovered Documents' dialog on next launch.

    Args:
        fusion_info: Fusion 360 detection info dict (unused but required by interface).
        args: Dict with:
            - force: If true, force-kill all Fusion processes (default: false).
            - skipCleanClose: If true, skip document close via add-in (default: false).
    """
    force = args.get("force", False)
    skip_clean = args.get("skipCleanClose", False)

    # Always try to close documents cleanly first (unless explicitly skipped)
    close_report = {"closed": [], "errors": [], "count": 0}
    if not skip_clean:
        close_report = _close_all_documents_via_addin()
        if close_report["closed"]:
            time.sleep(0.5)  # Brief pause after closing docs

    if force:
        # Force kill all Fusion processes immediately
        killed = _force_kill_fusion()
        time.sleep(1)
        if _has_fusion_process():
            return {
                "success": False,
                "error": "Some Fusion processes survived force-kill.",
                "_hint": "Report to the user and ask them to manually kill remaining Fusion processes via Activity Monitor, then retry.",
            }
        return {
            "success": True,
            "output": f"Force-killed Fusion 360 ({killed} process group{'s' if killed != 1 else ''}). "
                      f"Closed {len(close_report['closed'])} document(s) cleanly first.",
            "data": {"documentsClosed": close_report["closed"]},
        }

    # Graceful close
    windows = _find_windows_by_title("Autodesk Fusion")

    if not windows:
        # No windows but maybe processes are hanging
        if _has_fusion_process():
            return {
                "success": True,
                "output": "No Fusion windows found but processes are running. Use force=true to kill them.",
            }
        return {"success": True, "output": "Fusion 360 is not running."}

    closed = []
    failed = []
    for winid, title in windows:
        if _close_and_wait(winid, title):
            closed.append(title)
        else:
            failed.append(title)

    if failed:
        # Try force-killing since graceful close failed
        _force_kill_fusion()
        time.sleep(1)
        if not _has_fusion_process():
            return {
                "success": True,
                "output": "Fusion 360 force-killed after graceful close failed.",
            }
        return {
            "success": False,
            "error": f"Failed to close: {', '.join(failed)}",
            "output": f"Closed: {', '.join(closed)}" if closed else "",
            "_hint": "Call fusion_close with {\"force\": true} to force-kill stuck windows, then retry.",
        }

    # Wait for lingering processes
    for _ in range(10):
        if not _has_fusion_process():
            break
        time.sleep(0.5)

    return {
        "success": True,
        "output": f"Fusion 360 closed ({len(closed)} window{'s' if len(closed) != 1 else ''}).",
    }


def handle_fusion_stop(fusion_info: dict, args: dict) -> dict:
    """Gracefully STOP Fusion 360 - the clean opposite of fusion_start.

    Closes all docs cleanly via the add-in (avoids the 'Recovered Documents' dialog),
    then closes every Fusion window and waits. Does NOT force-kill: if a window will
    not close (usually a modal dialog blocking the close), it returns failure and tells
    you to use fusion_kill. No surprise escalation - that's the whole point.

    Args: skipCleanClose (bool, default false) - skip the add-in document close.
    """
    skip_clean = args.get("skipCleanClose", False)
    close_report = {"closed": [], "errors": [], "count": 0}
    if not skip_clean:
        close_report = _close_all_documents_via_addin()
        if close_report["closed"]:
            time.sleep(0.5)

    windows = _find_windows_by_title("Autodesk Fusion")
    if not windows:
        if _has_fusion_process():
            return {
                "success": False,
                "error": "No Fusion windows found, but Fusion processes are still running.",
                "_hint": "A graceful stop cannot reach them - use fusion_kill to force-kill.",
            }
        return {"success": True, "output": "Fusion 360 is not running."}

    closed, failed = [], []
    for winid, title in windows:
        (closed if _close_and_wait(winid, title) else failed).append(title)

    if failed:
        return {
            "success": False,
            "error": f"Graceful stop failed for: {', '.join(failed)}",
            "output": f"Closed: {', '.join(closed)}" if closed else "",
            "_hint": "A window would not close gracefully (a modal dialog often blocks it). "
                     "Use fusion_kill to force-kill, then retry.",
        }

    for _ in range(10):
        if not _has_fusion_process():
            break
        time.sleep(0.5)
    return {
        "success": True,
        "output": f"Fusion 360 stopped gracefully ({len(closed)} window{'s' if len(closed) != 1 else ''}); "
                  f"{len(close_report['closed'])} document(s) closed cleanly first.",
        "data": {"documentsClosed": close_report["closed"]},
    }


def handle_fusion_kill(fusion_info: dict, args: dict) -> dict:
    """Force-KILL Fusion 360 - the 'desperate' path for when fusion_stop
    can't close it (a stuck modal, a wedged process).

    Still closes docs cleanly via the add-in FIRST when reachable, to avoid the
    'Recovered Documents' dialog on next launch.

    Args: skipCleanClose (bool, default false) - skip the add-in document close.
    """
    skip_clean = args.get("skipCleanClose", False)
    close_report = {"closed": [], "errors": [], "count": 0}
    if not skip_clean:
        close_report = _close_all_documents_via_addin()
        if close_report["closed"]:
            time.sleep(0.5)
    killed = _force_kill_fusion()
    time.sleep(1)
    if _has_fusion_process():
        return {
            "success": False,
            "error": "Some Fusion processes survived force-kill.",
            "_hint": "Ask the user to kill remaining Fusion processes via Activity Monitor, then retry.",
        }
    return {
        "success": True,
        "output": f"Fusion 360 force-killed ({killed} process group{'s' if killed != 1 else ''}); "
                  f"{len(close_report['closed'])} document(s) closed cleanly first.",
        "data": {"documentsClosed": close_report["closed"]},
    }
