//
//  VPNManager.swift
//  sdlan
//
//  Created by 安礼成 on 2024/1/17.
//

import Foundation
import NetworkExtension
import SwiftUI
import Observation

enum VPNManagerError: Error {
    case disconnected
}

// vpn管理类
@Observable
class VPNManager {
    static let shared = VPNManager()
    
    private var manager: NETunnelProviderManager?
    private var statusObserver: NSObjectProtocol?
    
    var vpnStatus: VPNStatus = .disconnected
    var isConnected: Bool = false
    
    var vpnStatusStream: AsyncStream<VPNStatus>
    private var vpnStatusCont: AsyncStream<VPNStatus>.Continuation
    
    enum VPNStatus {
        case connecting
        case connected
        case disconnecting
        case disconnected
    }
    
    private init() {
        (self.vpnStatusStream, self.vpnStatusCont) = AsyncStream.makeStream(of: VPNStatus.self)
    }
    
    // 开启vpn
    func enableVpn(options: [String : NSObject]) async throws {
        NSLog("enable vpn with options: \(options)")
        let manager = try await loadAndCreateProviderManager()
        try await manager.loadFromPreferences()
        self.addVPNStatusObserver(manager)
        try manager.connection.startVPNTunnel(options: options)
        
        self.manager = manager
    }
    
    // 关闭vpn
    func disableVpn() async throws {
        guard let manager = self.manager else {
            return
        }
        
        try await manager.loadFromPreferences()
        manager.connection.stopVPNTunnel()
        self.manager = nil
    }
    
    func sendMessage(_ message: Data) async throws -> Data {
        guard let session = self.manager?.connection as? NETunnelProviderSession else {
            throw VPNManagerError.disconnected
        }
        
        guard session.status == .connected || session.status == .connecting else {
            throw VPNManagerError.disconnected
        }
        
        return try await withCheckedThrowingContinuation { continuation in
            do {
                try session.sendProviderMessage(message) { responseData in
                    // 收到响应，恢复异步挂起点
                    continuation.resume(returning: responseData ?? Data())
                }
            } catch {
                // 发送失败，抛出错误
                continuation.resume(throwing: error)
            }
        }
    }
    
    // MARK: - Private Methods
    
    // 监控系统VPN的状态的变化
    private func addVPNStatusObserver(_ manager: NETunnelProviderManager) {
        if let statusObserver {
            NotificationCenter.default.removeObserver(statusObserver)
            self.statusObserver = nil
        }
        
        self.statusObserver = NotificationCenter.default.addObserver(forName: .NEVPNStatusDidChange, object: manager.connection, queue: .main) {[weak self] _ in
            NSLog("status channge: \(manager.connection.status)")
            switch manager.connection.status {
            case .invalid, .disconnected:
                self?.vpnStatusCont.yield(.disconnected)
                self?.vpnStatus = .disconnected
                self?.isConnected = false
            case .disconnecting:
                self?.vpnStatusCont.yield(.disconnecting)
                self?.vpnStatus = .disconnecting
                self?.isConnected = false
            case .connecting, .reasserting:
                self?.vpnStatusCont.yield(.connecting)
                self?.vpnStatus = .connecting
                self?.isConnected = true
            case .connected:
                self?.vpnStatusCont.yield(.connected)
                self?.vpnStatus = .connected
                self?.isConnected = true
            @unknown default:
                self?.vpnStatusCont.yield(.disconnected)
                self?.vpnStatus = .disconnected
                self?.isConnected = false
            }
        }
    }
    
    // MARK: - Private Methods
    
    // 加载或者创建相关的配置
    private func loadAndCreateProviderManager() async throws -> NETunnelProviderManager {
        let managers = try await NETunnelProviderManager.loadAllFromPreferences()
        
        let manager = managers.first ?? NETunnelProviderManager()
        manager.localizedDescription = "punchnetmac"
        manager.isEnabled = true
        
        // 设置相关参数，在PacketTunnel中可以用
        let protocolConfiguration = NETunnelProviderProtocol()
        protocolConfiguration.serverAddress = "punchnetmac"
        protocolConfiguration.providerConfiguration = [String:AnyObject]()
        protocolConfiguration.providerBundleIdentifier = "com.jihe.punchnetmac.tun"
        manager.protocolConfiguration = protocolConfiguration
        manager.isOnDemandEnabled = false
        
        manager.isEnabled = true
        
        try await manager.saveToPreferences()
        
        return manager
    }
    
    deinit {
        if let statusObserver {
            NotificationCenter.default.removeObserver(statusObserver)
        }
    }
    
}
