"""Detect the focused app and resolve project context."""

import json
import logging
import os
import sqlite3
import subprocess
from pathlib import Path

import AppKit
import ApplicationServices

from wordbird.config import DATA_DIR

logger = logging.getLogger(__name__)

EDITOR_CONTEXTS_DIR = DATA_DIR / "editor-contexts"


def get_frontmost_app() -> tuple[str, str]:
    """Return (bundle_id, app_name) of the frontmost application."""
    workspace = AppKit.NSWorkspace.sharedWorkspace()
    app = workspace.frontmostApplication()
    return (app.bundleIdentifier() or "", app.localizedName() or "")


def get_terminal_cwd() -> str | None:
    """Get the cwd of the shell in the frontmost Terminal.app tab."""
    try:
        tty = subprocess.run(
            [
                "osascript",
                "-e",
                'tell application "Terminal" to tty of selected tab of front window',
            ],
            capture_output=True,
            text=True,
            timeout=5,
        )
        if tty.returncode != 0 or not tty.stdout.strip():
            return None

        tty_name = tty.stdout.strip()
        tty_short = tty_name.replace("/dev/", "")

        ps = subprocess.run(
            ["ps", "-t", tty_short, "-o", "pid=,comm="],
            capture_output=True,
            text=True,
            timeout=5,
        )
        if ps.returncode != 0:
            return None

        pid = None
        for line in ps.stdout.strip().splitlines():
            parts = line.split()
            if len(parts) >= 2 and any(
                sh in parts[-1] for sh in ("zsh", "bash", "fish")
            ):
                pid = parts[0]
                break

        if pid is None:
            return None

        lsof = subprocess.run(
            ["lsof", "-a", "-p", pid, "-d", "cwd", "-Fn"],
            capture_output=True,
            text=True,
            timeout=5,
        )
        for line in lsof.stdout.strip().splitlines():
            if line.startswith("n/"):
                return line[1:]

    except Exception:
        logger.debug("Failed to detect Terminal.app cwd", exc_info=True)

    return None


def _is_child_of(child_pid: int, parent_pid: int) -> bool:
    """Check if child_pid is a descendant of parent_pid."""
    try:
        result = subprocess.run(
            ["ps", "-o", "ppid=", "-p", str(child_pid)],
            capture_output=True,
            text=True,
            timeout=2,
        )
        ppid = int(result.stdout.strip())
        if ppid == parent_pid:
            return True
        if ppid <= 1:
            return False
        return _is_child_of(ppid, parent_pid)
    except Exception:
        logger.debug("Failed to check process ancestry", exc_info=True)
        return False


def _process_is_alive(pid: int) -> bool:
    """Check if a process is still running."""
    try:
        os.kill(pid, 0)
        return True
    except (ProcessLookupError, PermissionError):
        return False


def _get_focused_window_title(pid: int) -> str | None:
    """Get the title of the focused window for a given PID using Accessibility API."""
    try:
        app_ref = ApplicationServices.AXUIElementCreateApplication(pid)
        err, focused_window = ApplicationServices.AXUIElementCopyAttributeValue(
            app_ref, "AXFocusedWindow", None
        )
        if err or focused_window is None:
            return None
        err, title = ApplicationServices.AXUIElementCopyAttributeValue(
            focused_window, "AXTitle", None
        )
        if err or title is None:
            return None
        return str(title)
    except Exception:
        logger.debug("Failed to get window title for pid %d", pid, exc_info=True)
        return None


ZED_DB_PATH = Path.home() / "Library/Application Support/Zed/db/0-stable/db.sqlite"


def _get_zed_workspace(pid: int) -> str | None:
    """Get the workspace path for the focused Zed window.

    Uses the window title to extract the project name, then looks it up
    in Zed's workspace SQLite DB to get the full path.
    """
    title = _get_focused_window_title(pid)
    if not title:
        return None

    # Zed titles: "projectname" or "projectname — filename.ext"
    project_name = title.split(" \u2014 ")[0].strip()
    if not project_name:
        return None

    if not ZED_DB_PATH.exists():
        return None

    try:
        uri = f"file:{ZED_DB_PATH}?mode=ro"
        with sqlite3.connect(uri, uri=True, timeout=1) as conn:
            row = conn.execute(
                "SELECT paths FROM workspaces"
                " WHERE paths LIKE ? ORDER BY timestamp DESC LIMIT 1",
                (f"%/{project_name}",),
            ).fetchone()
        if row:
            return row[0]
    except Exception:
        logger.debug("Failed to query Zed workspace DB", exc_info=True)

    return None


def _cleanup_stale_contexts():
    """Remove context files for processes that are no longer running."""
    if not EDITOR_CONTEXTS_DIR.is_dir():
        return
    for path in EDITOR_CONTEXTS_DIR.iterdir():
        if path.suffix != ".json":
            continue
        try:
            data = json.loads(path.read_text())
            pid = data.get("pid")
            if pid is not None and not _process_is_alive(pid):
                path.unlink()
                logger.debug("Cleaned up stale context: %s (pid %d)", path.name, pid)
        except Exception:
            try:
                path.unlink()
            except Exception:
                pass


def _read_wordbird_md_from_disk(workspace: str) -> str | None:
    """Read WORDBIRD.md from a workspace directory on disk."""
    try:
        return (Path(workspace) / "WORDBIRD.md").read_text()
    except Exception:
        return None


def _read_editor_contexts(frontmost_pid: int) -> tuple[str | None, str | None]:
    """Scan editor-contexts/ for the context matching the frontmost app.

    Resolution order:
    1. Filter by PID ancestry
    2. One candidate → use it
    3. All candidates have identical WORDBIRD.md → use any
    4. Contents differ → match by focused window title
    5. Still ambiguous → read WORDBIRD.md from disk in each workspace,
       the one where disk content matches the context file is correct
    6. Cannot determine → return None
    """
    _cleanup_stale_contexts()

    if not EDITOR_CONTEXTS_DIR.is_dir():
        return None, None

    # Step 1: collect candidates that belong to the frontmost app
    candidates: list[tuple[str | None, str | None]] = []
    for path in EDITOR_CONTEXTS_DIR.iterdir():
        if path.suffix != ".json":
            continue
        try:
            data = json.loads(path.read_text())
        except Exception:
            continue

        ctx_pid = data.get("pid")
        if ctx_pid is None:
            continue

        if ctx_pid != frontmost_pid and not _is_child_of(ctx_pid, frontmost_pid):
            continue

        candidates.append((data.get("workspace"), data.get("wordbird_md")))

    if not candidates:
        return None, None

    # Step 2: single candidate
    if len(candidates) == 1:
        return candidates[0]

    # Step 3: all WORDBIRD.md contents identical AND same workspace
    contents = {c[1] for c in candidates}
    workspaces = {c[0] for c in candidates}
    if len(contents) == 1 and len(workspaces) == 1:
        return candidates[0]

    # Step 4: contents differ — match by focused window title
    title = _get_focused_window_title(frontmost_pid)
    if title:
        for workspace, wordbird_md in candidates:
            if workspace and Path(workspace).name in title:
                return workspace, wordbird_md

    # Step 5: read WORDBIRD.md from disk in each workspace and compare
    # against what the context file has. The matching one is correct.
    for workspace, wordbird_md in candidates:
        if workspace and wordbird_md is not None:
            disk_content = _read_wordbird_md_from_disk(workspace)
            if disk_content == wordbird_md:
                return workspace, wordbird_md

    # Step 6: cannot determine
    logger.debug(
        "Could not disambiguate %d editor contexts for pid %d",
        len(candidates),
        frontmost_pid,
    )
    return None, None


def find_context_file(start_dir: str) -> Path | None:
    """Walk up from start_dir looking for a WORDBIRD.md file."""
    current = Path(start_dir).resolve()
    while True:
        candidate = current / "WORDBIRD.md"
        if candidate.is_file():
            return candidate
        parent = current.parent
        if parent == current:
            break
        current = parent
    return None


def get_context() -> tuple[str, str | None, str | None]:
    """Get current context: (app_name, cwd, WORDBIRD.md contents or None)."""
    bundle_id, app_name = get_frontmost_app()

    cwd = None
    context_content = None

    if bundle_id == "com.apple.Terminal":
        cwd = get_terminal_cwd()
        if cwd:
            context_file = find_context_file(cwd)
            if context_file:
                try:
                    context_content = context_file.read_text()
                except Exception:
                    logger.debug("Failed to read %s", context_file, exc_info=True)
    elif bundle_id == "dev.zed.Zed":
        ws = AppKit.NSWorkspace.sharedWorkspace()
        frontmost_pid = ws.frontmostApplication().processIdentifier()
        workspace_path = _get_zed_workspace(frontmost_pid)
        if workspace_path:
            cwd = workspace_path
            context_file = find_context_file(workspace_path)
            if context_file:
                try:
                    context_content = context_file.read_text()
                except Exception:
                    logger.debug("Failed to read %s", context_file, exc_info=True)
        else:
            # Fall through to editor-contexts as backup
            cwd, context_content = _read_editor_contexts(frontmost_pid)
    else:
        # Try editor-contexts/ (works for any editor with an extension)
        ws = AppKit.NSWorkspace.sharedWorkspace()
        frontmost_pid = ws.frontmostApplication().processIdentifier()
        cwd, context_content = _read_editor_contexts(frontmost_pid)

    return app_name, cwd, context_content
