import Foundation
import os

/// Diagnostics for the guest host-path re-creation step (best-effort; failures never fail a boot).
private let hostPathLog = Logger(subsystem: "com.nucleic", category: "macvm.hostpaths")

// MARK: - Host-path re-creation inside the guest
//
// A session's repo lives at some absolute path on the host (e.g. `/Users/me/proj`) but virtiofs
// surfaces it at a *different* guest path — `/Volumes/My Shared Files/<name>` on a macOS guest,
// `/mnt/nucleic/<name>` on a Linux guest (the container sandbox avoids this by bind-mounting at the
// identical path; VMs can't). Any build artifact that captured an absolute path — SwiftPM's `.build`,
// Xcode DerivedData, `compile_commands.json`, `.o` debug maps, generated project references, package
// caches — then points at a directory that doesn't exist in the guest, and the build fails.
//
// The fix: re-create each share's original host path inside the guest, pointing it at the shared copy.
// The mechanism is per-OS (see ``MacVMSpec/Mount/recreateHostPath``):
//   • **Linux** bind-mounts the share at the identical path, so `<host>` is a real directory backed by
//     the same virtiofs inode — highest fidelity, matching the container's identical-path model. If the
//     bind can't be made it falls back to a symlink.
//   • **macOS** symlinks `<host>` → the automounted share, because macOS only automounts virtiofs at the
//     fixed `/Volumes/My Shared Files/<name>` location and arbitrary mount points aren't supported. The
//     provisioned `agent` account's passwordless sudo creates the parent chain + the link.
//
// The script is idempotent (safe to run on every boot / clone restart) and best-effort (a failure for
// one share never aborts the others or the boot). It is built by a pure function so it's unit-testable.

#if arch(arm64)
extension MacVMEngine {
    /// Where a **macOS** guest automounts virtiofs shares (Apple's fixed automount location); each
    /// share appears as `<root>/<name>`.
    public static let macOSShareRoot = "/Volumes/My Shared Files"
    /// Where a **Linux** guest mounts the `nucleicshare` virtiofs tag; each share appears as
    /// `<root>/<name>`. Mirrors the on-demand mount the exec path performs.
    public static let linuxShareRoot = "/mnt/nucleic"

    /// The guest path a share of `name` is surfaced at, for `os`.
    static func guestSharePath(os: GuestOS, name: String) -> String {
        switch os {
        case .macOS: return "\(macOSShareRoot)/\(name)"
        case .linux: return "\(linuxShareRoot)/\(name)"
        }
    }

    /// Build the idempotent shell snippet that re-creates each opted-in mount's original **host** path
    /// inside the guest (bind-mount on Linux, symlink on macOS). Returns `nil` when there's nothing to
    /// do — no mounts, none opted in, or every host path already equals its natural guest path (so
    /// recreation would self-reference). Pure, so it's unit-tested directly (mirrors ``remoteScript``).
    static func hostPathRecreationScript(os: GuestOS, mounts: [MacVMSpec.Mount]) -> String? {
        // Only shares that opted in and whose host path is non-empty and differs from the guest path.
        let targets = mounts.filter { m in
            m.recreateHostPath
                && !m.host.isEmpty
                && m.host != guestSharePath(os: os, name: m.name)
        }
        guard !targets.isEmpty else { return nil }

        switch os {
        case .macOS:
            // Symlink `<host>` → the automounted share. Only touch a path that's absent or already a
            // symlink — never clobber a real directory. `sudo` (passwordless on the provisioned agent
            // account) creates the parent chain and the link.
            var blocks: [String] = []
            for m in targets {
                let host = shQuote(m.host)
                let parent = shQuote(URL(fileURLWithPath: m.host).deletingLastPathComponent().path)
                let target = shQuote(guestSharePath(os: .macOS, name: m.name))
                blocks.append(
                    "if [ -L \(host) ] || [ ! -e \(host) ]; then "
                        + "sudo mkdir -p \(parent) && sudo ln -sfn \(target) \(host); fi")
            }
            return blocks.joined(separator: "\n")

        case .linux:
            // Ensure the session virtiofs is mounted at the share root, then bind-mount each share at its
            // identical host path (symlink fallback if the bind can't be made). Runs as root in the guest.
            let root = shQuote(linuxShareRoot)
            var lines: [String] = [
                "mkdir -p \(root)",
                "mountpoint -q \(root) 2>/dev/null || "
                    + "mount -t virtiofs \(shQuote(nucleicShareTag)) \(root) 2>/dev/null || true",
            ]
            for m in targets {
                let host = shQuote(m.host)
                let src = shQuote(guestSharePath(os: .linux, name: m.name))
                lines.append("mkdir -p \(host)")
                lines.append(
                    "mountpoint -q \(host) 2>/dev/null || mount --bind \(src) \(host) 2>/dev/null || "
                        + "{ rmdir \(host) 2>/dev/null || true; ln -sfn \(src) \(host); }")
            }
            return lines.joined(separator: "\n")
        }
    }

    /// Re-create the spec's host paths inside a freshly-ready guest (called once per boot from
    /// ``performBoot`` after the agent answers). Best-effort: a non-zero exit or a thrown error is logged
    /// and swallowed so a share that can't be reconstructed never fails the boot — the build simply falls
    /// back to the guest share path.
    func recreateHostPaths(_ spec: MacVMSpec) async {
        guard let script = Self.hostPathRecreationScript(os: spec.os, mounts: spec.mounts) else { return }
        do {
            let result = try await run(name: spec.name, command: script, workdir: nil)
            if result.exitCode != 0 {
                hostPathLog.error(
                    "host-path re-creation for \(spec.name, privacy: .public) exited \(result.exitCode, privacy: .public): \(result.stderr.trimmingCharacters(in: .whitespacesAndNewlines), privacy: .public)")
            }
        } catch {
            hostPathLog.error(
                "host-path re-creation for \(spec.name, privacy: .public) failed: \(String(describing: error), privacy: .public)")
        }
    }
}
#endif
