"""数据库备份单文件的受限文件系统操作。"""

from __future__ import annotations

import os
import re
import tempfile
from datetime import datetime
from pathlib import Path


_BACKUP_NAME = re.compile(
    r"^(?P<db_type>sqlite|postgresql)_"
    r"(?P<timestamp>\d{8}_\d{6})"
    r"(?:_(?P<sequence>\d+))?"
    r"(?P<suffix>\.db|\.dump)$"
)


class BackupFiles:
    """把备份文件操作限制在一个私有根目录内。"""

    def __init__(self, root: Path) -> None:
        self.root = Path(root)

    def create_temporary(self, suffix: str) -> Path:
        """在最终目录内创建私有临时文件，保证发布可使用原子替换。"""
        self._ensure_root()
        descriptor, filename = tempfile.mkstemp(
            prefix=".database-",
            suffix=f"{suffix}.partial",
            dir=self.root,
        )
        os.close(descriptor)
        path = Path(filename)
        path.chmod(0o600)
        return path

    def publish(self, temporary: Path, name: str) -> Path:
        """把已校验临时文件发布为正式备份文件。"""
        destination = self._resolve_name(name, require_exists=False)
        os.replace(temporary, destination)
        destination.chmod(0o600)
        return destination

    def discard(self, temporary: Path) -> None:
        """清理本次操作拥有的未发布临时文件。"""
        path = Path(temporary)
        if path.parent == self.root and path.name.startswith(".database-"):
            path.unlink(missing_ok=True)

    def list(self) -> list[Path]:
        """返回当前根目录内格式合法的正式备份文件。"""
        if not self.root.is_dir():
            return []
        paths = [
            path
            for path in self.root.iterdir()
            if path.is_file() and _BACKUP_NAME.fullmatch(path.name)
        ]
        return sorted(paths, key=lambda path: (self.created_at(path.name), path.name), reverse=True)

    def resolve(self, name: str) -> Path:
        """按受限文件名解析一个必须存在的备份文件。"""
        return self._resolve_name(name, require_exists=True)

    def delete(self, name: str) -> None:
        """删除一个已通过名称约束的备份文件。"""
        self.resolve(name).unlink()

    def available_name(self, *, db_type: str, created_at: datetime, suffix: str) -> str:
        """生成包含数据库类型和秒级时间的简短可读文件名。"""
        timestamp = created_at.strftime("%Y%m%d_%H%M%S")
        base = f"{db_type}_{timestamp}"
        candidate = f"{base}{suffix}"
        sequence = 1
        while (self.root / candidate).exists():
            candidate = f"{base}_{sequence}{suffix}"
            sequence += 1
        if not _BACKUP_NAME.fullmatch(candidate):
            raise ValueError("数据库备份文件名无效")
        return candidate

    @staticmethod
    def database_type(name: str) -> str:
        """从受管文件名读取数据库类型。"""
        return BackupFiles._match(name).group("db_type")

    @staticmethod
    def created_at(name: str) -> datetime:
        """从受管文件名读取本地创建时间。"""
        return datetime.strptime(
            BackupFiles._match(name).group("timestamp"),
            "%Y%m%d_%H%M%S",
        )

    def _ensure_root(self) -> None:
        self.root.mkdir(parents=True, exist_ok=True, mode=0o700)
        self.root.chmod(0o700)

    def _resolve_name(self, name: str, *, require_exists: bool) -> Path:
        normalized = str(name).strip()
        self._match(normalized)
        if Path(normalized).name != normalized:
            raise ValueError("数据库备份文件名不能包含路径")
        path = self.root / normalized
        if require_exists and not path.is_file():
            raise FileNotFoundError(normalized)
        return path

    @staticmethod
    def _match(name: str) -> re.Match[str]:
        matched = _BACKUP_NAME.fullmatch(str(name))
        if matched is None:
            raise ValueError("数据库备份文件名无效")
        return matched
