import Foundation

/// Owner-only file and directory hardening, in one place, for every platform
/// (docs/WINDOWS_PORT.md §4.3).
///
/// Nucleic writes a fair amount of credential material to disk — agent OAuth token files, the
/// file-backed `SecretStore`, staged `~/.claude` / `~/.codex` copies, the runner's credential
/// vault. On POSIX that hardening is `chmod 0600` (files) / `0700` (directories), spelled inline
/// at ~a dozen call sites as `FileManager` `.posixPermissions` attributes. **On Windows those
/// attributes do nothing**: NTFS has no mode bits, so the same code would silently produce files
/// carrying whatever the parent directory's inherited ACL happens to grant. This type is the seam
/// those call sites now go through, so "owner only" means owner only on every platform.
///
/// The POSIX legs are byte-for-byte the calls they replaced (same `FileManager` API, same modes,
/// same throwing behavior), so macOS and Linux behavior is unchanged by construction. The Windows
/// leg applies a *protected* DACL granting only the current user — see
/// `Windows/FilePermissions.swift`.
public enum SecretFile {
    /// Create `url` (and any missing parents) restricted to the current user.
    ///
    /// POSIX note: the mode applies to directories this call *creates*. An already-existing
    /// directory keeps its mode — matching the previous inline behavior exactly, which several
    /// call sites rely on (a user who deliberately widened `~/.config/nucleic` keeps it).
    public static func createDirectory(at url: URL) throws {
        #if os(Windows)
        let existed = FileManager.default.fileExists(atPath: url.path)
        try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
        if !existed { try? restrictToOwner(path: url.path, isDirectory: true) }
        #else
        try FileManager.default.createDirectory(
            at: url, withIntermediateDirectories: true,
            attributes: [.posixPermissions: 0o700])
        #endif
    }

    /// Atomically write `data` to `url`, then restrict it to the current user.
    ///
    /// The restriction runs *after* the write because the atomic write replaces the file node:
    /// hardening a pre-existing file first would harden the node the rename then discards.
    public static func write(_ data: Data, to url: URL) throws {
        try data.write(to: url, options: .atomic)
        try restrictToOwner(path: url.path, isDirectory: false)
    }

    /// Restrict an existing file or directory to the current user. Throws so a call site that
    /// treated a failed `chmod` as fatal (the runner credential vault) still does; sites that
    /// used `try?` keep using `try?`.
    public static func restrictToOwner(path: String, isDirectory: Bool) throws {
        #if os(Windows)
        try WindowsFilePermissions.restrictToOwner(path: path, isDirectory: isDirectory)
        #else
        try FileManager.default.setAttributes(
            [.posixPermissions: isDirectory ? 0o700 : 0o600], ofItemAtPath: path)
        #endif
    }
}
