//===----------------------------------------------------------------------===//
// Copyright © 2026 Apple Inc. and the container 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 ContainerAPIClient
import ContainerPersistence
import ContainerResource
import ContainerizationError
import ContainerizationOCI
import Foundation
import MachineAPIClient
import TerminalProgress

extension Application {
    public struct MachineCreate: AsyncLoggableCommand {
        public init() {}

        public static let configuration = CommandConfiguration(
            commandName: "create",
            abstract: "Create a new container machine and boot it")

        @OptionGroup(title: "Management options")
        var managementFlags: Flags.MachineManagement

        @OptionGroup(title: "Registry options")
        var registryFlags: Flags.Registry

        @OptionGroup(title: "Progress options")
        var progressFlags: Flags.Progress

        @OptionGroup(title: "Image fetch options")
        var imageFetchFlags: Flags.ImageFetch

        @OptionGroup
        public var logOptions: Flags.Logging

        @Option(name: [.short, .long], help: "Name for the container machine")
        public var name: String?

        @Flag(name: .long, help: "Set this container machine as the default")
        public var setDefault: Bool = false

        @Flag(name: .long, help: "Create the container machine without booting it")
        public var noBoot: Bool = false

        @Option(name: .long, help: "Number of virtual CPUs")
        public var cpus: Int?

        @Option(name: .long, help: "Memory allocation (e.g., 2G, 8G). Default: half of system memory")
        public var memory: String?

        @Option(name: .long, help: "User's home directory mount option (ro, rw, none). Default: rw")
        public var homeMount: String?

        @Flag(name: .long, help: "Enable nested virtualization (requires Apple Silicon M3+ and macOS 15+ and kernel with CONFIG_KVM=y)")
        public var virtualization: Bool = false

        @Option(name: .long, help: "Path to a custom kernel binary (e.g. vmlinux).")
        public var kernel: String?

        @Argument(help: "Container image reference (e.g., alpine:3.22)")
        var image: String

        public func run() async throws {
            if virtualization {
                try MachineCapabilities.requireNestedVirtualizationSupported()
            }
            let resolvedKernel = try kernel.map { try MachineConfig.validateKernelPath($0) }

            let progressConfig = try self.progressFlags.makeConfig(
                showTasks: true,
                showItems: true,
                ignoreSmallSize: true,
                totalTasks: 3
            )
            let progress = ProgressBar(config: progressConfig)
            defer {
                progress.finish()
            }
            progress.start()

            let containerSystemConfig: ContainerSystemConfig = try await ConfigurationLoader.load()
            let defaultConfig = containerSystemConfig.machine

            let bootConfig = try defaultConfig.with(
                [
                    "cpus": cpus.map { "\($0)" },
                    "memory": memory,
                    "home-mount": homeMount,
                    "virtualization": virtualization ? "true" : nil,
                    "kernel": resolvedKernel?.string,
                ].compactMapValues { $0 }
            )

            let id: String
            if let name {
                id = name
            } else {
                let reference = try Reference.parse(image)
                reference.normalize()
                let imageName = reference.name.components(separatedBy: "/").last!
                let suffix = reference.tag ?? reference.digest ?? "latest"
                id = "\(imageName)-\(suffix)"
            }

            guard ManagedContainer.nameValid(id) else {
                throw ContainerizationError(.invalidArgument, message: "machine ID \(id) is not a valid machine ID")
            }

            let client = MachineClient()
            let (config, resources) = try await MachineClient.machineConfigFromFlags(
                id: id,
                image: image,
                management: managementFlags,
                registry: registryFlags,
                imageFetch: imageFetchFlags,
                containerSystemConfig: containerSystemConfig,
                progressUpdate: progress.handler
            )

            do {
                try await client.create(configuration: config, resources: resources, bootConfig: bootConfig)
                progress.finish()  // Finish before subsequent output to avoid mangling
            } catch let error as ContainerizationError {
                if let cause = error.cause as? ContainerizationError, cause.isCode(.exists) {
                    let append = name == nil ? " (missing '-n/--name' flag)" : ""
                    throw ContainerizationError(.exists, message: cause.message + append)
                }
                throw error
            }

            if setDefault {
                try await client.setDefault(id: id)
            }

            if !noBoot {
                try await bootMachine(id: id, client: client, log: log, interactive: false)
            }

            print(id)
        }
    }
}
