import Foundation

@MainActor
final class PrivateChatViewModel: ObservableObject {
    @Published private(set) var messages: [MessageItem] = []
    @Published private(set) var isInitialLoading: Bool = false
    @Published private(set) var isLoadingMore: Bool = false
    @Published var errorMessage: String?
    @Published var sendingErrorMessage: String?
    @Published private(set) var isSending: Bool = false
    @Published private(set) var hasMore: Bool = true

    private let chatService: ChatService
    private let chatId: String
    private let currentUserId: String?
    private let pageSize: Int
    let maxMessageLength: Int = 4096
    private var didLoadInitially: Bool = false
    private var messageObserver: NSObjectProtocol?

    init(chatId: String, currentUserId: String?, chatService: ChatService = ChatService(), pageSize: Int = 30) {
        self.chatId = chatId
        self.currentUserId = currentUserId
        self.chatService = chatService
        self.pageSize = pageSize

        messageObserver = NotificationCenter.default.addObserver(
            forName: .socketDidReceivePrivateMessage,
            object: nil,
            queue: .main
        ) { [weak self] notification in
            guard
                let self,
                let message = notification.object as? MessageItem,
                message.chatId == self.chatId
            else { return }
            self.messages = Self.merge(existing: self.messages, newMessages: [message])
        }
    }

    deinit {
        if let observer = messageObserver {
            NotificationCenter.default.removeObserver(observer)
        }
    }

    func loadInitialHistory(force: Bool = false) {
        if !force && didLoadInitially { return }
        guard !isInitialLoading else { return }

        isInitialLoading = true
        errorMessage = nil
        hasMore = true

        chatService.fetchPrivateChatHistory(chatId: chatId, beforeMessageId: nil, limit: pageSize) { [weak self] result in
            guard let self else { return }

            switch result {
            case .success(let data):
                self.hasMore = data.hasMore
                self.messages = Self.merge(existing: [], newMessages: data.items)
                self.didLoadInitially = true
            case .failure(let error):
                self.errorMessage = self.message(for: error)
            }

            self.isInitialLoading = false
        }
    }

    func sendMessage(text: String, completion: @escaping (Bool) -> Void) {
        let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmed.isEmpty else {
            completion(false)
            return
        }

        guard !isSending else {
            sendingErrorMessage = NSLocalizedString("Дождитесь отправки предыдущего сообщения.", comment: "")
            completion(false)
            return
        }
        guard let currentUserId else {
            completion(false)
            return
        }

        isSending = true
        sendingErrorMessage = nil

        let chunks = splitMessage(trimmed, maxLength: maxMessageLength)
        let dispatchGroup = DispatchGroup()
        var overallSuccess = true

        for chunk in chunks {
            dispatchGroup.enter()
            chatService.sendPrivateMessage(chatId: chatId, content: chunk) { [weak self] result in
                guard let self else {
                    dispatchGroup.leave()
                    return
                }

                switch result {
                case .success(let data):
                    let newMessage = MessageItem(
                        messageId: data.messageId,
                        messageType: "text",
                        chatId: data.chatId,
                        senderId: currentUserId,
                        senderData: nil,
                        content: chunk,
                        mediaLink: nil,
                        isViewed: true,
                        viewedAt: nil,
                        createdAt: data.createdAt,
                        updatedAt: data.createdAt,
                        forwardMetadata: nil
                    )
                    self.messages = Self.merge(existing: self.messages, newMessages: [newMessage])
                case .failure(let error):
                    self.sendingErrorMessage = self.message(for: error)
                    overallSuccess = false
                }
                dispatchGroup.leave()
            }
        }

        dispatchGroup.notify(queue: .main) {
            self.isSending = false
            if overallSuccess {
                self.errorMessage = nil
            }
            completion(overallSuccess)
        }
    }

    private func splitMessage(_ message: String, maxLength: Int) -> [String] {
        var chunks: [String] = []
        var remaining = message
        while !remaining.isEmpty {
            let chunk = String(remaining.prefix(maxLength))
            chunks.append(chunk)
            remaining = String(remaining.dropFirst(maxLength))
        }
        return chunks
    }

    func refresh() {
        didLoadInitially = false
        loadInitialHistory(force: true)
    }

    func loadMoreIfNeeded(for message: MessageItem) {
        guard didLoadInitially, !isInitialLoading, hasMore, !isLoadingMore else { return }

        guard let messageIndex = messages.firstIndex(where: { $0.id == message.id }) else {
            return
        }

        let threshold = 10
        guard messageIndex < threshold else {
            return
        }

        guard let oldestMessage = messages.first else { return }

        isLoadingMore = true

        chatService.fetchPrivateChatHistory(chatId: chatId, beforeMessageId: oldestMessage.id, limit: pageSize) { [weak self] result in
            guard let self else { return }

            switch result {
            case .success(let data):
                self.hasMore = data.hasMore
                if !data.items.isEmpty {
                    self.messages = Self.merge(existing: self.messages, newMessages: data.items)
                }
            case .failure(let error):
                self.errorMessage = self.message(for: error)
            }

            self.isLoadingMore = false
        }
    }

    private func message(for error: Error) -> String {
        if let chatError = error as? ChatServiceError {
            return chatError.errorDescription ?? NSLocalizedString("Не удалось загрузить историю чата.", comment: "")
        }

        if let networkError = error as? NetworkError {
            switch networkError {
            case .unauthorized:
                return NSLocalizedString("Сессия истекла. Войдите снова.", comment: "")
            case .invalidURL, .noResponse:
                return NSLocalizedString("Ошибка соединения с сервером.", comment: "")
            case .network(let underlying):
                return String(format: NSLocalizedString("Ошибка сети: %@", comment: ""), underlying.localizedDescription)
            case .server(let statusCode, _):
                return String(format: NSLocalizedString("Ошибка сервера (%@).", comment: ""), "\(statusCode)")
            }
        }

        return NSLocalizedString("Неизвестная ошибка. Попробуйте позже.", comment: "")
    }

    private static func merge(existing: [MessageItem], newMessages: [MessageItem]) -> [MessageItem] {
        var combined: [MessageItem] = []
        combined.reserveCapacity(existing.count + newMessages.count)

        var seen: Set<String> = []

        for message in newMessages {
            if seen.insert(message.id).inserted {
                combined.append(message)
            }
        }

        for message in existing {
            if seen.insert(message.id).inserted {
                combined.append(message)
            }
        }

        combined.sort(by: compare)
        return combined
    }

    private static func compare(lhs: MessageItem, rhs: MessageItem) -> Bool {
        if let lhsDate = lhs.createdAt, let rhsDate = rhs.createdAt, lhsDate != rhsDate {
            return lhsDate < rhsDate
        }

        if let lhsId = Int(lhs.messageId), let rhsId = Int(rhs.messageId), lhsId != rhsId {
            return lhsId < rhsId
        }

        return lhs.messageId < rhs.messageId
    }
}
