//
//  LoginState.swift
//  punchnet
//
//  Created by 安礼成 on 2026/1/16.
//

import Foundation
import Observation

struct AppContextError: Error {
    let message: String
}

struct TokenHistoryItem: Codable, Hashable {
    let token: String
    let networkName: String?
}

@Observable
class AppContext {
    private let cachedTokenAccount = "token"
    private let cachedTokenHistoryAccount = "tokenHistory"
    private let maxCachedTokenCount = 10

    private var vpnManager = VPNManager.shared

    // 调用 "/connect" 之后的网络信息
    var networkContext: NetworkContext? = nil
    
    // 当前选择的出口节点 IP，为 nil 表示不设置出口节点
    var selectedExitNodeIp: String? = nil

    // 在menu里面需要使用
    var vpnOptions: [String: NSObject]? = nil

    // 当前app所处的场景
    var appScene: AppScene = .login(username: nil)

    // 当前的场景
    enum AppScene: Equatable {
        case login(username: String?)
        case logined
        case register
        case resetPassword
    }

    // 登陆凭证
    var loginCredit: Credit?

    // 判断用户是否登陆
    var isLogined: Bool {
        return loginCredit != nil
    }

    enum Credit {
        case token(token: String, session: NetworkSession)
        case accountAndPasword(account: String, password: String, session: NetworkSession)
    }

    @ObservationIgnored
    var networkSession: NetworkSession? {
        guard let loginCredit = self.loginCredit else {
            return nil
        }

        switch loginCredit {
        case .token(_, let session):
            return session
        case .accountAndPasword(_, _, let session):
            return session
        }
    }

    var tunnelEvent: SDLTunnelAppEventStore.Event? = nil

    @ObservationIgnored
    private var lastTunnelEventID: String?

    init() {
        self.observeTunnelEvent()
        self.consumeLatestTunnelEvent()
    }

    func loginWith(token: String) async throws {
        let networkSession = try await AuthService.loginWithToken(token: token)
        self.loginCredit = .token(token: token, session: networkSession)
        self.selectedExitNodeIp = self.loadExitNodeIp(networkId: networkSession.networkId)
        try self.saveCacheToken(token, networkName: networkSession.networkName)
    }

    func loginWith(username: String, password: String) async throws {
        let networkSession = try await AuthService.loginWithAccountAndPassword(username: username, password: password)
        self.loginCredit = .accountAndPasword(account: username, password: password, session: networkSession)
        self.selectedExitNodeIp = self.loadExitNodeIp(networkId: networkSession.networkId)
        // 将数据缓存到keychain
        if let data = "\(username):\(password)".data(using: .utf8) {
            try KeychainStore.shared.save(data, account: "accountAndPasword")
        }
    }

    // 连接到对应的网络
    func connectNetwork() async throws {
        guard let session = self.networkSession else {
            throw AppContextError(message: "未登陆")
        }

        // 避免重复连接
        guard !vpnManager.isConnected else {
            throw AppContextError(message: "网络已经连接")
        }

        self.networkContext = try await NetworkService.connectNetwork(accesToken: session.accessToken)
    }

    func changeExitNodeIp(exitNodeIp: String) async throws -> Data {
        // 避免重复连接
        guard vpnManager.isConnected else {
            throw AppContextError(message: "网络未连接")
        }

        var changeExitNode = AppRequest.ChangeExitNodeRequest()
        changeExitNode.ip = exitNodeIp

        var appRequest = AppRequest()
        appRequest.command = .changeExitNode(changeExitNode)

        let message = try appRequest.serializedData()

        return try await self.vpnManager.sendMessage(message)
    }

    // 启动tun
    func startTun() async throws {
        guard let session = self.networkSession, let context = self.networkContext else {
            return
        }

        let options = SystemConfig.getOptions(
            networkId: UInt32(session.networkId),
            networkDomain: session.networkDomain,
            ip: context.ip,
            maskLen: context.maskLen,
            accessToken: session.accessToken,
            identityId: context.identityId,
            hostname: context.hostname,
            exitNodeIp: self.selectedExitNodeIp
        )
        try await self.vpnManager.enableVpn(options: options)
    }

    // 断开网络连接
    func stopTun() async throws {
        try await self.vpnManager.disableVpn()
        self.networkContext = nil
    }

    // 退出登陆
    func logout() async throws {
        try await self.vpnManager.disableVpn()
        self.networkContext = nil
        self.selectedExitNodeIp = nil
        self.loginCredit = nil
    }

    func loadCacheToken() -> String? {
        if let item = self.loadCacheTokenHistory().first {
            return item.token
        }

        return nil
    }

    func loadCacheTokenHistory() -> [TokenHistoryItem] {
        if let data = try? KeychainStore.shared.load(account: cachedTokenHistoryAccount),
           let items = try? JSONDecoder().decode([TokenHistoryItem].self, from: data) {
            let normalizedItems = self.normalizedTokenHistory(items)
            if !normalizedItems.isEmpty {
                return normalizedItems
            }
        }

        if let data = try? KeychainStore.shared.load(account: cachedTokenHistoryAccount),
           let tokens = try? JSONDecoder().decode([String].self, from: data) {
            let normalizedItems = self.normalizedTokenHistory(tokens.map {
                TokenHistoryItem(token: $0, networkName: nil)
            })

            if !normalizedItems.isEmpty {
                return normalizedItems
            }
        }

        if let data = try? KeychainStore.shared.load(account: cachedTokenAccount),
           let token = String(data: data, encoding: .utf8) {
            return self.normalizedTokenHistory([
                TokenHistoryItem(token: token, networkName: nil)
            ])
        }

        return []
    }

    func deleteCacheToken(_ token: String) throws {
        let normalizedToken = token.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !normalizedToken.isEmpty else {
            return
        }

        var items = self.loadCacheTokenHistory()
        items.removeAll { $0.token == normalizedToken }
        items = Array(items.prefix(maxCachedTokenCount))

        let historyData = try JSONEncoder().encode(items)
        try KeychainStore.shared.save(historyData, account: cachedTokenHistoryAccount)

        if let firstToken = items.first?.token,
           let data = firstToken.data(using: .utf8) {
            try KeychainStore.shared.save(data, account: cachedTokenAccount)
        } else {
            try KeychainStore.shared.delete(account: cachedTokenAccount)
        }
    }

    private func saveCacheToken(_ token: String, networkName: String) throws {
        let normalizedToken = token.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !normalizedToken.isEmpty else {
            return
        }

        let normalizedNetworkName = networkName.trimmingCharacters(in: .whitespacesAndNewlines)
        var items = self.loadCacheTokenHistory()
        items.removeAll { $0.token == normalizedToken }
        items.insert(
            TokenHistoryItem(
                token: normalizedToken,
                networkName: normalizedNetworkName.isEmpty ? nil : normalizedNetworkName
            ),
            at: 0
        )
        items = Array(items.prefix(maxCachedTokenCount))

        let historyData = try JSONEncoder().encode(items)
        try KeychainStore.shared.save(historyData, account: cachedTokenHistoryAccount)

        if let data = normalizedToken.data(using: .utf8) {
            try KeychainStore.shared.save(data, account: cachedTokenAccount)
        }
    }

    private func normalizedTokenHistory(_ items: [TokenHistoryItem]) -> [TokenHistoryItem] {
        var seen = Set<String>()

        return items.compactMap { item in
            let normalizedToken = item.token.trimmingCharacters(in: .whitespacesAndNewlines)
            guard !normalizedToken.isEmpty, !seen.contains(normalizedToken) else {
                return nil
            }

            seen.insert(normalizedToken)
            let normalizedNetworkName = item.networkName?.trimmingCharacters(in: .whitespacesAndNewlines)
            return TokenHistoryItem(
                token: normalizedToken,
                networkName: normalizedNetworkName?.isEmpty == true ? nil : normalizedNetworkName
            )
        }
    }

    func loadCacheUsernameAndPassword() -> (String, String)? {
        if let data = try? KeychainStore.shared.load(account: "accountAndPasword"),
            let str = String(data: data, encoding: .utf8) {
            let parts = str.split(separator: ":")
            if parts.count == 2 {
                return (String(parts[0]), String(parts[1]))
            }
        }
        return nil
    }
    
    // MARK: TunEvent
    
    func dismissTunnelEvent() {
        self.tunnelEvent = nil
    }

    private func observeTunnelEvent() {
        SDLNotificationCenter.shared.addObserver(for: .tunnelEventChanged) { [weak self] _ in
            self?.consumeLatestTunnelEvent()
        }
    }

    private func consumeLatestTunnelEvent() {
        guard let event = SDLTunnelAppEventStore.loadLatestEvent(),
              self.lastTunnelEventID != event.id else {
            return
        }

        self.lastTunnelEventID = event.id
        self.tunnelEvent = event
        SDLTunnelAppEventStore.clearLatestEvent()
    }

    deinit {
        SDLNotificationCenter.shared.removeObserver(for: .tunnelEventChanged)
    }
    
}

// 处理网络出口数据
extension AppContext {
    
    private func exitNodeStorageAccount(networkId: Int?) -> String {
        if let networkId {
            return "exitNodeIp_\(networkId)"
        }
        
        return "exitNodeIp"
    }

    func loadExitNodeIp(networkId: Int? = nil) -> String? {
        let account = self.exitNodeStorageAccount(networkId: networkId)
        
        if let data = try? KeychainStore.shared.load(account: account) {
            return String(data: data, encoding: .utf8)
        }
        
        if networkId != nil,
           let data = try? KeychainStore.shared.load(account: self.exitNodeStorageAccount(networkId: nil)) {
            return String(data: data, encoding: .utf8)
        }
        
        return nil
    }

    func saveExitNodeIp(exitNodeIp: String, networkId: Int? = nil) async throws {
        let account = self.exitNodeStorageAccount(networkId: networkId)
        
        // 将数据缓存到keychain
        if let data = exitNodeIp.data(using: .utf8) {
            try KeychainStore.shared.save(data, account: account)
        }
    }
    
    func clearExitNodeIp(networkId: Int? = nil) throws {
        try KeychainStore.shared.delete(account: self.exitNodeStorageAccount(networkId: networkId))
    }
    
    func updateExitNodeIp(exitNodeIp: String?) async throws {
        let normalizedExitNodeIp = exitNodeIp?.trimmingCharacters(in: .whitespacesAndNewlines)
        let finalExitNodeIp = normalizedExitNodeIp?.isEmpty == true ? nil : normalizedExitNodeIp
        let networkId = self.networkSession?.networkId
        
        if vpnManager.isConnected {
            let result = try await self.changeExitNodeIp(exitNodeIp: finalExitNodeIp ?? "0.0.0.0")
            let reply = try TunnelResponse(serializedBytes: result)
            
            guard reply.code == 0 else {
                throw AppContextError(message: reply.message)
            }
        }
        
        if let finalExitNodeIp {
            try await self.saveExitNodeIp(exitNodeIp: finalExitNodeIp, networkId: networkId)
        } else {
            try self.clearExitNodeIp(networkId: networkId)
        }
        
        if networkId != nil {
            try? self.clearExitNodeIp(networkId: nil)
        }
        
        self.selectedExitNodeIp = finalExitNodeIp
    }

    

}
