// PacketTunnelProvider.swift — Pangolin macOS 隧道扩展(方案B / P1)
//
// NEPacketTunnelProvider 子进程,把嵌入的 sing-box(libbox)拉起来建 TUN,免 root、
// 自包含。数据流见 docs/p1-macos-system-extension.md。
//
// gomobile 约定:每个 Go 接口生成同名 protocol + class;Swift 里实现用 `XxxProtocol`,
// 参数/返回用同名 class。NE 生命周期 override 用 completion-handler(规避 Swift 6.2
// async→ObjC 桥接 thunk 的编译器崩溃)。

import Darwin
import Foundation
import Libbox
import Network
import NetworkExtension
import os

private let log = Logger(subsystem: "com.pangolin.pangolin.PacketTunnel", category: "provider")
// macOS 原生 App Group 格式 <TeamID>.<name>(非 iOS 的 group. 前缀)。
// sysextd 校验请求方 app 时要求此格式;用 group. 式会在 realize 暂存前被拒。
private let appGroup = "BYL4KQHMTN.com.pangolin.pangolin"

final class PacketTunnelProvider: NEPacketTunnelProvider {
    private var commandServer: LibboxCommandServer?
    private var platform: PangolinPlatformInterface?

    override func startTunnel(options: [String: NSObject]?,
                              completionHandler: @escaping (Error?) -> Void) {
        log.info("startTunnel")
        do {
            let rawConfig = try resolveConfig(options)

            guard let base = FileManager.default
                .containerURL(forSecurityApplicationGroupIdentifier: appGroup) else {
                throw simpleError("no app group container")
            }
            let work = base.appendingPathComponent("work", isDirectory: true)
            try? FileManager.default.createDirectory(at: work, withIntermediateDirectories: true)

            // 【临时】剥掉远程 rule-set(#5);并把 sing-box 日志直写容器 box.log(debug)以定位启动卡点。
            let configContent = Self.stripRemoteRuleSets(
                rawConfig, logOutput: base.appendingPathComponent("box.log").path)

            let setup = LibboxSetupOptions()
            setup.basePath = base.path
            setup.workingPath = work.path
            setup.tempPath = NSTemporaryDirectory()
            var setupErr: NSError?
            LibboxSetup(setup, &setupErr)
            if let setupErr { throw setupErr }

            let platform = PangolinPlatformInterface(provider: self)
            self.platform = platform

            var newErr: NSError?
            guard let server = LibboxNewCommandServer(self, platform, &newErr) else {
                throw newErr ?? simpleError("LibboxNewCommandServer returned nil")
            }
            try server.start()
            // 必须传非空 options:此版本 libbox 的 StartOrReloadService 会解引用 options,
            // 传 nil 会在 command_server.go:175 触发 SIGSEGV(空指针),扩展进程崩溃。
            try server.startOrReloadService(configContent, options: LibboxOverrideOptions())
            self.commandServer = server

            log.info("startTunnel: service started")
            completionHandler(nil)
        } catch {
            // 【临时诊断】完整错误写进 stderr.log;并设 public 避免 os_log 脱敏成 <private>。
            FileHandle.standardError.write("startTunnel failed: \(error)\n".data(using: .utf8)!)
            log.error("startTunnel failed: \(error.localizedDescription, privacy: .public)")
            completionHandler(error)
        }
    }

    override func stopTunnel(with reason: NEProviderStopReason,
                             completionHandler: @escaping () -> Void) {
        log.info("stopTunnel reason=\(reason.rawValue)")
        try? commandServer?.closeService()
        commandServer?.close()
        commandServer = nil
        platform?.stopMonitor()
        platform = nil
        completionHandler()
    }

    override func handleAppMessage(_ messageData: Data,
                                   completionHandler: ((Data?) -> Void)?) {
        completionHandler?(nil)
    }

    private func resolveConfig(_ options: [String: NSObject]?) throws -> String {
        if let inline = options?["configContent"] as? String, !inline.isEmpty {
            return inline
        }
        guard let base = FileManager.default
            .containerURL(forSecurityApplicationGroupIdentifier: appGroup) else {
            throw simpleError("no app group container")
        }
        let content = (try? String(contentsOf: base.appendingPathComponent("config.json"),
                                   encoding: .utf8)) ?? ""
        guard !content.isEmpty else { throw simpleError("empty sing-box config") }
        return content
    }

    // 【临时】移除配置里的远程 rule-set 定义,以及 route.rules 中引用了 rule_set 的规则。
    // 解析失败则原样返回。正式分流方案应改用本地 .srs(客户端预取)。
    static func stripRemoteRuleSets(_ json: String, logOutput: String) -> String {
        guard let data = json.data(using: .utf8),
              var root = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any],
              var route = root["route"] as? [String: Any] else {
            return json
        }
        if route["rule_set"] != nil { route.removeValue(forKey: "rule_set") }
        if let rules = route["rules"] as? [[String: Any]] {
            route["rules"] = rules.filter { $0["rule_set"] == nil }
        }
        root["route"] = route
        // 【临时诊断】sing-box 日志直写容器 box.log(debug),定位启动卡点。
        root["log"] = ["level": "debug", "timestamp": true, "output": logOutput]
        guard let out = try? JSONSerialization.data(withJSONObject: root),
              let s = String(data: out, encoding: .utf8) else { return json }
        return s
    }
}

// ── LibboxCommandServerHandler:服务端事件回调 ──────────────────────────────
extension PacketTunnelProvider: LibboxCommandServerHandlerProtocol {
    func serviceReload() throws {
        if let cfg = try? resolveConfig(nil) {
            try commandServer?.startOrReloadService(cfg, options: LibboxOverrideOptions())
        }
    }
    func serviceStop() throws { cancelTunnelWithError(nil) }
    func postServiceClose() {}
    func getSystemProxyStatus() throws -> LibboxSystemProxyStatus {
        let s = LibboxSystemProxyStatus(); s.available = false; s.enabled = false; return s
    }
    func setSystemProxyEnabled(_ enabled: Bool) throws {}
    func writeStatus(_ message: LibboxStatusMessage?) {}
    func writeLogs(_ messageList: (any LibboxLogIteratorProtocol)?) {
        // 【临时诊断】把 sing-box 日志写进 stderr.log,定位启动卡点。
        guard let it = messageList else { return }
        while it.hasNext() {
            guard let entry = it.next() else { continue }
            let line = "[box] \(entry.message)\n"
            FileHandle.standardError.write(line.data(using: .utf8) ?? Data())
        }
    }
    func writeConnectionEvents(_ events: LibboxConnectionEvents?) {}
    func writeGroups(_ message: (any LibboxOutboundGroupIteratorProtocol)?) {}
    func writeDebugMessage(_ message: String?) {}
    func setDefaultLogLevel(_ level: Int32) {}
    func initializeClashMode(_ modeList: (any LibboxStringIteratorProtocol)?, currentMode: String?) {}
    func updateClashMode(_ newMode: String?) {}
    func clearLogs() {}
    func connected() {}
    func disconnected(_ message: String?) {}
}

// ── libbox ↔ NetworkExtension 桥(LibboxPlatformInterface)──────────────────
final class PangolinPlatformInterface: NSObject, LibboxPlatformInterfaceProtocol {
    private weak var provider: NEPacketTunnelProvider?
    private var monitor: NWPathMonitor?
    private var defaultInterfaceIndex: Int32 = -1
    private let monitorQueue = DispatchQueue(label: "pangolin.tunnel.pathmonitor")

    init(provider: NEPacketTunnelProvider) {
        self.provider = provider
        super.init()
    }

    func stopMonitor() { monitor?.cancel(); monitor = nil }

    // MARK: openTun —— 据 TunOptions 建 NEPacketTunnelNetworkSettings,返回 utun fd
    func openTun(_ options: (any LibboxTunOptionsProtocol)?, ret0_: UnsafeMutablePointer<Int32>?) throws {
        guard let options, let provider else { throw simpleError("openTun: no options/provider") }
        let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
        settings.mtu = NSNumber(value: options.getMTU())

        let v4addrs = routePrefixes(options.getInet4Address())
        if !v4addrs.isEmpty {
            let ipv4 = NEIPv4Settings(addresses: v4addrs.map(\.address),
                                      subnetMasks: v4addrs.map(\.mask))
            ipv4.includedRoutes = includedRoutes4(options)
            settings.ipv4Settings = ipv4
        }
        let v6addrs = routePrefixes(options.getInet6Address())
        if !v6addrs.isEmpty {
            let ipv6 = NEIPv6Settings(addresses: v6addrs.map(\.address),
                                      networkPrefixLengths: v6addrs.map { NSNumber(value: $0.prefix) })
            ipv6.includedRoutes = includedRoutes6(options)
            settings.ipv6Settings = ipv6
        }
        if let dns = try? options.getDNSServerAddress(), !dns.value.isEmpty {
            settings.dnsSettings = NEDNSSettings(servers: [dns.value])
        }

        let sem = DispatchSemaphore(value: 0)
        var applyError: Error?
        provider.setTunnelNetworkSettings(settings) { err in applyError = err; sem.signal() }
        sem.wait()
        if let applyError { throw applyError }

        guard let fd = Self.tunFd() else { throw simpleError("openTun: utun fd not found") }
        ret0_?.pointee = fd
    }

    // MARK: 默认接口监控
    func startDefaultInterfaceMonitor(_ listener: (any LibboxInterfaceUpdateListenerProtocol)?) throws {
        let m = NWPathMonitor()
        monitor = m
        // 必须阻塞到首个 path 更新再返回:否则 sing-box 紧接着下载远程 rule-set 时
        // defaultInterfaceIndex 仍为 -1,autoDetectControl 跳过绑接口 → "no available
        // network interface"。对齐 sing-box-for-apple 的实现。
        let firstUpdate = DispatchSemaphore(value: 0)
        var signaled = false
        m.pathUpdateHandler = { [weak self] path in
            guard let self else { return }
            let iface = path.availableInterfaces.first { path.usesInterfaceType($0.type) }
                ?? path.availableInterfaces.first
            let name = iface?.name ?? ""
            let index = Int32(iface?.index ?? 0)
            self.defaultInterfaceIndex = index
            listener?.updateDefaultInterface(name, interfaceIndex: index,
                                             isExpensive: path.isExpensive,
                                             isConstrained: path.isConstrained)
            if !signaled { signaled = true; firstUpdate.signal() }
        }
        m.start(queue: monitorQueue)
        _ = firstUpdate.wait(timeout: .now() + 5)
    }
    func closeDefaultInterfaceMonitor(_ listener: (any LibboxInterfaceUpdateListenerProtocol)?) throws {
        stopMonitor()
    }

    // MARK: 把 socket 绑到默认物理接口
    func autoDetectControl(_ fd: Int32) throws {
        guard defaultInterfaceIndex > 0 else { return }
        var idx = defaultInterfaceIndex
        _ = setsockopt(fd, IPPROTO_IP, IP_BOUND_IF, &idx, socklen_t(MemoryLayout<Int32>.size))
        _ = setsockopt(fd, IPPROTO_IPV6, IPV6_BOUND_IF, &idx, socklen_t(MemoryLayout<Int32>.size))
    }
    func usePlatformAutoDetectControl() -> Bool { true }

    // MARK: 系统接口枚举
    func getInterfaces() throws -> LibboxNetworkInterfaceIteratorProtocol {
        InterfaceIterator(items: Self.enumerateInterfaces())
    }

    // MARK: 其余协议方法(取合理默认)
    func underNetworkExtension() -> Bool { true }
    func useProcFS() -> Bool { false }
    func includeAllNetworks() -> Bool { false }
    func clearDNSCache() {}
    func readWIFIState() -> LibboxWIFIState? { nil }
    func systemCertificates() -> (any LibboxStringIteratorProtocol)? { nil }
    func localDNSTransport() -> (any LibboxLocalDNSTransportProtocol)? { nil }
    func send(_ notification: LibboxNotification?) throws {}
    func findConnectionOwner(_ ipProtocol: Int32, sourceAddress: String?, sourcePort: Int32,
                             destinationAddress: String?, destinationPort: Int32) throws -> LibboxConnectionOwner {
        throw simpleError("findConnectionOwner: unsupported")
    }

    // ── 工具 ──
    private func routePrefixes(_ it: (any LibboxRoutePrefixIteratorProtocol)?) -> [(address: String, mask: String, prefix: Int32)] {
        var out: [(address: String, mask: String, prefix: Int32)] = []
        guard let it else { return out }
        while it.hasNext() {
            if let p = it.next() { out.append((address: p.address(), mask: p.mask(), prefix: p.prefix())) }
        }
        return out
    }
    private func includedRoutes4(_ o: any LibboxTunOptionsProtocol) -> [NEIPv4Route] {
        let r = routePrefixes(o.getInet4RouteAddress())
        if r.isEmpty { return [NEIPv4Route.default()] }
        return r.map { NEIPv4Route(destinationAddress: $0.address, subnetMask: $0.mask) }
    }
    private func includedRoutes6(_ o: any LibboxTunOptionsProtocol) -> [NEIPv6Route] {
        let r = routePrefixes(o.getInet6RouteAddress())
        if r.isEmpty { return [NEIPv6Route.default()] }
        return r.map { NEIPv6Route(destinationAddress: $0.address,
                                   networkPrefixLength: NSNumber(value: $0.prefix)) }
    }

    // utun fd:getpeername 得 sockaddr_ctl,匹配 utun 控制 id(WireGuard 技法)
    private static func tunFd() -> Int32? {
        var ctlInfo = ctl_info()
        withUnsafeMutableBytes(of: &ctlInfo.ctl_name) { ptr in
            _ = "com.apple.net.utun_control".withCString {
                strncpy(ptr.baseAddress!.assumingMemoryBound(to: CChar.self), $0, ptr.count - 1)
            }
        }
        // CTLIOCGINFO = _IOWR('N', 3, struct ctl_info)(Swift 不导出该宏,按定义算)
        let ctliocginfo: UInt = 0x8000_0000 | 0x4000_0000
            | ((UInt(MemoryLayout<ctl_info>.stride) & 0x1fff) << 16)
            | (UInt(UInt8(ascii: "N")) << 8) | 3
        for fd in Int32(0)...1024 {
            var addr = sockaddr_ctl()
            var len = socklen_t(MemoryLayout<sockaddr_ctl>.size)
            let ok = withUnsafeMutablePointer(to: &addr) { p in
                p.withMemoryRebound(to: sockaddr.self, capacity: 1) { getpeername(fd, $0, &len) }
            }
            if ok == 0, addr.sc_family == AF_SYSTEM {
                if ioctl(fd, ctliocginfo, &ctlInfo) == 0, addr.sc_id == ctlInfo.ctl_id {
                    return fd
                }
            }
        }
        return nil
    }

    private static func enumerateInterfaces() -> [LibboxNetworkInterface] {
        var result: [String: LibboxNetworkInterface] = [:]
        var head: UnsafeMutablePointer<ifaddrs>?
        guard getifaddrs(&head) == 0 else { return [] }
        defer { freeifaddrs(head) }
        var cur = head
        while let p = cur {
            let name = String(cString: p.pointee.ifa_name)
            if result[name] == nil {
                let i = LibboxNetworkInterface()
                i.name = name
                i.index = Int32(if_nametoindex(p.pointee.ifa_name))
                result[name] = i
            }
            cur = p.pointee.ifa_next
        }
        return Array(result.values)
    }
}

// LibboxNetworkInterfaceIterator 适配器。
final class InterfaceIterator: NSObject, LibboxNetworkInterfaceIteratorProtocol {
    private var idx = 0
    private let items: [LibboxNetworkInterface]
    init(items: [LibboxNetworkInterface]) { self.items = items }
    func hasNext() -> Bool { idx < items.count }
    func next() -> LibboxNetworkInterface? {
        guard idx < items.count else { return nil }
        defer { idx += 1 }
        return items[idx]
    }
}

private func simpleError(_ msg: String) -> NSError {
    NSError(domain: "pangolin.tunnel", code: 1, userInfo: [NSLocalizedDescriptionKey: msg])
}
