//===----------------------------------------------------------------------===//
// 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 ContainerResource
import ContainerXPC
import ContainerizationError
import ContainerizationExtras
import ContainerizationOS
import Foundation

/// A client for managing virtual networks through the container API server.
///
/// `NetworkClient` communicates with `container-apiserver` over XPC to create,
/// list, inspect, and delete networks. Each instance holds a dedicated XPC
/// connection; create one client and reuse it across related operations.
///
/// ```swift
/// let client = NetworkClient()
/// let network = try await client.create(configuration: config)
/// let networks = try await client.list()
/// try await client.delete(id: network.id)
/// ```
public struct NetworkClient: Sendable {
    /// The Mach service name used to locate the container API server.
    ///
    /// Pass a different value to ``init(serviceIdentifier:)`` to connect to an
    /// alternative service endpoint, for example during testing.
    public static let defaultServiceIdentifier = "com.apple.container.apiserver"

    /// The name of the default network created automatically on first use.
    public static let defaultNetworkName = "default"

    /// The reserved name that indicates a container should have no network attachment.
    public static let noNetworkName = "none"

    private let xpcClient: XPCClient

    /// Creates a new network client connected to the given service endpoint.
    ///
    /// - Parameter serviceIdentifier: The Mach service name of the API server.
    ///   Defaults to ``defaultServiceIdentifier``.
    public init(serviceIdentifier: String = Self.defaultServiceIdentifier) {
        self.xpcClient = XPCClient(service: serviceIdentifier)
    }

    @discardableResult
    private func xpcSend(
        message: XPCMessage,
        timeout: Duration? = XPCClient.xpcRegistrationTimeout
    ) async throws -> XPCMessage {
        try await xpcClient.send(message, responseTimeout: timeout)
    }

    /// Creates a new network with the given configuration.
    ///
    /// The API server launches a network plugin instance for the new network and
    /// returns a ``NetworkResource`` reflecting the network once it is running.
    ///
    /// - Parameter configuration: The configuration describing the network to create.
    /// - Returns: The running state of the newly created network.
    /// - Throws: ``ContainerizationError`` if the server does not return a valid
    ///   network resource, or if the underlying XPC call fails.
    public func create(configuration: NetworkConfiguration) async throws -> NetworkResource {
        let request = XPCMessage(route: .networkCreate)
        request.set(key: .networkId, value: configuration.id)

        let data = try JSONEncoder().encode(configuration)
        request.set(key: .networkConfig, value: data)

        let response = try await xpcSend(message: request)

        guard let resourceData = response.dataNoCopy(key: .networkResource) else {
            throw ContainerizationError(.invalidArgument, message: "network configuration not received")
        }
        return try JSONDecoder().decode(NetworkResource.self, from: resourceData)
    }

    /// Returns the current state of all networks known to the API server.
    ///
    /// - Returns: An array of ``NetworkResource`` values, or an empty array if no
    ///   networks exist or the server returns no data.
    /// - Throws: ``ContainerizationError`` if the underlying XPC call fails.
    public func list() async throws -> [NetworkResource] {
        let request = XPCMessage(route: .networkList)

        let response = try await xpcSend(message: request, timeout: .seconds(1))

        guard let resourceData = response.dataNoCopy(key: .networkResources) else {
            return []
        }
        return try JSONDecoder().decode([NetworkResource].self, from: resourceData)
    }

    /// Returns the network with the given identifier.
    ///
    /// - Parameter id: The identifier of the network to look up.
    /// - Returns: The ``NetworkResource`` for the matching network.
    /// - Throws: ``ContainerizationError/notFound`` if no network with the given
    ///   identifier exists, or a communication error if the XPC call fails.
    public func get(id: String) async throws -> NetworkResource {
        let networks = try await list()
        guard let network = networks.first(where: { $0.id == id }) else {
            throw ContainerizationError(.notFound, message: "network \(id) not found")
        }
        return network
    }

    /// Deletes the network with the given identifier.
    ///
    /// Deletion succeeds only when no containers are currently attached to the
    /// network. The default network cannot be deleted.
    ///
    /// - Parameter id: The identifier of the network to delete.
    /// - Throws: ``ContainerizationError`` if the network has active attachments,
    ///   if the network is the built-in default, or if the XPC call fails.
    public func delete(id: String) async throws {
        let request = XPCMessage(route: .networkDelete)
        request.set(key: .networkId, value: id)
        try await xpcSend(message: request)
    }

    /// The built-in network, if one exists.
    ///
    /// The built-in network is created automatically on first use and cannot be
    /// deleted. Returns `nil` if the API server cannot find a network with the
    /// built-in resource labels.
    ///
    /// - Throws: ``ContainerizationError`` if the underlying XPC call fails.
    public var builtin: NetworkResource? {
        get async throws {
            try await list().first { $0.isBuiltin }
        }
    }
}
