"""Shared input validation helpers for path traversal prevention."""

import re
from pathlib import Path


# Same regex the wizard uses in create_change()
CHANGE_NAME_RE = re.compile(r"^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$")

# Task IDs: alphanumeric, hyphens, dots (e.g. "hangar-0oq.1")
TASK_ID_RE = re.compile(r"^[a-z0-9][a-z0-9.\-]*[a-z0-9]$|^[a-z0-9]$")


def validate_change_name(name: str) -> str:
    """Validate that a change name is safe for filesystem use.

    Allows lowercase alphanumeric and hyphens only — no dots, slashes,
    or other characters that could enable path traversal.

    Returns the validated name (unchanged).
    Raises ValueError if the name is invalid.
    """
    if not CHANGE_NAME_RE.match(name):
        raise ValueError(
            f"Invalid change name '{name}'. "
            "Use lowercase letters, numbers, and hyphens only."
        )
    return name


def validate_task_id(task_id: str) -> str:
    """Validate that a task ID is safe for use in CLI commands.

    Allows lowercase alphanumeric, hyphens, and dots (e.g. hangar-0oq.1).

    Returns the validated task_id (unchanged).
    Raises ValueError if the task_id is invalid.
    """
    if not TASK_ID_RE.match(task_id):
        raise ValueError(
            f"Invalid task ID '{task_id}'. "
            "Use lowercase letters, numbers, hyphens, and dots only."
        )
    return task_id


def validate_path_containment(base_dir: Path, untrusted_path: str) -> Path:
    """Resolve an untrusted relative path and verify it stays within base_dir.

    Returns the resolved absolute Path.
    Raises ValueError if the path escapes the base directory.
    """
    # Resolve the base to an absolute path
    base = base_dir.resolve()
    # Join and resolve the untrusted path
    target = (base_dir / untrusted_path).resolve()

    # Check containment: target must start with base
    if not str(target).startswith(str(base) + "/") and target != base:
        raise ValueError(
            f"Path '{untrusted_path}' escapes the allowed directory"
        )

    return target
