//! Storage Engine 核心
//!
//! 所有本地文件操作的基础能力：
//! - sanitize_*: 路径净化，防御路径遍历
//! - atomic_write_*: 原子写入，防崩溃截断
//! - with_file_lock: 文件锁，防并发竞态
//! - read_json: 安全读取，文件不存在返回 None

use std::fs;
use std::path::Path;
use serde::{de::DeserializeOwned, Serialize};
use thiserror::Error;

/// 存储层统一错误类型
#[derive(Debug, Error)]
pub enum StorageError {
    #[error("Invalid identifier: {0}")]
    InvalidId(String),
    #[error("Path traversal detected")]
    PathTraversal,
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    #[error("Serialization error: {0}")]
    Serialization(#[from] serde_json::Error),
    #[error("Lock acquisition failed: {0}")]
    LockFailed(String),
}

/// 将 StorageError 转换为 String
impl From<StorageError> for String {
    fn from(err: StorageError) -> Self {
        err.to_string()
    }
}

/// ID 白名单校验
///
/// 只允许字母、数字、下划线、连字符。
/// 用于 project_id, avatar_id 等标识符。
pub fn sanitize_id(id: &str) -> Result<String, StorageError> {
    if id.is_empty() || id.len() > 64 {
        return Err(StorageError::InvalidId(
            "empty or too long".into(),
        ));
    }
    if !id.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
        return Err(StorageError::InvalidId(
            "contains illegal characters".into(),
        ));
    }
    Ok(id.to_string())
}

/// 文件名净化
///
/// 提取纯文件名部分，拒绝任何目录组件（如 `../../etc/passwd`）。
/// 返回净化后的文件名字符串。
pub fn sanitize_filename(name: &str) -> Result<String, StorageError> {
    let path = Path::new(name);
    let file_name = path
        .file_name()
        .and_then(|n| n.to_str())
        .ok_or(StorageError::PathTraversal)?;
    if file_name.is_empty() || file_name.len() > 255 {
        return Err(StorageError::InvalidId(
            "invalid filename".into(),
        ));
    }
    Ok(file_name.to_string())
}

/// 原子写入 JSON 文件
///
/// 先写入 `.tmp` 临时文件，再通过 `fs::rename` 原子替换目标文件。
/// 确保崩溃或断电时不会留下半写文件。
pub fn atomic_write_json(
    path: &Path,
    value: &impl Serialize,
) -> Result<(), StorageError> {
    let tmp = path.with_extension("tmp");
    let content = serde_json::to_string_pretty(value)?;
    fs::write(&tmp, content)?;
    fs::rename(&tmp, path)?;
    Ok(())
}

/// 原子写入二进制文件
///
/// 同 `atomic_write_json`，但写入原始字节。
pub fn atomic_write_bytes(
    path: &Path,
    data: &[u8],
) -> Result<(), StorageError> {
    let tmp = path.with_extension("tmp");
    fs::write(&tmp, data)?;
    fs::rename(&tmp, path)?;
    Ok(())
}

/// 在文件锁保护下执行操作
///
/// 对目标文件创建 `.lock` 锁文件，加独占锁后执行闭包，
/// 完成后自动释放锁并清理锁文件。
///
/// 注意：锁粒度为单个文件，不同文件之间互不阻塞。
pub fn with_file_lock<T>(
    path: &Path,
    f: impl FnOnce() -> Result<T, StorageError>,
) -> Result<T, StorageError> {
    let lock_path = path.with_extension("lock");
    let file = fs::OpenOptions::new()
        .write(true)
        .create(true)
        .truncate(true)
        .open(&lock_path)
        .map_err(|e| StorageError::LockFailed(e.to_string()))?;

    fs2::FileExt::lock_exclusive(&file)
        .map_err(|e| StorageError::LockFailed(e.to_string()))?;

    let result = f();

    let _ = fs2::FileExt::unlock(&file);
    let _ = fs::remove_file(&lock_path);

    result
}

/// 安全读取 JSON 文件
///
/// - 文件不存在 → 返回 `Ok(None)`
/// - 解析失败 → 返回 `Err(StorageError::Serialization)`
/// - 成功 → 返回 `Ok(Some(T))`
pub fn read_json<T: DeserializeOwned>(
    path: &Path,
) -> Result<Option<T>, StorageError> {
    match fs::read_to_string(path) {
        Ok(content) => {
            let value = serde_json::from_str(&content)?;
            Ok(Some(value))
        }
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
        Err(e) => Err(e.into()),
    }
}

/// 确保目录存在
///
/// 幂等操作，多线程并发调用安全。
pub fn ensure_dir(path: &Path) -> Result<(), StorageError> {
    if !path.exists() {
        fs::create_dir_all(path)?;
    }
    Ok(())
}

/// 设置 Unix 文件权限为 0600（仅所有者可读写）
///
/// 非 Unix 平台上为无操作。
pub fn restrict_file_permissions(path: &Path) -> Result<(), StorageError> {
    let _ = path; // 非 Unix 平台上避免 unused_variables 警告
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        let mut perms = fs::metadata(path)?.permissions();
        perms.set_mode(0o600);
        fs::set_permissions(path, perms)?;
    }
    Ok(())
}
