//
//  NetworkModel.swift
//  punchnet
//
//  Created by 安礼成 on 2026/4/17.
//
import Foundation
import Observation

@MainActor
@Observable
final class NetworkModel {
    @ObservationIgnored
    private weak var appContext: AppContext?
    
    @ObservationIgnored
    private let vpnManager = VPNManager.shared
    
    @ObservationIgnored
    private var vpnStatusTask: Task<Void, Never>?
    
    var showMode: NetworkShowMode = .resource
    var phase: NetworkConnectionPhase = .disconnected
    
    var networkSession: NetworkSession?
    var networkContext: NetworkContext?
    var selectedExitNodeIp: String?
    var selectedNodeId: Int?
    
    private(set) var nodeResourcesById: [Int: [NetworkContext.Resource]] = [:]
    private(set) var loadingNodeIDs: Set<Int> = []
    private(set) var isUpdatingExitNode: Bool = false
    private(set) var errorMessage: String?
    
    deinit {
        self.vpnStatusTask?.cancel()
    }
    
    var isBusy: Bool {
        switch self.phase {
        case .connecting, .disconnecting:
            return true
        case .connected, .disconnected:
            return self.isUpdatingExitNode
        }
    }
    
    var isTunnelEnabled: Bool {
        switch self.phase {
        case .connecting, .connected:
            return true
        case .disconnecting, .disconnected:
            return false
        }
    }
    
    var shouldShowModePicker: Bool {
        self.phase == .connected
    }
    
    var resourceList: [NetworkContext.Resource] {
        self.networkContext?.resourceList ?? []
    }
    
    var nodeList: [NetworkContext.Node] {
        self.networkContext?.nodeList ?? []
    }
    
    var selectedNode: NetworkContext.Node? {
        self.networkContext?.getNode(id: self.selectedNodeId)
    }
    
    var canSelectExitNode: Bool {
        guard self.networkSession != nil else {
            return false
        }
        
        guard !self.isUpdatingExitNode else {
            return false
        }
        
        switch self.phase {
        case .connected:
            return self.networkContext != nil || self.selectedExitNodeIp != nil
        case .disconnected:
            return self.selectedExitNodeIp != nil
        case .connecting, .disconnecting:
            return false
        }
    }
    
    var exitNodeOptions: [ExitNodeOption] {
        guard let networkContext = self.networkContext else {
            return []
        }
        
        return networkContext.exitNodeList.compactMap { exitNode in
            guard let node = networkContext.getNode(id: exitNode.nnid) else {
                return nil
            }
            
            return ExitNodeOption(
                id: exitNode.nnid,
                nodeName: exitNode.nodeName,
                ip: node.ip
            )
        }
    }
    
    var selectedExitNode: ExitNodeOption? {
        guard let selectedExitNodeIp = self.selectedExitNodeIp else {
            return nil
        }
        
        return self.exitNodeOptions.first(where: { $0.ip == selectedExitNodeIp })
            ?? ExitNodeOption(
                id: -1,
                nodeName: "已保存出口节点",
                ip: selectedExitNodeIp
            )
    }
    
    var exitNodeHelpText: String {
        if self.isUpdatingExitNode {
            return "正在更新出口节点"
        }
        
        if self.networkContext == nil {
            return "建立连接后可选择当前网络的出口节点"
        }
        
        return "切换当前网络流量的出口节点，也可以保持未设置"
    }
    
    func activate(appContext: AppContext) async {
        if self.appContext !== appContext {
            self.appContext = appContext
        }
        
        self.startObservingVPNStatusIfNeeded()
        await self.handleVPNStatusChange(self.vpnManager.vpnStatus)
    }
    
    func clearError() {
        self.errorMessage = nil
    }
    
    func setConnectionEnabled(_ enabled: Bool) async {
        if enabled {
            await self.connect()
        } else {
            await self.disconnect()
        }
    }
    
    func connect() async {
        guard let appContext = self.appContext else {
            return
        }
        
        guard !self.isBusy else {
            return
        }
        
        self.errorMessage = nil
        self.phase = .connecting
        
        do {
            if appContext.networkContext == nil {
                try await appContext.connectNetwork()
            }
            
            self.syncSharedStateFromAppContext()
            await self.applyNetworkContext(appContext.networkContext)
            try await appContext.startTun()
            
            if self.vpnManager.vpnStatus == .connected {
                self.phase = .connected
            }
        } catch let err as SDLAPIError {
            self.errorMessage = err.message
            self.handleDisconnectedState(syncAppContext: true)
        } catch let err as AppContextError {
            self.errorMessage = err.message
            self.handleDisconnectedState(syncAppContext: true)
        } catch {
            self.errorMessage = error.localizedDescription
            self.handleDisconnectedState(syncAppContext: true)
        }
    }
    
    func disconnect() async {
        guard let appContext = self.appContext else {
            return
        }
        
        guard !self.isBusy else {
            return
        }
        
        self.errorMessage = nil
        self.phase = .disconnecting
        
        do {
            try await appContext.stopTun()
            self.handleDisconnectedState(syncAppContext: false)
        } catch let err as AppContextError {
            self.errorMessage = err.message
            await self.handleVPNStatusChange(self.vpnManager.vpnStatus)
        } catch {
            self.errorMessage = error.localizedDescription
            await self.handleVPNStatusChange(self.vpnManager.vpnStatus)
        }
    }
    
    func selectNode(id: Int?) {
        self.selectedNodeId = id
        
        guard let id else {
            return
        }
        
        Task { @MainActor in
            await self.loadResourcesIfNeeded(for: id)
        }
    }
    
    func resources(for nodeId: Int) -> [NetworkContext.Resource] {
        self.nodeResourcesById[nodeId] ?? []
    }
    
    func isLoadingResources(for nodeId: Int) -> Bool {
        self.loadingNodeIDs.contains(nodeId)
    }
    
    func loadResourcesIfNeeded(for nodeId: Int) async {
        guard let session = self.networkSession else {
            return
        }
        
        guard self.nodeResourcesById[nodeId] == nil else {
            return
        }
        
        guard !self.loadingNodeIDs.contains(nodeId) else {
            return
        }
        
        let currentContextIdentity = self.contextIdentity(self.networkContext)
        
        self.loadingNodeIDs.insert(nodeId)
        defer {
            self.loadingNodeIDs.remove(nodeId)
        }
        
        let resources = await NetworkService.loadNodeResources(accesToken: session.accessToken, id: nodeId)
        
        guard currentContextIdentity == self.contextIdentity(self.networkContext) else {
            return
        }
        
        self.nodeResourcesById[nodeId] = resources
    }
    
    func updateExitNodeSelection(_ ip: String?) async {
        guard let appContext = self.appContext else {
            return
        }
        
        guard !self.isUpdatingExitNode else {
            return
        }
        
        self.errorMessage = nil
        self.isUpdatingExitNode = true
        defer {
            self.isUpdatingExitNode = false
        }
        
        do {
            try await appContext.updateExitNodeIp(exitNodeIp: ip)
            self.syncSharedStateFromAppContext()
        } catch let err as AppContextError {
            self.errorMessage = err.message
        } catch {
            self.errorMessage = error.localizedDescription
        }
    }
    
    private func startObservingVPNStatusIfNeeded() {
        guard self.vpnStatusTask == nil else {
            return
        }
        
        let vpnStatusStream = self.vpnManager.vpnStatusStream
        
        self.vpnStatusTask = Task { [weak self, vpnStatusStream] in
            guard let self else {
                return
            }
            
            for await status in vpnStatusStream {
                if Task.isCancelled {
                    return
                }
                
                await self.handleVPNStatusChange(status)
            }
        }
    }
    
    private func syncSharedStateFromAppContext() {
        self.networkSession = self.appContext?.networkSession
        self.selectedExitNodeIp = self.appContext?.selectedExitNodeIp
    }
    
    private func handleVPNStatusChange(_ status: VPNManager.VPNStatus) async {
        self.syncSharedStateFromAppContext()
        
        switch status {
        case .connecting:
            self.phase = .connecting
            await self.applyNetworkContext(self.appContext?.networkContext)
        case .connected:
            self.phase = .connected
            await self.applyNetworkContext(self.appContext?.networkContext)
        case .disconnecting:
            self.phase = .disconnecting
        case .disconnected:
            self.handleDisconnectedState(syncAppContext: true)
        }
    }
    
    private func handleDisconnectedState(syncAppContext: Bool) {
        if syncAppContext {
            self.appContext?.networkContext = nil
        }
        
        self.phase = .disconnected
        self.networkContext = nil
        self.selectedNodeId = nil
        self.nodeResourcesById.removeAll()
        self.loadingNodeIDs.removeAll()
        self.showMode = .resource
        self.syncSharedStateFromAppContext()
    }
    
    private func applyNetworkContext(_ newContext: NetworkContext?) async {
        let contextChanged = self.contextIdentity(self.networkContext) != self.contextIdentity(newContext)
        
        self.networkContext = newContext
        
        if contextChanged {
            self.nodeResourcesById.removeAll()
            self.loadingNodeIDs.removeAll()
            self.selectedNodeId = nil
        }
        
        guard let newContext else {
            self.selectedNodeId = nil
            return
        }
        
        if let selectedNodeId = self.selectedNodeId,
           newContext.getNode(id: selectedNodeId) != nil {
            await self.loadResourcesIfNeeded(for: selectedNodeId)
            return
        }
        
        self.selectedNodeId = newContext.firstNodeId()
        
        if let selectedNodeId = self.selectedNodeId {
            await self.loadResourcesIfNeeded(for: selectedNodeId)
        }
    }
    
    private func contextIdentity(_ context: NetworkContext?) -> String? {
        guard let context else {
            return nil
        }
        
        return "\(context.identityId)-\(context.ip)"
    }
}

extension NetworkModel {
    struct ExitNodeOption: Identifiable, Equatable {
        let id: Int
        let nodeName: String
        let ip: String
    }

}
