"""Collection 自动推断 — 统一降级链 + 延迟预算控制

detect_collection() 是唯一的 collection 推断入口，被 hooks.py / indexer.py / mcp_server.py 共用。

降级链：
1. config.collection 显式配置（非 "default"）
2. effective_cwd（CLAUDE_PROJECT_DIR env 替代 cwd）
3. [max_latency_ms >= 10] Git remote URL → 预编译正则提取 repo 名
4. 向上查找 marker 文件 → 目录名（sanity check）
5. CWD basename（sanity check）→ "default" 兜底

See: Issue #136 (internal)
"""

from __future__ import annotations

import logging
import os
import re
import subprocess
from pathlib import Path

logger = logging.getLogger(__name__)

# ── 常量 ──

# 项目根目录标识文件
PROJECT_MARKERS = (".git", "pyproject.toml", "package.json", "Cargo.toml", "go.mod")

# 无意义的目录名 — 命中时 fallback 到 parent 或 "default"
_MEANINGLESS_NAMES = frozenset({
    # 通用无意义目录
    "tmp", "temp", "home", "root",
    ".", "..", "/",
    # 用户目录
    "desktop", "documents", "downloads",
    # 代码通用目录
    "src", "app", "lib", "pkg", "code",
    "project", "projects", "repo", "repos",
    "workspace", "workspaces",
    # 系统目录
    "usr", "opt", "var",
})

# Git remote URL → repo 名，预编译正则
# 覆盖：HTTPS / 无.git后缀 / SSH / GitLab多层子组 / ssh:// / file://
_RE_REPO_NAME = re.compile(r'[/:]([^/:]+?)(?:\.git)?/?$')

# subprocess 硬编码超时（独立于 max_latency_ms）
_GIT_TIMEOUT_SECONDS = 2

# ── 缓存（进程级）──
# Hook 进程每次新建，缓存天然不过期
# MCP server 长驻进程，缓存随进程生命周期
# None 也缓存（非 Git 仓库不重复 subprocess）
_remote_cache: dict[str, str | None] = {}


# ── 公开 API ──


def detect_collection(
    cwd: str | Path | None = None,
    paths: list[str | Path] | None = None,
    config: object | None = None,
    max_latency_ms: int = 500,
) -> str:
    """统一 collection 推断 — 混合降级链 + 延迟预算控制

    Args:
        cwd: 当前工作目录（hooks 传 data["cwd"]，MCP 可不传）
        paths: 索引路径列表（indexer 场景传入）
        config: Config 对象（有 .collection 属性）
        max_latency_ms: 延迟预算
            < 10  → 只走内存/文件操作（marker 查找，<5ms）
            >= 10 → 可以跑 subprocess（git remote）

    Returns:
        推断出的 collection 名，永远返回有效值

    NOTE: monorepo 扩展点 —— 当前 Git root 名 = collection 名，
    monorepo 场景下可通过 paths 参数的公共子目录推断子项目名。
    """
    # 1. config 显式配置（非 "default"）
    config_collection = getattr(config, "collection", "default") if config else "default"
    if config_collection != "default":
        return config_collection

    # 2. 确定 effective_cwd（CLAUDE_PROJECT_DIR 替代 cwd）
    effective_cwd = _resolve_effective_cwd(cwd, paths)
    if not effective_cwd:
        return "default"

    # 3. [max_latency_ms >= 10] Git remote URL
    if max_latency_ms >= 10:
        repo_name = _cached_remote(str(effective_cwd))
        if repo_name and is_meaningful_name(repo_name):
            return repo_name

    # 4. 向上查找 marker 文件
    marker_name = _find_project_root_name(effective_cwd)
    if marker_name:
        return marker_name

    # 5. CWD basename（sanity check）
    if is_meaningful_name(effective_cwd.name):
        return effective_cwd.name

    # 6. "default" 兜底
    return "default"


def is_meaningful_name(name: str) -> bool:
    """目录名是否适合作为 collection 名"""
    if not name or len(name) < 2:
        return False
    if name.lower() in _MEANINGLESS_NAMES:
        return False
    if name.isdigit():
        return False
    # 用户 home 目录名（如 jia、john）
    try:
        import getpass
        if name == getpass.getuser():
            return False
    except Exception:
        pass
    return True


# ── 内部函数 ──


def _resolve_effective_cwd(
    cwd: str | Path | None,
    paths: list[str | Path] | None,
) -> Path | None:
    """确定有效工作目录：CLAUDE_PROJECT_DIR > cwd > paths[0]

    不强制 is_dir() 验证 — hooks 传入的 cwd 可能是远程路径。
    仅在路径确认是文件时切换到 parent。
    """
    # CLAUDE_PROJECT_DIR 是更可靠的路径来源
    project_dir = os.environ.get("CLAUDE_PROJECT_DIR", "")
    if project_dir:
        p = Path(project_dir)
        if p.is_file():
            p = p.parent
        return p

    if cwd:
        p = Path(cwd)
        if p.is_file():
            p = p.parent
        return p

    if paths:
        p = Path(paths[0])
        if p.is_file():
            p = p.parent
        return p

    return None


def _extract_repo_name(url: str) -> str | None:
    """从 git remote URL 提取 repo 名 — 预编译正则覆盖所有格式

    支持格式：
    - https://github.com/user/repo.git
    - https://github.com/user/repo（无 .git 后缀）
    - git@github.com:user/repo.git
    - git@github.com:org/sub-group/repo.git（GitLab 多层子组）
    - ssh://git@gitlab.com/group/repo
    - file:///local/path/repo.git（本地 bare repo）
    """
    if not url:
        return None
    match = _RE_REPO_NAME.search(url.strip())
    return match.group(1) if match else None


def _get_git_remote_url(cwd: Path) -> str | None:
    """执行 git remote get-url origin，返回 URL 或 None"""
    try:
        result = subprocess.run(
            ["git", "remote", "get-url", "origin"],
            cwd=str(cwd),
            capture_output=True,
            text=True,
            timeout=_GIT_TIMEOUT_SECONDS,
        )
        if result.returncode == 0 and result.stdout.strip():
            return result.stdout.strip()
    except (subprocess.TimeoutExpired, FileNotFoundError, OSError) as e:
        logger.debug("git remote get-url 失败: %s", e)
    return None


def _cached_remote(cwd: str) -> str | None:
    """带缓存的 git remote 查询 — 失败结果也缓存"""
    if cwd not in _remote_cache:
        url = _get_git_remote_url(Path(cwd))
        _remote_cache[cwd] = _extract_repo_name(url) if url else None
    return _remote_cache[cwd]


def _find_project_root_name(cwd: Path) -> str | None:
    """从 cwd 向上查找 marker 文件，返回项目目录名（需 sanity check）"""
    for p in [cwd, *cwd.parents]:
        if any((p / marker).exists() for marker in PROJECT_MARKERS):
            if is_meaningful_name(p.name):
                return p.name
            # 项目根目录名无意义，尝试 parent
            if p.parent and is_meaningful_name(p.parent.name):
                return p.parent.name
            return None
    return None


def clear_cache() -> None:
    """清除 git remote 缓存（MCP config reset 用）"""
    _remote_cache.clear()
