from __future__ import annotations

import hashlib
import os
import re
from pathlib import Path
from typing import BinaryIO

_SAFE_SEGMENT = re.compile(r"[^A-Za-z0-9._ -]+")


def sanitize_segment(value: str, fallback: str = "item") -> str:
    value = (value or "").strip().replace("/", "_").replace("\\", "_")
    value = _SAFE_SEGMENT.sub("_", value)
    value = value.strip(" ._")
    return value or fallback


def safe_relative_path(raw_path: str | None, fallback_filename: str) -> Path:
    """Return a safe relative path, preventing path traversal outside storage root."""
    if raw_path:
        parts = []
        for part in Path(str(raw_path).replace("\\", "/")).parts:
            if part in ("", ".", ".."):
                continue
            parts.append(sanitize_segment(part))
        if parts:
            return Path(*parts)
    return Path(sanitize_segment(fallback_filename, "document.bin"))


def resolve_under_root(storage_root: Path, relative_path: Path) -> Path:
    target = (storage_root / relative_path).resolve()
    root = storage_root.resolve()
    if os.path.commonpath([str(root), str(target)]) != str(root):
        raise RuntimeError("Refusing to write/read outside storage root")
    target.parent.mkdir(parents=True, exist_ok=True)
    return target


def sha256_file(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as handle:
        for chunk in iter(lambda: handle.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def write_stream_to_file(stream: BinaryIO, destination: Path) -> tuple[int, str]:
    digest = hashlib.sha256()
    total = 0
    tmp = destination.with_suffix(destination.suffix + ".part")
    with tmp.open("wb") as out:
        while True:
            chunk = stream.read(1024 * 1024)
            if not chunk:
                break
            out.write(chunk)
            digest.update(chunk)
            total += len(chunk)
    tmp.replace(destination)
    return total, digest.hexdigest()
