namespace MiGu.Server.Infra;

/// <summary>
/// 原子写文件工具：先写同目录临时文件，再用 File.Replace / File.Move 整体替换目标，
/// 避免 File.WriteAllText 写到一半进程崩溃 / 断电导致目标文件被截断成「半个 JSON」。
///
/// 用于 rbac.json / config-*.json / ops-audit.json 等关键持久化文件 —— 这些文件一旦损坏，
/// 加载时会被当成「解析失败」回退默认 seed，进而静默丢失自定义用户 / 角色 / 配置。
/// </summary>
public static class AtomicFile
{
    public static void WriteAllText(string path, string contents)
    {
        var dir = Path.GetDirectoryName(path);
        if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir);

        // 临时名带 GUID：保证同一目标文件的并发写各用独立临时文件，互不覆盖（即便调用方未加锁）。
        var tmp = $"{path}.{Guid.NewGuid():N}.tmp";
        try
        {
            File.WriteAllText(tmp, contents);

            try
            {
                if (File.Exists(path))
                    File.Replace(tmp, path, null);
                else
                    File.Move(tmp, path);
            }
            catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
            {
                // 个别环境（杀软锁定 / 跨卷）File.Replace 会失败：退化为覆盖复制兜底（仍优于半截写入）。
                File.Copy(tmp, path, true);
            }
        }
        finally
        {
            // 兜底清理：File.Replace/Move 成功时 tmp 已不存在；其余异常路径下避免遗留临时文件累积。
            try { if (File.Exists(tmp)) File.Delete(tmp); } catch { /* ignore */ }
        }
    }

    /// <summary>把疑似损坏的文件复制一份带时间戳的备份（不抛异常）。返回备份路径或 null。</summary>
    public static string? BackupCorrupt(string path)
    {
        try
        {
            if (!File.Exists(path)) return null;
            var bak = $"{path}.corrupt-{DateTime.UtcNow:yyyyMMddHHmmss}";
            File.Copy(path, bak, true);
            return bak;
        }
        catch
        {
            return null;
        }
    }
}
