"""
路径安全策略模块

提供 workspace 和 plan-mode 的路径安全校验。
使用 pathlib 确保跨平台兼容性。
"""

from dataclasses import dataclass
from pathlib import Path
from typing import List, Optional, Tuple
from loguru import logger


# ============================================================================
# 数据结构
# ============================================================================


@dataclass
class PathValidationResult:
    """路径验证结果。

    Attributes:
        valid: 是否通过验证
        abs_path: 解析后的绝对路径（验证通过时有值）
        error_message: 验证失败时的错误消息
    """

    valid: bool
    abs_path: Optional[Path] = None
    error_message: Optional[str] = None


@dataclass
class PathPolicy:
    """路径安全策略配置。

    Attributes:
        workspace_root: 工作区根目录
        allow_outside_workspace: 是否允许工作区外的文件
        plan_mode: 是否处于 plan 模式
        plans_dir: plan 模式下允许的目录
    """

    workspace_root: Path
    allow_outside_workspace: bool = False
    plan_mode: bool = False
    plans_dir: Optional[Path] = None

    @classmethod
    def from_args(cls, args, source_dir: str) -> "PathPolicy":
        """从 AutoCoderArgs 创建策略配置。

        Args:
            args: AutoCoderArgs 实例
            source_dir: 源目录

        Returns:
            PathPolicy 实例
        """
        workspace_root = Path(source_dir).resolve()
        plans_dir = Path.home() / ".auto-coder" / "plans"

        return cls(
            workspace_root=workspace_root,
            allow_outside_workspace=args.allow_files_outside_workspace or False,
            plan_mode=args.agentic_mode == "plan",
            plans_dir=plans_dir,
        )


# ============================================================================
# 路径验证函数
# ============================================================================


def resolve_file_path(file_path: str, workspace_root: Path) -> Path:
    """解析文件路径为绝对路径。

    Args:
        file_path: 相对或绝对文件路径
        workspace_root: 工作区根目录

    Returns:
        解析后的绝对路径
    """
    path = Path(file_path)
    if path.is_absolute():
        return path.resolve()
    return (workspace_root / path).resolve()


def is_path_within_directory(path: Path, directory: Path) -> bool:
    """检查路径是否在指定目录内。

    使用 is_relative_to（Python 3.9+）或兼容实现。

    Args:
        path: 要检查的路径
        directory: 目录路径

    Returns:
        是否在目录内
    """
    try:
        resolved_path = path.resolve()
        resolved_dir = directory.resolve()
        # Python 3.9+ 支持 is_relative_to
        if hasattr(resolved_path, "is_relative_to"):
            return resolved_path.is_relative_to(resolved_dir)
        # Python 3.8 兼容实现
        try:
            resolved_path.relative_to(resolved_dir)
            return True
        except ValueError:
            return False
    except Exception as e:
        logger.warning(f"Error checking path containment: {e}")
        return False


def is_in_plans_directory(file_path: Path) -> bool:
    """检查文件路径是否在 ~/.auto-coder/plans 目录内。

    Args:
        file_path: 文件路径

    Returns:
        是否在 plans 目录内
    """
    plans_dir = Path.home() / ".auto-coder" / "plans"
    return is_path_within_directory(file_path, plans_dir)


def validate_file_path(
    file_path: str,
    policy: PathPolicy,
) -> PathValidationResult:
    """验证文件路径是否符合安全策略。

    Args:
        file_path: 相对或绝对文件路径
        policy: 路径安全策略

    Returns:
        PathValidationResult 对象
    """
    abs_path = resolve_file_path(file_path, policy.workspace_root)

    # Plan mode 限制
    if policy.plan_mode:
        plans_dir = policy.plans_dir or (Path.home() / ".auto-coder" / "plans")
        if not is_path_within_directory(abs_path, plans_dir):
            return PathValidationResult(
                valid=False,
                abs_path=abs_path,
                error_message=(
                    f"Currently in plan mode, only writing to {plans_dir} directory is allowed. "
                    f"File '{file_path}' is not in the plans directory. "
                    f"Please save your plan file to {plans_dir}/<plan-name>_<uuid>.plan.md"
                ),
            )
        # 处于 plan mode 时，只允许 plans_dir 内的文件，不再额外施加 workspace 限制
        return PathValidationResult(valid=True, abs_path=abs_path)

    # Workspace 限制
    if not policy.allow_outside_workspace:
        if not is_path_within_directory(abs_path, policy.workspace_root):
            return PathValidationResult(
                valid=False,
                abs_path=abs_path,
                error_message=(
                    f"Access denied: File '{file_path}' is outside the workspace. "
                    f"Workspace root: {policy.workspace_root}"
                ),
            )

    return PathValidationResult(valid=True, abs_path=abs_path)


def validate_multiple_file_paths(
    file_paths: List[str],
    policy: PathPolicy,
) -> Tuple[bool, List[PathValidationResult]]:
    """验证多个文件路径是否符合安全策略。

    Args:
        file_paths: 文件路径列表
        policy: 路径安全策略

    Returns:
        (all_valid, results) 元组
    """
    results: List[PathValidationResult] = []
    all_valid = True

    for file_path in file_paths:
        result = validate_file_path(file_path, policy)
        results.append(result)
        if not result.valid:
            all_valid = False

    return all_valid, results


# ============================================================================
# 便捷函数
# ============================================================================


def get_plans_directory() -> Path:
    """获取 plans 目录路径。

    Returns:
        ~/.auto-coder/plans 的 Path 对象
    """
    return Path.home() / ".auto-coder" / "plans"


def ensure_plans_directory_exists() -> Path:
    """确保 plans 目录存在。

    Returns:
        plans 目录的 Path 对象
    """
    plans_dir = get_plans_directory()
    plans_dir.mkdir(parents=True, exist_ok=True)
    return plans_dir
