//
//  SDLPuncherActor.swift
//  Tun
//
//  Created by 安礼成 on 2026/1/7.
//

import Foundation
import NIOCore

actor SDLPuncherActor {
    // 10秒内只需要提交一次查询
    nonisolated private let cooldownInterval: TimeInterval = 10
    // 等待peerInfo返回的超时时间
    nonisolated private let peerInfoTimeout: TimeInterval = 3

    struct RegisterRequest {
        let srcMac: Data
        let dstMac: Data
        let networkId: UInt32
    }
    
    private enum RequestPhase {
        case waitingPeerInfo(deadline: Date)
        case coolingDown
    }
    
    private struct RequestEntry {
        let request: RegisterRequest
        let cooldownUntil: Date
        var phase: RequestPhase
        
        func canSubmit(at now: Date) -> Bool {
            return cooldownUntil <= now
        }
        
        func isWaitingPeerInfo(at now: Date) -> Bool {
            guard case .waitingPeerInfo(let deadline) = self.phase else {
                return false
            }
            
            return deadline > now
        }
        
        mutating func markCoolingDown() {
            self.phase = .coolingDown
        }
    }
    
    // dstMac
    private var requestEntries: [Data: RequestEntry] = [:]
    private var cleanupTask: Task<Void, Never>?
    
    func start() {
        guard self.cleanupTask == nil else {
            return
        }
        
        self.cleanupTask = Task { [weak self] in
            while !Task.isCancelled {
                try? await Task.sleep(for: .seconds(1))
                await self?.cleanupExpiredEntries()
            }
        }
    }
    
    func submitRegisterRequest(quicClient: SDLQUICClient?, request: RegisterRequest) async {
        guard let quicClient else {
            return
        }
        
        let now = Date()
        self.cleanupExpiredEntries(now: now)
        
        if let entry = self.requestEntries[request.dstMac], !entry.canSubmit(at: now) {
            return
        }
        
        var queryInfo = SDLQueryInfo()
        queryInfo.dstMac = request.dstMac
        
        guard let queryData = try? queryInfo.serializedData() else {
            SDLLogger.log("[SDLPuncherActor] failed to encode queryInfo", for: .debug)
            return
        }
        
        self.requestEntries[request.dstMac] = RequestEntry(
            request: request,
            cooldownUntil: now.addingTimeInterval(self.cooldownInterval),
            phase: .waitingPeerInfo(deadline: now.addingTimeInterval(self.peerInfoTimeout))
        )
        
        await quicClient.send(type: .queryInfo, data: queryData)
    }
    
    func handlePeerInfo(using udpHole: SDLUDPHole?, udpHoleV6: SDLUDPHoleV6?, peerInfo: SDLPeerInfo) async {
        let now = Date()
        self.cleanupExpiredEntries(now: now)
        
        guard var entry = self.requestEntries[peerInfo.dstMac] else {
            return
        }
        
        guard entry.isWaitingPeerInfo(at: now) else {
            return
        }
        
        entry.markCoolingDown()
        self.requestEntries[peerInfo.dstMac] = entry
        
        guard udpHole != nil || udpHoleV6 != nil else {
            SDLLogger.log("[SDLPuncherActor] udpHole and udpHoleV6 are nil when peerInfo arrived", for: .debug)
            return
        }
        
        var register = SDLRegister()
        register.networkID = entry.request.networkId
        register.srcMac = entry.request.srcMac
        register.dstMac = entry.request.dstMac
        
        guard let registerData = try? register.serializedData() else {
            SDLLogger.log("[SDLPuncherActor] failed to encode register", for: .debug)
            return
        }
        
        // 并行发送register请求
        if peerInfo.hasV4Info {
            if let remoteAddress = try? await peerInfo.v4Info.socketAddress() {
                SDLLogger.log("[SDLContext] hole sock address: \(remoteAddress)", for: .debug)
                await self.sendRegister(using: udpHole, udpHoleV6: udpHoleV6, registerData: registerData, remoteAddress: remoteAddress)
            } else {
                SDLLogger.log("[SDLPuncherActor] failed to resolve peerInfo.v4Info", for: .debug)
            }
        }
        
        if peerInfo.hasV6Info {
            if let remoteAddress = try? await peerInfo.v6Info.socketAddress() {
                SDLLogger.log("[SDLContext] hole sock address v6: \(remoteAddress)", for: .debug)
                await self.sendRegister(using: udpHole, udpHoleV6: udpHoleV6, registerData: registerData, remoteAddress: remoteAddress)
            } else {
                SDLLogger.log("[SDLPuncherActor] failed to resolve peerInfo.v6Info", for: .debug)
            }
        }
        
    }
    
    func stop() {
        self.cleanupTask?.cancel()
        self.cleanupTask = nil
        self.requestEntries.removeAll()
    }
    
    private func cleanupExpiredEntries(now: Date = Date()) {
        self.requestEntries = self.requestEntries.filter { _, entry in
            !entry.canSubmit(at: now)
        }
    }
    
    private func sendRegister(using udpHole: SDLUDPHole?, udpHoleV6: SDLUDPHoleV6?, registerData: Data, remoteAddress: SocketAddress) async {
        switch remoteAddress {
        case .v4:
            guard let udpHole else {
                SDLLogger.log("[SDLPuncherActor] udpHole is nil when v4 peerInfo arrived", for: .debug)
                return
            }
            await udpHole.send(type: .register, data: registerData, remoteAddress: remoteAddress)
        case .v6:
            guard let udpHoleV6 else {
                SDLLogger.log("[SDLPuncherActor] udpHoleV6 is nil when v6 peerInfo arrived", for: .debug)
                return
            }
            udpHoleV6.send(type: .register, data: registerData, remoteAddress: remoteAddress)
        default:
            SDLLogger.log("[SDLPuncherActor] unsupported peer address family: \(remoteAddress)", for: .debug)
        }
    }

    deinit {
        self.cleanupTask?.cancel()
    }
}
