import Foundation

// MARK: - Chat Event Types

enum ChatEventType {
    case joined, userJoined, userLeft, chatEnded, disconnected, error
}

struct ChatEvent {
    let type: ChatEventType
    let message: String?
    let data: [String: Any]?

    init(type: ChatEventType, message: String? = nil, data: [String: Any]? = nil) {
        self.type = type
        self.message = message
        self.data = data
    }
}

struct TypingEvent {
    let userType: String
    let userName: String
    let isTyping: Bool
}

// MARK: - Chat Service (WebSocket)

@MainActor
final class ChatService: ObservableObject {
    private static let wsBaseURL = "wss://app.payfrit.com:3001"

    @Published var isConnected = false
    @Published var chatClosed = false

    private var webSocketTask: URLSessionWebSocketTask?
    private var currentTaskId: Int?
    private var userType: String?
    private var pingTimer: Timer?

    // Callbacks
    var onMessage: ((ChatMessage) -> Void)?
    var onTyping: ((TypingEvent) -> Void)?
    var onEvent: ((ChatEvent) -> Void)?

    // MARK: - Connect

    func connect(taskId: Int, userToken: String, userType: String) async -> Bool {
        if webSocketTask != nil { disconnect() }

        currentTaskId = taskId
        self.userType = userType

        guard let url = URL(string: Self.wsBaseURL) else { return false }

        let session = URLSession(configuration: .default)
        webSocketTask = session.webSocketTask(with: url)
        webSocketTask?.resume()

        // Send join event
        let joinPayload: [String: Any] = [
            "event": "join-chat",
            "task_id": taskId,
            "user_token": userToken,
            "user_type": userType
        ]

        guard let joinData = try? JSONSerialization.data(withJSONObject: joinPayload),
              let joinString = String(data: joinData, encoding: .utf8) else { return false }

        do {
            try await webSocketTask?.send(.string(joinString))
            isConnected = true
            startListening()
            startPing()
            return true
        } catch {
            isConnected = false
            return false
        }
    }

    // MARK: - Listen

    private func startListening() {
        webSocketTask?.receive { [weak self] result in
            Task { @MainActor in
                guard let self = self else { return }
                switch result {
                case .success(let message):
                    switch message {
                    case .string(let text):
                        self.handleMessage(text)
                    case .data(let data):
                        if let text = String(data: data, encoding: .utf8) {
                            self.handleMessage(text)
                        }
                    @unknown default:
                        break
                    }
                    self.startListening()
                case .failure:
                    self.isConnected = false
                    self.onEvent?(ChatEvent(type: .disconnected))
                }
            }
        }
    }

    private func handleMessage(_ text: String) {
        guard let data = text.data(using: .utf8),
              let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
              let event = json["event"] as? String else { return }

        switch event {
        case "joined":
            onEvent?(ChatEvent(type: .joined, data: json))
        case "new-message":
            if let msgData = json["data"] as? [String: Any] {
                let msg = ChatMessage(json: msgData)
                onMessage?(msg)
            }
        case "user-typing":
            let data = json["data"] as? [String: Any] ?? json
            onTyping?(TypingEvent(
                userType: data["userType"] as? String ?? "",
                userName: data["userName"] as? String ?? "",
                isTyping: data["isTyping"] as? Bool ?? false
            ))
        case "user-joined":
            onEvent?(ChatEvent(type: .userJoined, data: json["data"] as? [String: Any]))
        case "user-left":
            onEvent?(ChatEvent(type: .userLeft, data: json["data"] as? [String: Any]))
        case "chat-ended", "chat-closed":
            chatClosed = true
            onEvent?(ChatEvent(type: .chatEnded, message: (json["data"] as? [String: Any])?["message"] as? String ?? "Chat has ended"))
        case "error":
            onEvent?(ChatEvent(type: .error, message: (json["data"] as? [String: Any])?["message"] as? String ?? "Unknown error"))
        default:
            break
        }
    }

    // MARK: - Send

    func sendMessage(_ text: String) {
        guard let taskId = currentTaskId else { return }
        let payload: [String: Any] = [
            "event": "send-message",
            "task_id": taskId,
            "message": text
        ]
        sendJSON(payload)
    }

    func setTyping(_ isTyping: Bool) {
        guard let taskId = currentTaskId else { return }
        let payload: [String: Any] = [
            "event": "typing",
            "task_id": taskId,
            "is_typing": isTyping
        ]
        sendJSON(payload)
    }

    func closeChatWS() {
        guard let taskId = currentTaskId else { return }
        let payload: [String: Any] = [
            "event": "chat-closed",
            "task_id": taskId
        ]
        sendJSON(payload)
    }

    private func sendJSON(_ payload: [String: Any]) {
        guard let data = try? JSONSerialization.data(withJSONObject: payload),
              let string = String(data: data, encoding: .utf8) else { return }
        webSocketTask?.send(.string(string)) { _ in }
    }

    // MARK: - Ping

    private func startPing() {
        pingTimer?.invalidate()
        pingTimer = Timer.scheduledTimer(withTimeInterval: 30, repeats: true) { [weak self] _ in
            self?.webSocketTask?.sendPing { _ in }
        }
    }

    // MARK: - Disconnect

    func disconnect() {
        pingTimer?.invalidate()
        pingTimer = nil
        webSocketTask?.cancel(with: .goingAway, reason: nil)
        webSocketTask = nil
        isConnected = false
        currentTaskId = nil
        userType = nil
    }
}
