//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//

/// A protocol for interacting with a netlink socket.
public protocol NetlinkSocket {
    var pid: UInt32 { get }
    func send(buf: UnsafeRawPointer!, len: Int, flags: Int32) throws -> Int
    func recv(buf: UnsafeMutableRawPointer!, len: Int, flags: Int32) throws -> Int
}

/// A netlink socket provider.
public typealias NetlinkSocketProvider = () throws -> any NetlinkSocket

/// Errors thrown when interacting with a netlink socket.
public enum NetlinkSocketError: Swift.Error, CustomStringConvertible, Equatable {
    case socketFailure(rc: Int32)
    case bindFailure(rc: Int32)
    case sendFailure(rc: Int32)
    case recvFailure(rc: Int32)
    case notImplemented

    /// The description of the errors.
    public var description: String {
        switch self {
        case .socketFailure(let rc):
            return "could not create netlink socket, rc = \(rc)"
        case .bindFailure(let rc):
            return "could not bind netlink socket, rc = \(rc)"
        case .sendFailure(let rc):
            return "could not send netlink packet, rc = \(rc)"
        case .recvFailure(let rc):
            return "could not receive netlink packet, rc = \(rc)"
        case .notImplemented:
            return "socket function not implemented for platform"
        }
    }
}

#if os(Linux)
#if canImport(Musl)
import Musl
let osSocket = Musl.socket
let osBind = Musl.bind
let osSend = Musl.send
let osRecv = Musl.recv
#elseif canImport(Glibc)
import Glibc
let osSocket = Glibc.socket
let osBind = Glibc.bind
let osSend = Glibc.send
let osRecv = Glibc.recv
#endif

/// A default implementation of `NetlinkSocket`.
public class DefaultNetlinkSocket: NetlinkSocket {
    private let sockfd: Int32

    /// The process identifier of the process creating this socket.
    public let pid: UInt32

    /// Creates a new instance.
    public init() throws {
        pid = UInt32(getpid())
        sockfd = osSocket(Int32(AddressFamily.AF_NETLINK), SocketType.SOCK_RAW, NetlinkProtocol.NETLINK_ROUTE)
        guard sockfd >= 0 else {
            throw NetlinkSocketError.socketFailure(rc: errno)
        }

        let addr = SockaddrNetlink(family: AddressFamily.AF_NETLINK, pid: pid)
        var buffer = [UInt8](repeating: 0, count: SockaddrNetlink.size)
        _ = try addr.appendBuffer(&buffer, offset: 0)
        guard let ptr = buffer.bind(as: sockaddr.self, size: buffer.count) else {
            throw NetlinkSocketError.bindFailure(rc: 0)
        }
        guard osBind(sockfd, ptr, UInt32(buffer.count)) >= 0 else {
            throw NetlinkSocketError.bindFailure(rc: errno)
        }
    }

    deinit {
        close(sockfd)
    }

    /// Sends a request to a netlink socket.
    /// Returns the number of bytes sent.
    /// - Parameters:
    ///   - buf: The buffer to send.
    ///   - len: The length of the buffer to send.
    ///   - flags: The send flags.
    public func send(buf: UnsafeRawPointer!, len: Int, flags: Int32) throws -> Int {
        let count = osSend(sockfd, buf, len, flags)
        guard count >= 0 else {
            throw NetlinkSocketError.sendFailure(rc: errno)
        }

        return count
    }

    /// Receives a response from a netlink socket.
    /// Returns the number of bytes received.
    /// - Parameters:
    ///   - buf: The buffer to receive into.
    ///   - len: The maximum number of bytes to receive.
    ///   - flags: The receive flags.
    public func recv(buf: UnsafeMutableRawPointer!, len: Int, flags: Int32) throws -> Int {
        let count = osRecv(sockfd, buf, len, flags)
        guard count >= 0 else {
            throw NetlinkSocketError.recvFailure(rc: errno)
        }

        return count
    }
}
#else
public class DefaultNetlinkSocket: NetlinkSocket {
    public var pid: UInt32 { 0 }

    public init() throws {}

    public func send(buf: UnsafeRawPointer!, len: Int, flags: Int32) throws -> Int {
        throw NetlinkSocketError.notImplemented
    }

    public func recv(buf: UnsafeMutableRawPointer!, len: Int, flags: Int32) throws -> Int {
        throw NetlinkSocketError.notImplemented
    }
}
#endif
