//===----------------------------------------------------------------------===//
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
//
// 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 ContainerClient
import ContainerLog
import ContainerNetworkService
import ContainerSandboxService
import ContainerVersion
import ContainerXPC
import Containerization
import ContainerizationError
import Foundation
import Logging
import NIO

@main
struct RuntimeLinuxHelper: AsyncParsableCommand {
    static let label = "com.apple.container.runtime.container-runtime-linux"

    static let configuration = CommandConfiguration(
        commandName: "container-runtime-linux",
        abstract: "XPC Service for managing a Linux sandbox",
        version: ReleaseVersion.singleLine(appName: "container-runtime-linux")
    )

    @Flag(name: .long, help: "Enable debug logging")
    var debug = false

    @Option(name: .shortAndLong, help: "Sandbox UUID")
    var uuid: String

    @Option(name: .shortAndLong, help: "Root directory for the sandbox")
    var root: String

    var machServiceLabel: String {
        "\(Self.label).\(uuid)"
    }

    func run() async throws {
        let commandName = Self._commandName
        let log = setupLogger()
        log.info("starting \(commandName)")
        defer {
            log.info("stopping \(commandName)")
        }

        let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
        do {
            try adjustLimits()
            signal(SIGPIPE, SIG_IGN)

            log.info("configuring XPC server")
            let interfaceStrategy: any InterfaceStrategy
            if #available(macOS 26, *) {
                interfaceStrategy = NonisolatedInterfaceStrategy(log: log)
            } else {
                interfaceStrategy = IsolatedInterfaceStrategy()
            }
            let server = SandboxService(root: .init(fileURLWithPath: root), interfaceStrategy: interfaceStrategy, eventLoopGroup: eventLoopGroup, log: log)
            let xpc = XPCServer(
                identifier: machServiceLabel,
                routes: [
                    SandboxRoutes.bootstrap.rawValue: server.bootstrap,
                    SandboxRoutes.createProcess.rawValue: server.createProcess,
                    SandboxRoutes.state.rawValue: server.state,
                    SandboxRoutes.stop.rawValue: server.stop,
                    SandboxRoutes.kill.rawValue: server.kill,
                    SandboxRoutes.resize.rawValue: server.resize,
                    SandboxRoutes.wait.rawValue: server.wait,
                    SandboxRoutes.start.rawValue: server.startProcess,
                    SandboxRoutes.dial.rawValue: server.dial,
                ],
                log: log
            )

            log.info("starting XPC server")
            try await xpc.listen()
        } catch {
            log.error("\(commandName) failed", metadata: ["error": "\(error)"])
            try? await eventLoopGroup.shutdownGracefully()
            RuntimeLinuxHelper.exit(withError: error)
        }
    }

    private func setupLogger() -> Logger {
        LoggingSystem.bootstrap { label in
            OSLogHandler(
                label: label,
                category: "RuntimeLinuxHelper"
            )
        }
        var log = Logger(label: "com.apple.container")
        if debug {
            log.logLevel = .debug
        }
        log[metadataKey: "uuid"] = "\(uuid)"
        return log
    }

    private func adjustLimits() throws {
        var limits = rlimit()
        guard getrlimit(RLIMIT_NOFILE, &limits) == 0 else {
            throw POSIXError(.init(rawValue: errno)!)
        }
        limits.rlim_cur = 65536
        limits.rlim_max = 65536
        guard setrlimit(RLIMIT_NOFILE, &limits) == 0 else {
            throw POSIXError(.init(rawValue: errno)!)
        }
    }
}
