import Combine
import Foundation
import SocketIO

/// Entspricht `SocketClient.kt`: gleiche Events, `setSessionId`, `login`, Emits.
final class SocketClient: @unchecked Sendable {
    private let baseURL: String
    private var manager: SocketManager?
    private var socket: SocketIOClient?
    private var pendingExpressSessionId: String?

    private let eventSubject = PassthroughSubject<SocketEvent, Never>()

    /// Ereignisse vom Server (analog `SharedFlow<SocketEvent>`).
    var eventPublisher: AnyPublisher<SocketEvent, Never> {
        eventSubject.eraseToAnyPublisher()
    }

    var isConnected: Bool {
        socket?.status == .connected
    }

    init(baseURL: String) {
        self.baseURL = baseURL.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
    }

    func connect() {
        disconnect()

        guard let url = URL(string: baseURL) else {
            notify(.error("Ungültige Socket-Basis-URL"))
            return
        }

        var config: SocketIOClientConfiguration = [
            .log(false),
            .compress,
            .reconnects(true),
            .reconnectAttempts(-1),
            .reconnectWait(1),
            .reconnectWaitMax(30),
            .version(.three),
        ]

        if let cookies = HTTPCookieStorage.shared.cookies(for: url), !cookies.isEmpty {
            config.insert(.cookies(cookies))
        }

        let manager = SocketManager(socketURL: url, config: config)
        self.manager = manager
        let socket = manager.defaultSocket
        self.socket = socket

        socket.on(clientEvent: .connect) { [weak self] _, _ in
            self?.handleSocketConnect()
        }
        socket.on(clientEvent: .disconnect) { [weak self] data, _ in
            let reason = data.first.map { String(describing: $0) }
            self?.notify(.connectionChanged(connected: false, reason: reason))
        }
        socket.on(clientEvent: .error) { [weak self] data, _ in
            let msg = data.first.map { String(describing: $0) } ?? ""
            self?.notify(.error("Socket-Verbindung fehlgeschlagen: \(msg)"))
        }

        registerServerEvents(socket)
        manager.connect()
    }

    func disconnect() {
        socket?.removeAllHandlers()
        manager?.disconnect()
        socket = nil
        manager = nil
    }

    func setSessionId(_ expressSessionId: String) {
        pendingExpressSessionId = expressSessionId
        guard isConnected else { return }
        socket?.emit("setSessionId", ["expressSessionId": expressSessionId])
    }

    func login(userName: String, gender: String, age: Int, country: String, expressSessionId: String?) {
        var payload: [String: Any] = [
            "userName": userName,
            "gender": gender,
            "age": age,
            "country": country,
        ]
        if let expressSessionId {
            payload["expressSessionId"] = expressSessionId
        } else {
            payload["expressSessionId"] = NSNull()
        }
        socket?.emit("login", payload)
    }

    func sendMessage(toUserName: String?, message: String, messageId: String = "\(Int(Date().timeIntervalSince1970 * 1000))") {
        var payload: [String: Any] = [
            "message": message.trimmingCharacters(in: .whitespacesAndNewlines),
            "messageId": messageId,
        ]
        if let toUserName, !toUserName.isEmpty {
            payload["toUserName"] = toUserName
        }
        socket?.emit("message", payload)
    }

    func sendImage(toUserName: String, imageCode: String, imageUrl: String, messageId: String = "\(Int(Date().timeIntervalSince1970 * 1000))") {
        socket?.emit(
            "message",
            [
                "toUserName": toUserName,
                "message": imageCode,
                "messageId": messageId,
                "isImage": true,
                "imageUrl": imageUrl,
            ]
        )
    }

    func requestConversation(withUserName: String) {
        socket?.emit("requestConversation", ["withUserName": withUserName])
    }

    func userSearch(nameIncludes: String?, minAge: Int?, maxAge: Int?, countries: [String], genders: [String]) {
        var payload: [String: Any] = [
            "countries": countries,
            "genders": genders,
        ]
        payload["nameIncludes"] = nameIncludes ?? NSNull()
        payload["minAge"] = minAge.map { $0 as Any } ?? NSNull()
        payload["maxAge"] = maxAge.map { $0 as Any } ?? NSNull()
        socket?.emit("userSearch", payload)
    }

    func requestHistory() {
        socket?.emit("requestHistory")
    }

    func requestOpenConversations() {
        socket?.emit("requestOpenConversations")
    }

    func blockUser(userName: String) {
        socket?.emit("blockUser", ["userName": userName])
    }

    func unblockUser(userName: String) {
        socket?.emit("unblockUser", ["userName": userName])
    }

    // MARK: - Private

    private func handleSocketConnect() {
        if let sid = pendingExpressSessionId {
            socket?.emit("setSessionId", ["expressSessionId": sid])
        }
        // Reihenfolge wie `SocketClient.kt` (EVENT_CONNECT): zuerst setSessionId, dann ConnectionChanged.
        notify(.connectionChanged(connected: true, reason: nil))
    }

    private func notify(_ event: SocketEvent) {
        eventSubject.send(event)
    }

    private func registerServerEvents(_ socket: SocketIOClient) {
        socket.on("connected") { [weak self] data, _ in
            guard let json = Self.firstDictionary(from: data) else { return }
            self?.notify(
                .connected(
                    sessionId: json.stringOrNil("sessionId"),
                    loggedIn: json.bool("loggedIn", default: false),
                    user: json.userObject("user")
                )
            )
        }
        socket.on("loginSuccess") { [weak self] data, _ in
            guard let json = Self.firstDictionary(from: data) else { return }
            self?.notify(.loginSuccess(sessionId: json.stringOrNil("sessionId"), user: json.userObject("user")))
        }
        socket.on("userList") { [weak self] data, _ in
            guard let json = Self.firstDictionary(from: data),
                  let users = json["users"] as? [[String: Any]]
            else { return }
            self?.notify(.userList(users: users.map { UserDto(json: $0) }))
        }
        socket.on("message") { [weak self] data, _ in
            guard let json = Self.firstDictionary(from: data) else { return }
            self?.notify(.incomingMessage(ChatMessageDto(json: json)))
        }
        socket.on("messageSent") { [weak self] data, _ in
            guard let json = Self.firstDictionary(from: data) else { return }
            self?.notify(.messageSent(messageId: json.stringOrNil("messageId"), to: json.stringOrNil("to")))
        }
        socket.on("conversation") { [weak self] data, _ in
            guard let json = Self.firstDictionary(from: data) else { return }
            let withName = json.string("with")
            let rawMessages = json["messages"] as? [[String: Any]] ?? []
            let messages = rawMessages.map { ChatMessageDto(json: $0) }
            self?.notify(.conversation(withUserName: withName, messages: messages))
        }
        socket.on("searchResults") { [weak self] data, _ in
            guard let json = Self.firstDictionary(from: data),
                  let results = json["results"] as? [[String: Any]]
            else { return }
            self?.notify(.searchResults(results.map { UserDto(json: $0) }))
        }
        socket.on("historyResults") { [weak self] data, _ in
            guard let json = Self.firstDictionary(from: data),
                  let results = json["results"] as? [[String: Any]]
            else { return }
            self?.notify(.historyResults(results.map { HistoryItemDto(json: $0) }))
        }
        socket.on("inboxResults") { [weak self] data, _ in
            guard let json = Self.firstDictionary(from: data),
                  let results = json["results"] as? [[String: Any]]
            else { return }
            self?.notify(.inboxResults(results.map { InboxItemDto(json: $0) }))
        }
        socket.on("unreadChats") { [weak self] data, _ in
            guard let json = Self.firstDictionary(from: data) else { return }
            self?.notify(.unreadChats(count: json.int("count", default: 0)))
        }
        socket.on("userBlocked") { [weak self] data, _ in
            guard let json = Self.firstDictionary(from: data) else { return }
            self?.notify(.userBlocked(json.string("userName")))
        }
        socket.on("userUnblocked") { [weak self] data, _ in
            guard let json = Self.firstDictionary(from: data) else { return }
            self?.notify(.userUnblocked(json.string("userName")))
        }
        socket.on("commandResult") { [weak self] data, _ in
            guard let json = Self.firstDictionary(from: data) else { return }
            let lines = (json["lines"] as? [Any])?.map { String(describing: $0) } ?? []
            let kind = json.string("kind", default: "info")
            self?.notify(.commandResult(lines: lines, kind: kind))
        }
        socket.on("commandTable") { [weak self] data, _ in
            guard let json = Self.firstDictionary(from: data) else { return }
            let title = json.string("title", default: "Ausgabe")
            let columns = (json["columns"] as? [Any])?.map { String(describing: $0) } ?? []
            let rows = Self.nestedStringRows(json["rows"])
            self?.notify(.commandTable(title: title, columns: columns, rows: rows))
        }
        socket.on("error") { [weak self] data, _ in
            let json = Self.firstDictionary(from: data)
            let message =
                json?.stringOrNil("message")
                ?? data.first.map { String(describing: $0) }
                ?? "Unbekannter Socket-Fehler"
            self?.notify(.error(message))
        }
    }

    private static func firstDictionary(from data: [Any]) -> [String: Any]? {
        guard let first = data.first else { return nil }
        if let d = first as? [String: Any] { return d }
        if let n = first as? NSDictionary { return n as? [String: Any] }
        return nil
    }

    private static func nestedStringRows(_ any: Any?) -> [[String]] {
        guard let outer = any as? [Any] else { return [] }
        return outer.map { row in
            (row as? [Any])?.map { String(describing: $0) } ?? []
        }
    }
}

// MARK: - JSON helpers (JSONObject-Äquivalent)

private extension Dictionary where Key == String, Value == Any {
    func stringOrNil(_ key: String) -> String? {
        guard let v = self[key], !(v is NSNull) else { return nil }
        if let s = v as? String { return s }
        return String(describing: v)
    }

    func string(_ key: String, default def: String = "") -> String {
        stringOrNil(key) ?? def
    }

    func bool(_ key: String, default def: Bool) -> Bool {
        guard let v = self[key], !(v is NSNull) else { return def }
        if let b = v as? Bool { return b }
        if let n = v as? NSNumber { return n.boolValue }
        return def
    }

    func int(_ key: String, default def: Int) -> Int {
        guard let v = self[key], !(v is NSNull) else { return def }
        if let i = v as? Int { return i }
        if let n = v as? NSNumber { return n.intValue }
        return def
    }

    func userObject(_ key: String) -> UserDto? {
        guard let nested = self[key] as? [String: Any] else { return nil }
        return UserDto(json: nested)
    }
}

private extension UserDto {
    init(json: [String: Any]) {
        self.init(
            sessionId: json.stringOrNil("sessionId"),
            userName: json.string("userName"),
            gender: json.string("gender"),
            age: json.int("age", default: 0),
            country: json.string("country"),
            isoCountryCode: json.string("isoCountryCode")
        )
    }
}

private extension ChatMessageDto {
    init(json: [String: Any]) {
        self.init(
            from: json.string("from"),
            to: json.stringOrNil("to"),
            message: json.string("message"),
            messageId: json.stringOrNil("messageId"),
            timestamp: json.string("timestamp"),
            read: json.bool("read", default: false),
            isImage: json.bool("isImage", default: false),
            imageType: json.stringOrNil("imageType"),
            imageUrl: json.stringOrNil("imageUrl"),
            imageCode: json.stringOrNil("imageCode")
        )
    }
}

private extension HistoryItemDto {
    init(json: [String: Any]) {
        let last: ChatMessageDto? = {
            guard let nested = json["lastMessage"] as? [String: Any] else { return nil }
            return ChatMessageDto(json: nested)
        }()
        self.init(userName: json.string("userName"), lastMessage: last)
    }
}

private extension InboxItemDto {
    init(json: [String: Any]) {
        self.init(userName: json.string("userName"), unreadCount: json.int("unreadCount", default: 0))
    }
}
