"""Vault adoption API — point AgntSpace at a folder (direct mode)."""

from __future__ import annotations

import asyncio
import logging
import os
import sys
from pathlib import Path

from fastapi import APIRouter, Request
from pydantic import BaseModel

log = logging.getLogger(__name__)


def _restart_server() -> None:
    """Restart the server process so new vault location takes effect.

    All core services (VaultReader, VaultPaths, JournalService, etc.) cache
    the vault path at init time — there is no safe hot-swap path, so we
    re-exec the process.

    Uses `sys.orig_argv` (Python 3.10+) to preserve the original `-m` form.
    Falling back to `sys.argv` would drop `-m agntspace.cli`, re-exec as a
    plain script, and sys.path[0] would become the cli.py directory —
    shadowing the installed `mcp` SDK with our local `agntspace/mcp/` package.
    """
    log.info("Restarting server after vault location change...")
    orig_argv = getattr(sys, "orig_argv", None)
    if orig_argv:
        os.execv(orig_argv[0], orig_argv)
    else:
        os.execv(sys.executable, [sys.executable] + sys.argv)


router = APIRouter(prefix="/sync", tags=["sync"])


class SyncConfigRequest(BaseModel):
    mode: str = "off"  # "off" | "direct"
    external_path: str = ""


class AdoptVaultRequest(BaseModel):
    vault_path: str = ""
    obsidian_path: str = ""  # legacy alias kept for the UI's folder picker

    @property
    def path(self) -> str:
        return self.vault_path or self.obsidian_path


@router.get("")
async def sync_status(request: Request) -> dict:
    """Get vault adoption status."""
    status = request.app.state.vault_sync.status()
    vault = request.app.state.vault
    vault_dir = vault.vault_dir if hasattr(vault, "vault_dir") else getattr(vault, "_vault_dir", "")
    status["vault_path"] = str(vault_dir)
    return status


@router.post("/configure")
async def configure_sync(request: Request, body: SyncConfigRequest) -> dict:
    """Configure vault adoption mode and external path."""
    vault_sync = request.app.state.vault_sync
    prev_mode = vault_sync._mode
    prev_external = str(vault_sync._external) if vault_sync._external else ""

    vault_sync.configure(body.mode, body.external_path)

    request.app.state.settings_service.update_section("sync", {
        "mode": body.mode,
        "external_path": body.external_path,
    })

    status = vault_sync.status()

    # If root_dir changes (direct mode toggles or external_path changes),
    # restart so cached services reload.
    root_dir_changed = (
        (prev_mode == "direct") != (body.mode == "direct")
        or (body.mode == "direct" and body.external_path != prev_external)
    )
    if root_dir_changed:
        asyncio.get_event_loop().call_later(0.5, _restart_server)
        status["restarting"] = True

    return status


@router.post("/adopt")
@router.post("/obsidian/adopt")  # alias kept for the UI folder picker
async def adopt_vault(request: Request, body: AdoptVaultRequest) -> dict:
    """Point AgntSpace at a folder as its vault (direct mode).

    This makes the chosen folder THE AgntSpace vault. AgntSpace creates
    `agntspace/` inside it and all data appears there in real-time.
    Existing content in the folder is never modified.
    """
    folder_path = body.path
    vault_sync = request.app.state.vault_sync
    result = vault_sync.adopt_obsidian_vault(folder_path)

    if "error" not in result:
        request.app.state.settings_service.update_section("sync", {
            "mode": "direct",
            "external_path": folder_path,
        })

        # Schedule restart so services pick up the new root_dir.
        # Config resolves root_dir from sync.external_path when mode=="direct".
        asyncio.get_event_loop().call_later(0.5, _restart_server)
        result["restarting"] = True
        result["message"] = (
            f"AgntSpace is now using {folder_path} as its vault. "
            "Server is restarting to activate the new location..."
        )

    return result


def _is_known_vault(path: Path) -> bool:
    """Check if a directory is an initialized AgntSpace vault.

    An AgntSpace vault is identified by the presence of its identity files,
    not just an empty `agntspace/` folder or an unrelated Obsidian vault.
    The home directory is excluded — `~/.agntspace/` is the fixed app_dir
    (config, db, credentials), not a user vault.
    """
    try:
        if path.resolve() == Path.home().resolve():
            return False
    except (OSError, RuntimeError):
        pass
    return (path / "agntspace" / "system" / "identity" / "SOUL.md").is_file()


@router.get("/browse")
async def browse_directory(path: str = "") -> dict:
    """Browse directories for the folder picker UI."""
    if not path:
        home = Path.home()
        drives = []
        import platform
        if platform.system() == "Windows":
            import string
            for letter in string.ascii_uppercase:
                drive = Path(f"{letter}:\\")
                if drive.exists():
                    drives.append({"name": f"{letter}:\\", "path": str(drive), "is_vault": False})
        return {
            "current": str(home),
            "parent": str(home.parent),
            "directories": [
                {"name": "Home", "path": str(home), "is_vault": _is_known_vault(home)},
                {"name": "Documents", "path": str(home / "Documents"), "is_vault": False},
                {"name": "Desktop", "path": str(home / "Desktop"), "is_vault": False},
                *drives,
            ],
        }

    browse_path = Path(path)
    if not browse_path.is_dir():
        return {"error": f"Not a directory: {path}"}

    dirs = []
    try:
        for item in sorted(browse_path.iterdir()):
            if item.is_dir() and not item.name.startswith("."):
                dirs.append({
                    "name": item.name,
                    "path": str(item),
                    "is_vault": _is_known_vault(item),
                    "is_skill": (item / "SKILL.md").is_file(),
                })
    except PermissionError:
        return {"error": f"Permission denied: {path}", "current": path, "directories": []}

    return {
        "current": str(browse_path),
        "parent": str(browse_path.parent) if browse_path.parent != browse_path else None,
        "directories": dirs,
        "is_skill_folder": (browse_path / "SKILL.md").is_file(),
    }


@router.post("/pick-folder")
async def pick_folder_native(initial: str = "") -> dict:
    """Open the OS-native folder picker (Windows Explorer / Finder / GTK).

    Runs tkinter.filedialog.askdirectory in a thread so the FastAPI event loop
    isn't blocked. Returns {"path": "..."} on selection, {"path": "", "cancelled": True}
    if the user dismissed the dialog, or {"error": "..."} if tkinter can't
    attach a display (e.g. server running as a headless daemon).

    Only works when the server and browser are on the same machine — which is
    AgntSpace's deployment model. For headless setups the UI should fall back
    to the in-app folder browser.
    """

    def _pick() -> dict:
        try:
            import tkinter
            from tkinter import filedialog
        except ImportError as exc:  # pragma: no cover — tk missing is rare
            return {"error": f"tkinter not available: {exc}"}

        try:
            root = tkinter.Tk()
            root.withdraw()
            root.attributes("-topmost", True)
            try:
                initial_dir = initial or str(Path.home())
                path = filedialog.askdirectory(
                    title="Select folder",
                    initialdir=initial_dir,
                    mustexist=True,
                )
            finally:
                root.destroy()
        except Exception as exc:
            return {"error": f"Native picker unavailable: {exc}"}

        if not path:
            return {"path": "", "cancelled": True}

        # tk returns forward slashes even on Windows — normalize to the local form.
        resolved = str(Path(path))
        return {
            "path": resolved,
            "is_skill_folder": (Path(resolved) / "SKILL.md").is_file(),
        }

    return await asyncio.to_thread(_pick)


@router.post("/obsidian/import")
async def import_obsidian_vault(request: Request, body: AdoptVaultRequest) -> dict:
    """Import an Obsidian vault's notes into a knowledge base (read-only).

    Scans the vault for `.md` files, copies them into the KB's `inbox/`,
    and triggers ingest. The Obsidian vault is never modified.
    """
    import shutil

    obs_path = Path(body.path)
    if not obs_path.is_dir():
        return {"error": f"Path does not exist: {body.path}"}

    md_files = [
        f for f in obs_path.rglob("*.md")
        if ".obsidian" not in f.parts and ".trash" not in f.parts
    ]
    if not md_files:
        return {"error": "No markdown files found in vault"}

    kb = request.app.state.kb_service
    vault = request.app.state.vault

    kb_slug = obs_path.name.lower().replace(" ", "-")
    kbs = kb.list_kbs()
    if not any(k["slug"] == kb_slug for k in kbs):
        kb.create_kb(obs_path.name)

    # Resolve the KB inbox through VaultPaths so it lands in the canonical
    # physical location regardless of layout.
    kb_inbox = vault.paths.resolve(f"knowledge/{kb_slug}/inbox")
    kb_inbox.mkdir(parents=True, exist_ok=True)

    copied = 0
    for md in md_files[:200]:  # cap at 200 per import
        dest = kb_inbox / md.name
        if not dest.exists():
            shutil.copy2(str(md), str(dest))
            copied += 1

    request.app.state.settings_service.update_section("sync", {
        "mode": "import",
        "external_path": str(obs_path),
    })

    result = await kb.ingest(kb_slug)

    return {
        "kb_slug": kb_slug,
        "files_found": len(md_files),
        "files_copied": copied,
        "ingested": result.get("count", 0),
        "wiki_updates": result.get("wiki_updates", []),
    }
