//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the Containerization project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//

import ArgumentParser
import CVersion
import ContainerizationOS
import Foundation
import Logging
import VminitdCore

@main
struct Application: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "vminitd",
        abstract: "Virtual machine init daemon",
        version: "0.1.0",
        subcommands: [
            AgentCommand.self,
            InitCommand.self,
            PauseCommand.self,
        ],
        defaultSubcommand: AgentCommand.self
    )

    static func main() async throws {
        setVersionMetadata(Self.versionMetadata())

        // Busybox-style: if invoked as .cz-init, run init mode directly.
        let invoked = CommandLine.arguments.first?.split(separator: "/").last.map(String.init) ?? ""
        if invoked == ".cz-init" {
            let args = Array(CommandLine.arguments.dropFirst())
            var command = try InitCommand.parse(args)
            try command.run()
            return
        }

        // Swift has issues spawning threads if /proc isn't mounted,
        // so we do this synchronously before any async code runs.
        try mountProc()

        // When running as PID 1 with a Musl-static build, Swift's runtime
        // captures argc/argv as empty. Recover argv from /proc/self/cmdline.
        var command = try parseAsRoot(Self.procSelfArgv())
        if let asyncCommand = command as? AsyncParsableCommand {
            nonisolated(unsafe) var unsafeCommand = asyncCommand
            try await unsafeCommand.run()
        } else {
            try command.run()
        }
    }

    private static func versionMetadata() -> Logger.Metadata {
        let gitCommit = String(cString: CZ_get_git_commit())
        let gitTag = String(cString: CZ_get_git_tag())
        let buildTime = String(cString: CZ_get_build_time())
        var metadata: Logger.Metadata = ["commit": "\(gitCommit)", "built": "\(buildTime)"]
        if !gitTag.isEmpty {
            metadata["tag"] = "\(gitTag)"
        }
        return metadata
    }

    private static func mountProc() throws {
        if isProcMounted() {
            return
        }

        let mnt = ContainerizationOS.Mount(
            type: "proc",
            source: "proc",
            target: "/proc",
            options: []
        )
        try mnt.mount(createWithPerms: 0o755)
    }

    // /proc/self/cmdline holds argv as NUL-separated bytes. Read it after
    // mountProc(). Returns argv minus argv[0], suitable for parseAsRoot(_:).
    private static func procSelfArgv() -> [String] {
        guard let data = try? Data(contentsOf: URL(fileURLWithPath: "/proc/self/cmdline")) else {
            return []
        }
        let parts = data.split(separator: 0, omittingEmptySubsequences: true)
            .map { String(decoding: $0, as: UTF8.self) }
        return Array(parts.dropFirst())
    }

    private static func isProcMounted() -> Bool {
        guard let data = try? String(contentsOfFile: "/proc/mounts", encoding: .utf8) else {
            return false
        }

        for line in data.split(separator: "\n") {
            let fields = line.split(separator: " ")
            if fields.count >= 2 {
                let mountPoint = String(fields[1])
                if mountPoint == "/proc" {
                    return true
                }
            }
        }

        return false
    }
}
