import Foundation
import MessagePack
import SwiftData
import SwiftUI
import os

extension UserDefaults {
    func bool(forKey key: String, default defaultValue: Bool) -> Bool {
        object(forKey: key) == nil ? defaultValue : bool(forKey: key)
    }
}

private let log = Logger(subsystem: "dev.smoll.KDChat", category: "Connection")

// MARK: - App Screen

enum AppScreen: Sendable, Equatable {
    case login
    case connecting
    case forms
    case chat
}

// MARK: - Server Config

struct ServerConfig: Sendable {
    var host: String
    var port: UInt16

    static let `default` = ServerConfig(host: "15.204.120.54", port: 4200)
}

// MARK: - Connection Manager

@Observable
@MainActor
final class ConnectionManager {
    // Navigation state
    var screen: AppScreen = .login
    var errorMessage: String?

    // Remote forms
    var remoteForms: [RemoteFormData] = []

    // Chat
    var buffers: [ChatBuffer] = []
    var currentCharacterId: String?
    var localSenderId: String?
    var sessionId = UUID()
    var modelContext: ModelContext?
    private var lastDMRecipientId: String?

    // Tracks which buffer the user is viewing (set by ChatView)
    var activeBuffer: String = "global"

    // Auth
    private(set) var isLoggedIn: Bool = false
    private var cachedToken: String?
    private var pendingEmail: String?
    private var pendingPassword: String?
    private var pendingCreateAccount: Bool = false

    // Networking
    private var client: LNLClient?
    private var eventTask: Task<Void, Never>?

    // MARK: - Connect

    func connect(host: String, port: UInt16, email: String, password: String, createAccount: Bool) {
        screen = .connecting
        errorMessage = nil
        pendingEmail = email
        pendingPassword = password
        pendingCreateAccount = createAccount

        startClient(host: host, port: port)
    }

    /// Reconnect using a cached JWT token
    func reconnectWithToken(host: String, port: UInt16) {
        guard let token = KeychainHelper.load(key: "jwt") else {
            screen = .login
            return
        }
        cachedToken = token

        screen = .connecting
        errorMessage = nil
        pendingEmail = nil
        pendingPassword = nil

        startClient(host: host, port: port)
    }

    private func tearDownClient() {
        eventTask?.cancel()
        eventTask = nil
        if let client {
            Task { await client.disconnect() }
        }
        client = nil
    }

    private func startClient(host: String, port: UInt16) {
        tearDownClient()
        sessionId = UUID()

        let client = LNLClient()
        self.client = client

        eventTask = Task { [weak self] in
            await client.connect(host: host, port: port, key: "Placeholder")
            for await event in client.events {
                guard let self else { break }
                switch event {
                case .connected:
                    if self.cachedToken != nil {
                        self.loginWithToken(self.cachedToken!)
                    } else {
                        self.handleConnected()
                    }
                case .received(let data):
                    log.debug("Event: received \(data.count) bytes, method=\(data.count >= 4 ? String(data.withUnsafeBytes { Int32(littleEndian: $0.loadUnaligned(as: Int32.self)) }) : "?")")
                    self.handlePacket(data)
                case .disconnected(let reason):
                    self.handleDisconnect(reason: reason)
                }
            }
        }
    }

    // MARK: - Chat Actions

    func sendChatMessage(buffer: String, content: String) {
        let packet = PacketEncoder.sendMessage(buffer: buffer, content: content)
        Task { await client?.send(data: packet) }
    }

    func sendDirectMessage(recipientId: String, message: String) {
        lastDMRecipientId = recipientId
        let packet = PacketEncoder.directMessage(recipientId: recipientId, message: message)
        Task { await client?.send(data: packet) }
    }

    // MARK: - Form Actions

    func sendFormEvent(
        formId: String,
        widgetId: String,
        eventType: RemoteFormEventType,
        value: FormEventValue = .none
    ) {
        log.info("sendFormEvent: \(String(describing: eventType)) widget=\(widgetId) form=\(formId)")
        let packet = PacketEncoder.remoteFormEvent(
            formId: formId, widgetId: widgetId,
            eventType: eventType, value: value
        )
        Task { await client?.send(data: packet) }
    }

    // MARK: - Character Actions

    func switchCharacter() {
        guard let charId = currentCharacterId else { return }
        let packet = PacketEncoder.leaveGame(characterId: charId)
        Task { await client?.send(data: packet) }
    }

    func logout() {
        KeychainHelper.delete(key: "jwt")
        cachedToken = nil
        isLoggedIn = false
        tearDownClient()
        resetState()
        screen = .login
    }

    // MARK: - Connection Callbacks

    private func handleConnected() {
        log.info("LiteNetLib connected, sending auth request")
        if let email = pendingEmail, let password = pendingPassword {
            if pendingCreateAccount {
                log.info("Sending CreateAccount for \(email)")
                let packet = PacketEncoder.createAccountRequest(email: email, password: password)
                Task { await client?.send(data: packet) }
            } else {
                log.info("Sending TokenRequest for \(email)")
                let packet = PacketEncoder.tokenRequest(email: email, password: password)
                Task { await client?.send(data: packet) }
            }
        } else {
            log.warning("handleConnected called but no pending credentials")
        }
    }

    private func loginWithToken(_ token: String) {
        let packet = PacketEncoder.loginRequest(token: token)
        Task { await client?.send(data: packet) }
    }

    private func handleDisconnect(reason: String?) {
        log.info("Disconnected: \(reason ?? "unknown")")
        if screen != .login {
            errorMessage = reason ?? "Disconnected"
            screen = .login
        }
        isLoggedIn = false
        // Don't call tearDownClient here — this is already called from the event stream
        // which means the client is already disconnecting. Just clear the reference.
        client = nil
    }

    private func resetState() {
        buffers.removeAll()
        remoteForms.removeAll()
        currentCharacterId = nil
        localSenderId = nil
        errorMessage = nil
    }

    // MARK: - Packet Dispatch

    private func handlePacket(_ data: Data) {
        guard data.count >= 4 else {
            log.warning("Packet too short (\(data.count) bytes)")
            return
        }
        let methodRaw = data.withUnsafeBytes { Int32(littleEndian: $0.loadUnaligned(as: Int32.self)) }
        let payload = Data(data[data.startIndex + 4 ..< data.endIndex])

        guard let method = ClientMethod(rawValue: methodRaw) else {
            log.info("Ignoring unhandled method ID \(methodRaw) (\(payload.count) bytes)")
            return
        }
        log.debug("Received \(String(describing: method))")

        switch method {
        case .accountCreationUpdate:
            guard let response = PacketDecoder.decode(GenericResponse.self, from: payload) else { return }
            handleAccountCreation(response)
        case .tokenRequestUpdate:
            guard let response = PacketDecoder.decode(GenericStringResponse.self, from: payload) else { return }
            handleTokenResponse(response)
        case .loginRequestUpdate:
            guard let response = PacketDecoder.decode(GenericResponse.self, from: payload) else { return }
            handleLoginResponse(response)
        case .addBuffer:
            guard let buffer = PacketDecoder.decode(ChatBuffer.self, from: payload) else { return }
            handleAddBuffer(buffer)
        case .addMessage:
            guard let message = PacketDecoder.decode(ChatMessage.self, from: payload) else { return }
            handleAddMessage(message)
        case .loadInstance:
            do {
                let value = try MessagePackDecoder().decode(MsgPackValue.self, from: payload)
                if let entityId = value[1]?.stringValue {
                    localSenderId = entityId
                    currentCharacterId = entityId
                    log.info("Local entity ID: \(entityId)")
                }
            } catch {
                log.error("LoadInstance decode failed: \(error)")
            }
        case .requestGameplay:
            handleRequestGameplay()
        case .leaveGameUpdate:
            guard let response = PacketDecoder.decode(GenericResponse.self, from: payload) else { return }
            handleLeaveGameUpdate(response)
        case .say:
            guard let say = PacketDecoder.decode(ServerSay.self, from: payload) else { return }
            handleSay(say)
        case .showRemoteForm:
            guard let value = PacketDecoder.decodeDynamic(from: payload) else {
                log.error("Failed to decode showRemoteForm payload (\(payload.count) bytes)")
                return
            }
            handleShowRemoteForm(value)
        case .updateRemoteForm:
            guard let value = PacketDecoder.decodeDynamic(from: payload) else { return }
            handleUpdateRemoteForm(value)
        case .closeRemoteForm:
            guard let value = PacketDecoder.decodeDynamic(from: payload) else { return }
            handleCloseRemoteForm(value)
        case .disconnect:
            guard let info = PacketDecoder.decode(ServerDisconnectInfo.self, from: payload) else { return }
            handleServerDisconnect(info)
        }
    }

    // MARK: - Auth Handlers

    private func handleAccountCreation(_ response: GenericResponse) {
        guard !isLoggedIn else { return }
        if response.success {
            // Account created, now request a token
            if let email = pendingEmail, let password = pendingPassword {
                pendingCreateAccount = false
                let packet = PacketEncoder.tokenRequest(email: email, password: password)
                Task { await client?.send(data: packet) }
            }
        } else {
            errorMessage = response.message ?? "Account creation failed"
            tearDownClient()
            screen = .login
        }
    }

    private func handleTokenResponse(_ response: GenericStringResponse) {
        guard !isLoggedIn else { return }
        if response.success, let token = response.data {
            log.info("Token received, logging in")
            cachedToken = token
            KeychainHelper.save(key: "jwt", value: token)
            loginWithToken(token)
        } else {
            log.error("Token request failed: \(response.message ?? "unknown")")
            errorMessage = response.message ?? "Authentication failed"
            tearDownClient()
            screen = .login
        }
    }

    private func handleLoginResponse(_ response: GenericResponse) {
        guard !isLoggedIn else { return }
        if response.success {
            log.info("Login successful, waiting for RemoteForms")
            isLoggedIn = true
            screen = .forms
        } else {
            log.error("Login failed: \(response.message ?? "unknown")")
            KeychainHelper.delete(key: "jwt")
            cachedToken = nil
            errorMessage = response.message ?? "Login failed"
            tearDownClient()
            screen = .login
        }
    }

    // MARK: - Chat Handlers

    private func handleAddBuffer(_ buffer: ChatBuffer) {
        log.info("addBuffer: \(buffer.name), canSend=\(buffer.canSend)")
        if !buffers.contains(where: { $0.name == buffer.name }) {
            buffers.append(buffer)
        }
    }

    private func handleAddMessage(_ message: ChatMessage) {
        // Learn our own entity ID from echoed messages
        if message.senderIsRecipient, let id = message.senderId {
            if localSenderId == nil { localSenderId = id }
            if currentCharacterId == nil { currentCharacterId = id }
        }

        // For DMs, characterId stores the conversation partner's ID
        let dmPartnerId: String? = if message.buffer == "direct-messages" {
            message.senderIsRecipient ? lastDMRecipientId : message.senderId
        } else {
            nil
        }

        let saved = SavedMessage(
            buffer: message.buffer,
            senderId: message.senderId,
            senderName: message.senderName,
            content: message.content,
            senderIsRecipient: message.senderIsRecipient,
            characterId: dmPartnerId,
            sessionId: sessionId
        )
        modelContext?.insert(saved)
        try? modelContext?.save()

    }

    private func handleSay(_ say: ServerSay) {
        let saved = SavedMessage(
            buffer: "debug",
            senderId: nil,
            senderName: "System",
            content: say.message,
            isSystem: true,
            sessionId: sessionId
        )
        modelContext?.insert(saved)
        try? modelContext?.save()

        announce(say.message)
        playStatusHaptic()
    }

    private func announce(_ text: String) {
        AccessibilityNotification.Announcement(text).post()
    }

    private func playStatusHaptic() {
        guard UserDefaults.standard.bool(forKey: "haptics_enabled", default: true),
              UserDefaults.standard.bool(forKey: "haptics_status", default: true) else { return }
        UINotificationFeedbackGenerator().notificationOccurred(.warning)
    }



    // MARK: - Game State Handlers

    private func handleRequestGameplay() {
        // Game entry signal — switch to chat view
        remoteForms.removeAll()
        screen = .chat
    }

    private func handleLeaveGameUpdate(_ response: GenericResponse) {
        if response.success {
            // Clear chat state, re-login to get account screen
            resetState()
            isLoggedIn = false
            screen = .forms
            if let token = cachedToken {
                loginWithToken(token)
            }
        }
    }

    // MARK: - Remote Form Handlers

    private func handleShowRemoteForm(_ payload: MsgPackValue) {
        guard let form = RemoteFormData.from(payload) else { return }
        remoteForms.append(form)
        if screen != .chat {
            screen = .forms
        }
    }

    private func handleUpdateRemoteForm(_ payload: MsgPackValue) {
        guard let formId = payload[0]?.stringValue,
              let form = remoteForms.first(where: { $0.id == formId }) else { return }
        form.updateWidgets(from: payload)
    }

    private func handleCloseRemoteForm(_ payload: MsgPackValue) {
        guard let formId = payload[0]?.stringValue else { return }
        remoteForms.removeAll { $0.id == formId }
    }

    // MARK: - Server Disconnect

    private func handleServerDisconnect(_ info: ServerDisconnectInfo) {
        errorMessage = info.reason
        tearDownClient()
        isLoggedIn = false
        screen = .login
    }
}
