"""Filesystem hardening, filename sanitization and media signature checks."""

from __future__ import annotations

import os
import re
import shutil
import unicodedata
import uuid
from dataclasses import dataclass
from pathlib import Path

ALLOWED_MEDIA_EXTENSIONS = {
    ".mp4",
    ".m4v",
    ".webm",
    ".mkv",
    ".mov",
    ".avi",
    ".flv",
    ".mpg",
    ".mpeg",
    ".ts",
    ".m2ts",
    ".ogv",
}
ALLOWED_THUMBNAIL_EXTENSIONS = {".jpg", ".jpeg", ".png", ".webp"}
_WINDOWS_RESERVED = {
    "con",
    "prn",
    "aux",
    "nul",
    *(f"com{i}" for i in range(1, 10)),
    *(f"lpt{i}" for i in range(1, 10)),
}
_SAFE_FILENAME_RE = re.compile(r"[^\w\-. ()\[\]\u0600-\u06FF]+", re.UNICODE)
_CONTROL_RE = re.compile(r"[\x00-\x1f\x7f]")
_HTML_PREFIXES = (b"<!doctype html", b"<html", b"<?xml", b"<head", b"<body")
_EXECUTABLE_PREFIXES = (
    b"MZ",
    b"\x7fELF",
    b"\xfe\xed\xfa\xce",
    b"\xfe\xed\xfa\xcf",
    b"\xce\xfa\xed\xfe",
    b"\xcf\xfa\xed\xfe",
    b"\xca\xfe\xba\xbe",
    b"#!",
)


class UnsafePathError(ValueError):
    """Raised when a path would escape the configured download root."""


class InvalidMediaFileError(ValueError):
    """Raised when content is not a permitted video container."""


@dataclass(frozen=True, slots=True)
class MediaProbe:
    mime_type: str
    extension: str
    size: int
    container: str


def utc_filename_stem() -> str:
    return uuid.uuid4().hex


def sanitize_filename(name: str, max_length: int = 110, fallback: str = "video") -> str:
    """Create a conservative filename component, never a path."""

    raw = unicodedata.normalize("NFKC", name or "")
    raw = raw.replace("\\", "/").split("/")[-1]
    raw = _CONTROL_RE.sub(" ", raw)
    raw = _SAFE_FILENAME_RE.sub("_", raw)
    raw = re.sub(r"\s+", " ", raw).strip(" .")
    if not raw or raw in {".", ".."}:
        raw = fallback
    stem, dot, ext = raw.rpartition(".")
    if dot and stem:
        stem = stem.rstrip(" .")
        normalized_stem = stem.lower().strip()
        suffix = "." + ext.lower()[:10]
        if normalized_stem in _WINDOWS_RESERVED:
            stem = f"_{stem}"
        raw = stem + suffix
    elif raw.lower() in _WINDOWS_RESERVED:
        raw = f"_{raw}"
    raw = raw[:max_length].rstrip(" .")
    return raw or fallback


def safe_join(base: Path, *parts: str | Path) -> Path:
    """Join under base and verify the resolved path cannot escape base."""

    base_resolved = base.resolve()
    candidate = base_resolved.joinpath(*parts)
    resolved = candidate.resolve(strict=False)
    try:
        resolved.relative_to(base_resolved)
    except ValueError as exc:
        raise UnsafePathError(f"path escapes allowed directory: {candidate}") from exc
    return resolved


def ensure_inside(path: Path, root: Path) -> Path:
    resolved = path.resolve(strict=False)
    try:
        resolved.relative_to(root.resolve())
    except ValueError as exc:
        raise UnsafePathError(f"path is outside allowed root: {path}") from exc
    return resolved


def is_safe_extension(extension: str) -> bool:
    return extension.lower() in ALLOWED_MEDIA_EXTENSIONS


def build_download_filename(title: str, extension: str = ".mp4") -> str:
    ext = extension.lower()
    if ext not in ALLOWED_MEDIA_EXTENSIONS:
        ext = ".mp4"
    stem = sanitize_filename(title, max_length=80, fallback="video")
    if Path(stem).suffix.lower() == ext:
        stem = Path(stem).stem
    return f"{utc_filename_stem()[:8]}-{stem}{ext}"


def sniff_media_header(header: bytes) -> tuple[str, str, str] | None:
    """Identify permitted video containers from magic bytes.

    Returns (mime, extension, container) or None. HTML, scripts and executables
    intentionally never match.
    """

    data = header[:512]
    lower = data[:32].lower()
    if data.startswith(_EXECUTABLE_PREFIXES) or data.startswith(b"\x00asm"):
        return None
    if lower.lstrip().startswith(_HTML_PREFIXES):
        return None

    if len(data) >= 12 and data[4:8] == b"ftyp":
        brand = data[8:12]
        if brand in {b"M4A ", b"M4B ", b"M4P ", b"F4A ", b"F4B "}:
            return None
        if brand == b"qt  ":
            return "video/quicktime", ".mov", "mov"
        return "video/mp4", ".mp4", "mp4"
    if data.startswith(b"\x1aE\xdf\xa3"):
        return "video/webm", ".webm", "webm"
    if data.startswith(b"OggS"):
        return "video/ogg", ".ogv", "ogg"
    if len(data) >= 12 and data.startswith(b"RIFF") and data[8:12] == b"AVI ":
        return "video/x-msvideo", ".avi", "avi"
    if data.startswith(b"FLV\x01"):
        return "video/x-flv", ".flv", "flv"
    if data.startswith((b"\x00\x00\x01\xba", b"\x00\x00\x01\xb3")):
        return "video/mpeg", ".mpg", "mpeg-ps"
    if len(data) >= 189 and data[0] == 0x47 and data[188] == 0x47:
        return "video/mp2t", ".ts", "mpeg-ts"
    if data.startswith(b"ID3"):
        return None
    return None


def _reject_symlink(path: Path) -> None:
    if path.is_symlink():
        raise InvalidMediaFileError("symlinks are not accepted as downloaded media")


def probe_media_file(path: Path, max_size_bytes: int | None = None) -> MediaProbe:
    """Validate a downloaded file as a real, non-executable video container."""

    _reject_symlink(path)
    if not path.exists() or not path.is_file():
        raise InvalidMediaFileError("file does not exist or is not a regular file")
    size = path.stat().st_size
    if size <= 0:
        raise InvalidMediaFileError("file is empty")
    if max_size_bytes is not None and size > max_size_bytes:
        raise InvalidMediaFileError("file exceeds configured size limit")
    with path.open("rb") as handle:
        header = handle.read(512)
    detected = sniff_media_header(header)
    if detected is None:
        raise InvalidMediaFileError("file signature is not a permitted video container")
    mime, extension, container = detected
    return MediaProbe(mime_type=mime, extension=extension, size=size, container=container)


def probe_image_file(path: Path, max_size_bytes: int = 5 * 1024 * 1024) -> str:
    """Validate JPEG/PNG/WebP thumbnail files without trusting their extension."""

    _reject_symlink(path)
    if not path.exists() or not path.is_file():
        raise InvalidMediaFileError("thumbnail does not exist")
    size = path.stat().st_size
    if size <= 0 or size > max_size_bytes:
        raise InvalidMediaFileError("thumbnail size is invalid")
    with path.open("rb") as handle:
        header = handle.read(32)
    if header.startswith(b"\xff\xd8\xff"):
        return "image/jpeg"
    if header.startswith(b"\x89PNG\r\n\x1a\n"):
        return "image/png"
    if header.startswith(b"RIFF") and header[8:12] == b"WEBP":
        return "image/webp"
    raise InvalidMediaFileError("thumbnail is not JPEG, PNG or WebP")


def check_disk_space(path: Path, required_bytes: int, reserve_bytes: int = 64 * 1024 * 1024) -> None:
    target = path if path.exists() else path.parent
    target.mkdir(parents=True, exist_ok=True)
    free = shutil.disk_usage(target).free
    if free < required_bytes + reserve_bytes:
        raise OSError(
            f"insufficient free disk space: need {required_bytes + reserve_bytes}, free {free}"
        )


def remove_file(path: Path | None) -> None:
    if path is None:
        return
    try:
        if path.is_file() and not path.is_symlink():
            path.unlink()
    except OSError:
        pass


def cleanup_stale_temp_files(directory: Path, max_age_seconds: int = 24 * 3600) -> int:
    """Best-effort cleanup of abandoned .part files in the owned temp directory."""

    import time

    if not directory.exists():
        return 0
    now = time.time()
    removed = 0
    for path in directory.iterdir():
        try:
            if path.is_file() and (now - path.stat().st_mtime) > max_age_seconds:
                path.unlink()
                removed += 1
        except OSError:
            continue
    return removed


def human_size(num_bytes: int | None) -> str:
    if num_bytes is None:
        return "نامشخص"
    value = float(max(0, num_bytes))
    for unit in ("B", "KB", "MB", "GB", "TB"):
        if value < 1024 or unit == "TB":
            if unit == "B":
                return f"{int(value)} B"
            return f"{value:.1f} {unit}"
        value /= 1024
    return f"{value:.1f} TB"


def random_part_path(directory: Path) -> Path:
    return safe_join(directory, f"{uuid.uuid4().hex}.part")


def fsync_directory(path: Path) -> None:
    """Best-effort directory fsync (not available on Windows)."""

    if os.name == "nt":
        return
    try:
        fd = os.open(path, os.O_RDONLY)
    except OSError:
        return
    try:
        os.fsync(fd)
    except OSError:
        pass
    finally:
        os.close(fd)
