"""Resolve user folder terms, app aliases, and workspace routing (P1-1 v1 MVP)."""

from __future__ import annotations

import os
import platform
import re
import shutil
import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Literal

from ...core.version import AGENT_MVP_VERSION

FolderScope = Literal[
    "desktop",
    "downloads",
    "documents",
    "pictures",
    "music",
    "videos",
    "home",
    "workspace",
    "trash",
    "unknown",
]
WorkspaceRoutingMode = Literal["safe", "balanced", "trusted"]

_USER_DIR_KEYS: dict[FolderScope, str] = {
    "desktop": "XDG_DESKTOP_DIR",
    "downloads": "XDG_DOWNLOAD_DIR",
    "documents": "XDG_DOCUMENTS_DIR",
    "pictures": "XDG_PICTURES_DIR",
    "music": "XDG_MUSIC_DIR",
    "videos": "XDG_VIDEOS_DIR",
}

_FOLDER_TERM_PATTERNS: tuple[tuple[re.Pattern[str], FolderScope], ...] = (
    (re.compile(r"\bзагрузк", re.IGNORECASE), "downloads"),
    (re.compile(r"\bdownloads?\b", re.IGNORECASE), "downloads"),
    (re.compile(r"\bdescargas?\b", re.IGNORECASE), "downloads"),
    (re.compile(r"\btéléchargements?\b", re.IGNORECASE), "downloads"),
    (re.compile(r"\bрабоч\w*\s+стол", re.IGNORECASE), "desktop"),
    (re.compile(r"\bdesktop\b", re.IGNORECASE), "desktop"),
    (re.compile(r"\bescritorio\b", re.IGNORECASE), "desktop"),
    (re.compile(r"\bbureau\b", re.IGNORECASE), "desktop"),
    (re.compile(r"\bдокумент", re.IGNORECASE), "documents"),
    (re.compile(r"\bdocuments?\b", re.IGNORECASE), "documents"),
    (re.compile(r"\bdocumentos?\b", re.IGNORECASE), "documents"),
    (re.compile(r"\bкартин", re.IGNORECASE), "pictures"),
    (re.compile(r"\bpictures?\b", re.IGNORECASE), "pictures"),
    (re.compile(r"\bimages?\b", re.IGNORECASE), "pictures"),
    (re.compile(r"\bмузык", re.IGNORECASE), "music"),
    (re.compile(r"\bmusic\b", re.IGNORECASE), "music"),
    (re.compile(r"\bвидео", re.IGNORECASE), "videos"),
    (re.compile(r"\bvideos?\b", re.IGNORECASE), "videos"),
    (re.compile(r"\bкорзин", re.IGNORECASE), "trash"),
    (re.compile(r"\btrash\b", re.IGNORECASE), "trash"),
    (re.compile(r"\biuve\s*workspace\b", re.IGNORECASE), "workspace"),
    (re.compile(r"\bworkspace\b", re.IGNORECASE), "workspace"),
    (re.compile(r"\bдомашн", re.IGNORECASE), "home"),
    (re.compile(r"\bhome\b", re.IGNORECASE), "home"),
)

_APP_ALIAS_DB: dict[str, tuple[str, ...]] = {
    "system_monitor": (
        "gnome-system-monitor",
        "mate-system-monitor",
        "xfce4-taskmanager",
        "ksysguard",
        "system monitor",
        "системный монитор",
        "монитор системы",
        "task manager",
    ),
    "terminal": (
        "gnome-terminal",
        "konsole",
        "xfce4-terminal",
        "xterm",
        "alacritty",
        "kitty",
        "терминал",
        "terminal",
    ),
    "pdf_viewer": (
        "evince",
        "okular",
        "atril",
        "xreader",
        "foxitreader",
        "pdf viewer",
        "просмотр pdf",
    ),
    "text_editor": (
        "gedit",
        "kate",
        "mousepad",
        "xed",
        "pluma",
        "текстовый редактор",
        "text editor",
    ),
    "browser": (
        "firefox",
        "google-chrome",
        "chromium",
        "brave-browser",
        "microsoft-edge",
        "браузер",
        "browser",
    ),
}

_BROWSER_CANDIDATES = (
    "firefox",
    "google-chrome",
    "google-chrome-stable",
    "chromium",
    "chromium-browser",
    "brave-browser",
    "microsoft-edge",
    "opera",
    "vivaldi",
)


@dataclass(frozen=True)
class ResolvedFolder:
    user_term: str
    scope: FolderScope
    path: Path
    policy_hint: str = ""
    routing_mode: WorkspaceRoutingMode = "safe"
    remapped: bool = False

    def as_dict(self) -> dict[str, str]:
        return {
            "user_term": self.user_term,
            "scope": self.scope,
            "path": str(self.path),
            "policy_hint": self.policy_hint,
            "routing_mode": self.routing_mode,
            "remapped": str(self.remapped).lower(),
        }


@dataclass(frozen=True)
class ResolvedApp:
    alias_key: str
    query: str
    launch_id: str = ""
    desktop_id: str = ""

    def as_dict(self) -> dict[str, str]:
        return {
            "alias_key": self.alias_key,
            "query": self.query,
            "launch_id": self.launch_id,
            "desktop_id": self.desktop_id,
        }


def _read_xdg_user_dir(key: str, default: Path) -> Path:
    config = Path.home() / ".config" / "user-dirs.dirs"
    if not config.is_file():
        return default
    try:
        text = config.read_text(encoding="utf-8", errors="replace")
    except OSError:
        return default
    match = re.search(rf'^{re.escape(key)}="([^"]+)"', text, flags=re.MULTILINE)
    if not match:
        return default
    raw = match.group(1).strip()
    raw = raw.replace("$HOME", str(Path.home()))
    return Path(raw).expanduser()


def resolve_trash_path() -> Path:
    home = Path.home()
    candidates = (
        home / ".local/share/Trash",
        home / ".Trash",
    )
    for candidate in candidates:
        if candidate.is_dir():
            return candidate
    return candidates[0]


def resolve_workspace_path() -> Path:
    workspace = Path.home() / "IuVeWorkspace"
    workspace.mkdir(parents=True, exist_ok=True)
    return workspace


def resolve_workspace_routing_mode(
    *,
    trust_state: str = "",
    config_value: str = "",
) -> WorkspaceRoutingMode:
    """Resolve routing mode: env/config override, then trust_state trusted → trusted."""
    raw = str(config_value or os.getenv("AVATAR_WORKSPACE_ROUTING_MODE", "")).strip().lower()
    if raw in {"safe", "balanced", "trusted"}:
        return raw  # type: ignore[return-value]
    if str(trust_state or "").strip().lower() == "trusted":
        return "trusted"
    return "safe"


def writable_scopes_for_mode(mode: WorkspaceRoutingMode) -> frozenset[FolderScope]:
    if mode == "trusted":
        return frozenset(
            {"desktop", "downloads", "documents", "pictures", "music", "videos", "home", "workspace"}
        )
    if mode == "balanced":
        return frozenset({"desktop", "downloads", "documents", "workspace"})
    return frozenset({"workspace"})


def write_restricted_to_workspace_only(
    mode: WorkspaceRoutingMode,
    *,
    trust_state: str = "",
    file_access_granted: bool = False,
) -> bool:
    """True when writes must stay under ~/IuVeWorkspace (Safe or non-trusted default)."""
    if str(trust_state or "").strip().lower() != "trusted":
        return True
    if not file_access_granted:
        return True
    return mode == "safe"


def _path_under_root(candidate: Path, root: Path) -> bool:
    try:
        root_real = root.expanduser().resolve()
        candidate_real = candidate.expanduser().resolve()
    except OSError:
        return False
    root_prefix = str(root_real) if str(root_real).endswith(os.sep) else str(root_real) + os.sep
    return str(candidate_real) == str(root_real) or str(candidate_real).startswith(root_prefix)


def validate_write_path_for_policy(
    absolute_path: str | Path,
    *,
    routing_mode: WorkspaceRoutingMode | None = None,
    trust_state: str = "",
    file_access_granted: bool = False,
    scope_workspace_paths: list[str | Path] | None = None,
) -> tuple[bool, str]:
    """Return (allowed, policy_hint) for a concrete write target path."""
    mode = routing_mode or resolve_workspace_routing_mode(trust_state=trust_state)
    workspace = resolve_workspace_path()
    try:
        target = Path(absolute_path).expanduser().resolve()
    except OSError:
        return False, "недопустимый путь"

    scope_roots: list[Path] = []
    for raw_root in scope_workspace_paths or []:
        try:
            scope_roots.append(Path(raw_root).expanduser().resolve())
        except OSError:
            continue

    workspace_roots: list[Path] = [workspace]
    for root in scope_roots:
        if root.name == "IuVeWorkspace":
            if root not in workspace_roots:
                workspace_roots.append(root)
        else:
            nested = root / "IuVeWorkspace"
            if nested not in workspace_roots:
                workspace_roots.append(nested)

    if write_restricted_to_workspace_only(mode, trust_state=trust_state, file_access_granted=file_access_granted):
        for root in workspace_roots:
            if _path_under_root(target, root):
                return True, ""
        if mode == "safe" and str(trust_state or "").strip().lower() == "trusted" and file_access_granted:
            hint = (
                "Режим Safe: запись только в IuVeWorkspace. "
                f"Запрос «рабочий стол» выполняется в {workspace}. "
                f"Системный путь: {target}"
            )
        else:
            hint = (
                "Запись разрешена только в IuVeWorkspace "
                f"(режим {mode}, grant file_access={'да' if file_access_granted else 'нет'}). "
                f"Запрошенный путь: {target}"
            )
        return False, hint

    for scope in writable_scopes_for_mode(mode):
        folder = resolve_xdg_folder(scope)
        if folder is not None and _path_under_root(target, folder):
            return True, ""

    for root in workspace_roots:
        if _path_under_root(target, root):
            return True, ""

    return False, f"Путь вне разрешённой области записи (режим {mode}): {target}"


def resolve_folder_scope(text: str) -> FolderScope:
    raw = str(text or "")
    for pattern, scope in _FOLDER_TERM_PATTERNS:
        if pattern.search(raw):
            return scope
    return "unknown"


def _os_family() -> str:
    family = platform.system().lower()
    if family == "darwin":
        return "macos"
    return family


def _windows_folder_defaults(home: Path) -> dict[FolderScope, Path]:
    profile = Path(os.environ.get("USERPROFILE") or home)
    return {
        "desktop": profile / "Desktop",
        "downloads": profile / "Downloads",
        "documents": profile / "Documents",
        "pictures": profile / "Pictures",
        "music": profile / "Music",
        "videos": profile / "Videos",
        "home": profile,
        "workspace": resolve_workspace_path(),
        "trash": profile / "AppData" / "Local" / "Microsoft" / "Windows" / "Recycle Bin",
    }


def _macos_folder_defaults(home: Path) -> dict[FolderScope, Path]:
    return {
        "desktop": home / "Desktop",
        "downloads": home / "Downloads",
        "documents": home / "Documents",
        "pictures": home / "Pictures",
        "music": home / "Music",
        "videos": home / "Movies",
        "home": home,
        "workspace": resolve_workspace_path(),
        "trash": home / ".Trash",
    }


def resolve_xdg_folder(scope: FolderScope) -> Path | None:
    """Return concrete user folder path for a known scope (platform-aware)."""
    home = Path.home()
    family = _os_family()
    if family == "windows":
        defaults = _windows_folder_defaults(home)
    elif family == "macos":
        defaults = _macos_folder_defaults(home)
    else:
        defaults = {
            "desktop": home / "Desktop",
            "downloads": home / "Downloads",
            "documents": home / "Documents",
            "pictures": home / "Pictures",
            "music": home / "Music",
            "videos": home / "Videos",
            "home": home,
            "workspace": resolve_workspace_path(),
            "trash": resolve_trash_path(),
        }
    if scope not in defaults:
        return None
    if scope == "workspace":
        return defaults["workspace"]
    if scope == "trash":
        return defaults["trash"]
    if family == "linux":
        key = _USER_DIR_KEYS.get(scope, "")
        return _read_xdg_user_dir(key, defaults[scope]) if key else defaults[scope]
    return defaults[scope]


def resolve_default_browser() -> str:
    """Detect default browser (xdg-settings on Linux; common binaries elsewhere)."""
    if _os_family() == "linux":
        xdg_settings = shutil.which("xdg-settings")
        if xdg_settings:
            try:
                proc = subprocess.run(
                    [xdg_settings, "get", "default-web-browser"],
                    capture_output=True,
                    text=True,
                    timeout=8,
                    check=False,
                )
                desktop_id = str(proc.stdout or "").strip().lower()
                if desktop_id:
                    stem = desktop_id.removesuffix(".desktop")
                    if stem:
                        return stem
            except (subprocess.TimeoutExpired, OSError):
                pass
    win_candidates = (
        "msedge",
        "chrome",
        "firefox",
        "brave",
        "opera",
        "vivaldi",
    )
    mac_candidates = (
        "Google Chrome",
        "firefox",
        "Microsoft Edge",
        "Brave Browser",
        "Safari",
        "opera",
        "vivaldi",
    )
    candidates = _BROWSER_CANDIDATES
    if _os_family() == "windows":
        candidates = win_candidates + _BROWSER_CANDIDATES
    elif _os_family() == "macos":
        candidates = mac_candidates + _BROWSER_CANDIDATES
    for candidate in candidates:
        if shutil.which(candidate):
            return candidate
    return "default"


def resolve_app_alias(text: str) -> ResolvedApp | None:
    """Map NL app reference to alias bucket (launch resolution stays in device scanner)."""
    raw = str(text or "").strip().lower()
    if not raw:
        return None
    for alias_key, variants in _APP_ALIAS_DB.items():
        for variant in variants:
            if variant in raw:
                launch_id = variant if shutil.which(variant) else ""
                return ResolvedApp(alias_key=alias_key, query=variant, launch_id=launch_id)
    return None


def resolve_user_folder(
    text: str,
    *,
    scope: FolderScope | None = None,
    for_read: bool = False,
    routing_mode: WorkspaceRoutingMode | None = None,
    trust_state: str = "",
    file_access_granted: bool = False,
) -> ResolvedFolder | None:
    """Map NL folder reference to a concrete path with policy disclosure."""
    resolved_scope = scope or resolve_folder_scope(text)
    if resolved_scope == "unknown":
        return None

    mode = routing_mode or resolve_workspace_routing_mode(trust_state=trust_state)
    system_path = resolve_xdg_folder(resolved_scope)
    if system_path is None:
        return None

    writable_scopes = writable_scopes_for_mode(mode)
    term = resolved_scope
    remapped = False
    hint = ""

    if resolved_scope == "trash":
        path = system_path
        hint = "Корзина — только чтение/очистка по политике."
    elif for_read or resolved_scope in {"home", "trash"}:
        path = system_path
        if resolved_scope == "desktop" and mode == "safe":
            hint = "Чтение с системного Desktop."
    elif resolved_scope == "workspace":
        path = system_path
        hint = "IuVeWorkspace — managed agent workspace"
    elif resolved_scope == "desktop":
        from .compound_intent_resolver import user_explicit_desktop_intent
        from iuve_connect_agents.shared.device_context_model import get_planning_dcm, resolve_path_from_dcm

        explicit_desktop = user_explicit_desktop_intent(text) and not for_read
        dcm = get_planning_dcm()
        if dcm:
            dcm_path, dcm_hint, dcm_remapped = resolve_path_from_dcm(
                "desktop",
                dcm=dcm,
                explicit_desktop=explicit_desktop,
                for_write=not for_read,
            )
            if dcm_path:
                path = Path(dcm_path)
                hint = dcm_hint
                remapped = dcm_remapped
            else:
                path = system_path
                hint = dcm_hint
        else:
            trusted = str(trust_state or "").strip().lower() == "trusted"
            allow_system_desktop_write = (
                trusted
                and file_access_granted
                and mode in {"balanced", "trusted"}
                and not for_read
            )
            if explicit_desktop and not for_read:
                path = system_path
                hint = f"Явный запрос рабочего стола: {system_path}"
            elif mode == "safe" and not for_read and not allow_system_desktop_write:
                path = resolve_workspace_path()
                remapped = True
                hint = (
                    "Запрос «рабочий стол» выполняется в IuVeWorkspace (режим Safe). "
                    f"Системный Desktop: {system_path}"
                )
            else:
                path = system_path
                if remapped is False and mode == "safe" and not for_read:
                    hint = "Режим Safe: запись на системный Desktop (file_access + trusted)."
                elif mode == "balanced":
                    hint = "Режим Balanced: запись на системный Desktop разрешена."
                elif mode == "trusted":
                    hint = "Режим Trusted: полный доступ к Desktop."
    elif resolved_scope in writable_scopes:
        path = system_path
        if mode == "balanced":
            hint = f"Режим Balanced: доступ к {resolved_scope}."
        elif mode == "trusted":
            hint = f"Режим Trusted: доступ к {resolved_scope}."
    else:
        path = resolve_workspace_path()
        remapped = True
        hint = (
            f"Запись в «{resolved_scope}» перенаправлена в IuVeWorkspace (режим {mode}). "
            f"Системный путь: {system_path}"
        )

    path = path.expanduser()
    if resolved_scope not in {"workspace", "trash"} and not remapped:
        try:
            path.mkdir(parents=True, exist_ok=True)
        except OSError:
            pass
    return ResolvedFolder(
        user_term=term,
        scope=resolved_scope,
        path=path,
        policy_hint=hint,
        routing_mode=mode,
        remapped=remapped,
    )


def resolve_folder_from_text(
    text: str,
    *,
    trust_state: str = "",
    for_read: bool = False,
) -> ResolvedFolder | None:
    return resolve_user_folder(text, for_read=for_read, trust_state=trust_state)


def capability_resolver_snapshot(*, trust_state: str = "") -> dict[str, Any]:
    """Expose resolved paths for planner/UX diagnostics."""
    mode = resolve_workspace_routing_mode(trust_state=trust_state)
    folders = {}
    for scope in ("desktop", "downloads", "documents", "pictures", "music", "videos", "home", "workspace", "trash"):
        resolved = resolve_user_folder("", scope=scope, for_read=True, routing_mode=mode)  # type: ignore[arg-type]
        if resolved:
            folders[scope] = str(resolved.path)
    write_desktop = resolve_user_folder("desktop", for_read=False, routing_mode=mode)
    return {
        "routing_mode": mode,
        "default_browser": resolve_default_browser(),
        "folders": folders,
        "desktop_write_path": str(write_desktop.path) if write_desktop else "",
        "desktop_write_remapped": bool(write_desktop and write_desktop.remapped),
        "desktop_policy_hint": str(write_desktop.policy_hint) if write_desktop else "",
    }


def build_capability_snapshot(
    *,
    trust_state: str = "",
    grants: dict[str, bool] | None = None,
    app_version: str = "",
) -> dict[str, Any]:
    """Full device capability snapshot for Orchestra graph (P2 §2)."""
    import hashlib
    import json
    import platform

    base = capability_resolver_snapshot(trust_state=trust_state)
    browsers: list[str] = []
    apps: list[str] = []
    handlers: dict[str, str] = {}
    installed_apps: list[dict[str, Any]] | None = None
    try:
        from .device_capability_scanner import load_profile

        profile = load_profile()
        if profile is not None:
            inv = getattr(profile, "inventory", None) or {}
            if isinstance(inv, dict):
                browsers = [str(b) for b in (inv.get("browsers") or []) if str(b).strip()][:20]
                apps = [str(a) for a in (inv.get("editors") or inv.get("apps") or []) if str(a).strip()][:40]
            handlers = {
                "default_browser": str(getattr(profile, "default_browser", "") or base.get("default_browser") or ""),
                "pdf": str(getattr(profile, "pdf_handler", "") or ""),
                "terminal": str(getattr(profile, "terminal_handler", "") or ""),
            }
            if isinstance(profile.get("installed_apps"), list):
                installed_apps = [dict(item) for item in profile["installed_apps"] if isinstance(item, dict)]
            elif isinstance(profile.get("applications"), list):
                from .device_capability_scanner import map_inventory_to_installed_apps

                installed_apps = map_inventory_to_installed_apps(profile["applications"])
    except Exception:
        pass

    grant_map = {str(k): bool(v) for k, v in (grants or {}).items()}
    snapshot = {
        "schema_version": AGENT_MVP_VERSION,
        "platform": platform.system().lower(),
        "os_name": platform.system(),
        "os_version": platform.release(),
        "app_version": str(app_version or ""),
        "trust_state": str(trust_state or ""),
        "routing_mode": base.get("routing_mode"),
        "folders": base.get("folders") or {},
        "default_browser": base.get("default_browser") or handlers.get("default_browser"),
        "browsers": browsers,
        "applications": apps,
        "handlers": handlers,
        "grants": grant_map,
        "vision_capability": bool(grant_map.get("screen_vision")),
        "automation_capability": bool(
            grant_map.get("keyboard_control") or grant_map.get("mouse_control")
        ),
        "file_access": bool(grant_map.get("file_access")),
    }
    if installed_apps:
        snapshot["installed_apps"] = installed_apps
    from iuve_connect_agents.shared.device_context_model import merge_dcm_into_capability_snapshot

    snapshot = merge_dcm_into_capability_snapshot(snapshot)
    canonical = json.dumps(snapshot, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
    snapshot["snapshot_hash"] = hashlib.sha256(canonical.encode("utf-8")).hexdigest()[:32]
    return snapshot
