import Foundation

/// nash's trusted operator policy, as the guest agent sees it (docs/NASH.md §4.3).
///
/// The Swift twin of `shell/nash/src/policy.rs` — deliberately a *reader only*, and deliberately
/// the same file nash itself reads, so the daemon's shell choice and the shells nash goes on to
/// spawn cannot disagree about whether observation is on.
///
/// Why a file and not an environment variable: the levers here (the kill switch, the real-bash
/// target) are operator levers, and the agent owns its own environment — it writes the command
/// line and can `export` anything. A lever in the environment is therefore a lever the observed
/// process can pull, which is how `NUCLEIC_NASH_DISABLE=1 <command>` used to be a complete, silent
/// opt-out of the agent shell. Ownership is what makes this channel different, so the ownership is
/// checked before a single line is honored.
public struct NashPolicy: Sendable, Equatable {
    /// Operator break-glass: run the legacy shell instead of nash.
    public var disable: Bool = false
    /// Observation may not be turned off by the observed process.
    public var requireObservation: Bool = false

    /// Where the guest's policy lives. The macOS twin of the Linux `/etc/nucleic/nash.conf`; an
    /// operator writes it with `sudo` inside the guest, which is exactly the friction intended —
    /// nothing an agent's command sets reaches it.
    public static let path = "/Library/Application Support/Nucleic/nash.conf"

    /// Read the policy, or defaults (observe, don't disable) when there is no trusted file.
    ///
    /// Trusted means owned by uid 0 and not group/other-writable, and not a symlink — a file the
    /// agent could have written carries the agent's uid and is ignored. Failing open is correct in
    /// both directions here: no file means no kill switch (not an agent-steerable one), and a
    /// policy that can't be read must never stop a command from running (§5.5).
    public static func load(path: String = NashPolicy.path) -> NashPolicy {
        var policy = NashPolicy()
        guard let attrs = try? FileManager.default.attributesOfItem(atPath: path),
            (attrs[.type] as? FileAttributeType) == .typeRegular,
            (attrs[.ownerAccountID] as? NSNumber)?.intValue == 0,
            let permissions = (attrs[.posixPermissions] as? NSNumber)?.intValue,
            permissions & 0o022 == 0,
            let text = try? String(contentsOfFile: path, encoding: .utf8)
        else { return policy }

        for rawLine in text.split(separator: "\n") {
            let line = rawLine.trimmingCharacters(in: .whitespaces)
            guard !line.isEmpty, !line.hasPrefix("#"),
                let separator = line.firstIndex(of: "=")
            else { continue }
            let key = line[line.startIndex..<separator].trimmingCharacters(in: .whitespaces)
            let value = line[line.index(after: separator)...].trimmingCharacters(in: .whitespaces)
            let isTrue = ["1", "true", "yes", "on"].contains(value)
            switch key {
            case "disable": policy.disable = isTrue
            case "require_observation": policy.requireObservation = isTrue
            default: break
            }
        }
        return policy
    }
}
