"""File handling for video uploads: client-supplied names are never used as paths, and every file
the app opens for TikTok lives directly inside a configured folder under a server-chosen name."""
import os
from collections import namedtuple
from uuid import uuid4

from werkzeug.utils import secure_filename

from app.services.errors import ValidationError

VIDEO_EXTENSIONS = frozenset({".mp4", ".mov", ".webm"})

StoredUpload = namedtuple("StoredUpload", ["path", "original_name"])


def _confined_path(folder: str, name: str) -> str:
    """Return the real path of `name` inside `folder`, refusing anything that resolves elsewhere."""
    root = os.path.realpath(folder)
    path = os.path.realpath(os.path.join(root, name))
    if os.path.dirname(path) != root:
        raise ValidationError("file name resolves outside its folder")
    return path


def _checked_extension(name: object, allowed) -> str:
    """Return the lower-case extension of `name`; raises ValidationError when it is not in `allowed`."""
    if not isinstance(name, str) or not name:
        raise ValidationError("file name is required")
    ext = os.path.splitext(name)[1].lower()
    if allowed is not None and ext not in allowed:
        raise ValidationError("file type not allowed; expected one of " + ", ".join(sorted(allowed)))
    return ext


def store_upload(file, folder: str, allowed_extensions=None, max_bytes=None) -> StoredUpload:
    """Save a client file under a server-generated uuid name inside `folder`; raises
    ValidationError for a bad name/extension/size. The caller decides when to remove the file."""
    ext = _checked_extension(file.filename, allowed_extensions)
    original_name = secure_filename(file.filename)
    os.makedirs(folder, exist_ok=True)
    stored_name = uuid4().hex + ext
    path = _confined_path(folder, stored_name)
    file.save(path)
    if max_bytes is not None and os.path.getsize(path) > max_bytes:
        remove_quietly(path)
        raise ValidationError("file is larger than the allowed size")
    return StoredUpload(path=path, original_name=original_name or stored_name)


def remove_quietly(path: str) -> None:
    """Delete a file, swallowing every error: cleanup must never mask the real failure."""
    try:
        os.remove(path)
    except OSError:
        pass
