import APIClient
import Collections
import Foundation

actor CommentsSheetCache {
    private static let maxCacheCount: Int = 10
    private static let maxUsersPerThread: Int = 50

    private var commentsCache: [String: CommentsSheet] = [:]
    private var countsCache: [String: Int] = [:]
    private var usersInThreads: [String: [String]] = [:] // entityId -> user handles

    private var mostRecentCommentTrackerIndex: Int = .zero
    private var mostRecentCommentsSections = Array(repeating: "", count: CommentsSheetCache.maxCacheCount)

    func getComment(entityId: String, entityType: CommentEntity.CommentEntityType, commentID: String) async -> CommentEntity? {
        let cacheKey = "\(entityType.rawValue):\(entityId)"
        let cacheItem = commentsCache[cacheKey]
        let result = cacheItem?.commentById(commentID)
        return result
    }

    func getCurrentCommentsCache() async -> CommentsSheetMap {
        var cacheCopy: [String: CommentsSheet] = [:]
        for item in commentsCache {
            cacheCopy[item.key] = item.value
        }
        return CommentsSheetMap(commentsCache: cacheCopy)
    }

    func getCurrentCountCache() async -> CommentsTotalCountMap {
        var cacheCopy: [String: Int] = [:]
        for item in countsCache {
            cacheCopy[item.key] = item.value
        }
        return CommentsTotalCountMap(counts: cacheCopy)
    }

    func getCurrentAccessCache() async -> CommentsAccessMap {
        var accessCacheCopy: [String: Bool] = [:]
        for item in commentsCache {
            accessCacheCopy[item.key] = item.value.allowComment
        }
        return CommentsAccessMap(accessMap: accessCacheCopy)
    }

    func addUserToThread(_ entityId: String, _ entityType: CommentEntity.CommentEntityType, _ userHandle: String) async {
        let cacheKey = "\(entityType.rawValue):\(entityId)"

        var users = OrderedSet(usersInThreads[cacheKey, default: []])
        users.append(userHandle) // OrderedSet prevents duplicates

        let finalUsers = Array(users)
        usersInThreads[cacheKey] = finalUsers

        // Keep only the most recent users
        if finalUsers.count > Self.maxUsersPerThread {
            usersInThreads[cacheKey] = Array(finalUsers.suffix(Self.maxUsersPerThread))
        }
    }

    func addUsersToThread(_ entityId: String, _ entityType: CommentEntity.CommentEntityType, _ userHandles: [String]) async {
        let cacheKey = "\(entityType.rawValue):\(entityId)"
        var users = OrderedSet(usersInThreads[cacheKey, default: []])
        for userHandle in userHandles {
            users.append(userHandle) // OrderedSet prevents duplicates
        }

        let finalUsers = Array(users)
        usersInThreads[cacheKey] = finalUsers

        // Keep only the most recent users
        if finalUsers.count > Self.maxUsersPerThread {
            usersInThreads[cacheKey] = Array(finalUsers.suffix(Self.maxUsersPerThread))
        }
    }

    func getUsersInThread(_ entityId: String, _ entityType: CommentEntity.CommentEntityType) async -> [String] {
        let cacheKey = "\(entityType.rawValue):\(entityId)"
        return usersInThreads[cacheKey] ?? []
    }

    func updateCountCacheWithEntity(_ entityId: String, entityType: CommentEntity.CommentEntityType, commentCount: Int) async {
        let cacheKey = "\(entityType.rawValue):\(entityId)"
        countsCache[cacheKey] = commentCount
    }

    func updateCacheWithCommentsPage(_ entityId: String, entityType: CommentEntity.CommentEntityType, page: CommentsSheetPage) async {
        let cacheKey = "\(entityType.rawValue):\(entityId)"

        // Track users from comments
        let userHandles = page.results.map(\.userHandle)
        await addUsersToThread(entityId, entityType, userHandles)

        if var cacheItem = commentsCache[cacheKey] {
            cacheItem.addCommentsPage(page) // This is mutating and causes prepItem to run again
            commentsCache[cacheKey] = cacheItem
        } else {
            limitCache(cacheKey)
            commentsCache[cacheKey] = CommentsSheet(
                entityId: entityId,
                entityType: entityType,
                nextPageCursor: page.nextCursor,
                comments: page.results,
                allowComment: page.allowComment,
                disableReason: page.disableReason
            )
        }
    }

    func limitCache(_ newEntityKey: String) {
        let trackerIndex = mostRecentCommentTrackerIndex
        let oldEntityKey = mostRecentCommentsSections[trackerIndex]

        // Check if oldEntityKey exists in OTHER positions (not the current index)
        let isStillReferenced = mostRecentCommentsSections.enumerated().contains { index, key in
            index != trackerIndex && key == oldEntityKey
        }

        // Remove the old entry from the cache if it exists
        // and is not still referenced in the tracker array
        if !oldEntityKey.isEmpty,
           oldEntityKey != newEntityKey,
           !isStillReferenced
        {
            commentsCache.removeValue(forKey: oldEntityKey)
            usersInThreads.removeValue(forKey: oldEntityKey)
        }

        // Update the tracker and move to the next index; circular
        mostRecentCommentsSections[trackerIndex] = newEntityKey
        mostRecentCommentTrackerIndex = (trackerIndex + 1) % CommentsSheetCache.maxCacheCount
    }

    func updateCacheWithReplies(_ entityId: String, entityType: CommentEntity.CommentEntityType, commentID: String, page: CommentSheetRepliesPage) async {
        let cacheKey = "\(entityType.rawValue):\(entityId)"

        // Track users from replies
        let userHandles = page.replies.map(\.userHandle)
        await addUsersToThread(entityId, entityType, userHandles)

        guard var cacheItem = commentsCache[cacheKey] else { return }
        cacheItem.addRepliesPage(page, rootCommentID: commentID) // This is mutating and causes prepItem to run again
        commentsCache[cacheKey] = cacheItem
    }

    func addNewCommentToCache(_ entityId: String, entityType: CommentEntity.CommentEntityType, newComment: CommentEntity) async {
        let cacheKey = "\(entityType.rawValue):\(entityId)"

        // Track user from new comment
        await addUserToThread(entityId, entityType, newComment.userHandle)

        if var cacheItem = commentsCache[cacheKey] {
            cacheItem.addComment(newComment) // This is mutating and causes prepItem to run again
            commentsCache[cacheKey] = cacheItem
        } else {
            // Unlikely to reach this case
            //
            // Assumes this means there are no comments,
            // As reaching an area where you are able to comment
            // would mean first requesting the existing comments
            commentsCache[cacheKey] = CommentsSheet(
                entityId: entityId,
                entityType: entityType,
                nextPageCursor: nil,
                comments: [newComment],
                allowComment: true,
                disableReason: nil
            )
        }
    }

    func updateReactionOnCommentInCache(_ entityId: String, entityType: CommentEntity.CommentEntityType, commentID: String, reaction: CommentReactionType, numLikes: Int) async {
        let cacheKey = "\(entityType.rawValue):\(entityId)"
        guard var cacheItem = commentsCache[cacheKey] else { return }
        // This is mutating and however DOES NOT cause prepItem to run again
        cacheItem.updateCommentReaction(commentID, reaction, numLikes)
        commentsCache[cacheKey] = cacheItem
    }

    func updateCommentAbilityOnEntity(_ entityId: String, entityType: CommentEntity.CommentEntityType, canComment: Bool) async {
        let cacheKey = "\(entityType.rawValue):\(entityId)"
        guard var cacheItem = commentsCache[cacheKey] else { return }
        cacheItem.allowComment = canComment
        commentsCache[cacheKey] = cacheItem
    }

    func deleteCommentInCache(_ entityId: String, entityType: CommentEntity.CommentEntityType, commentID: String) async {
        let cacheKey = "\(entityType.rawValue):\(entityId)"
        guard var cacheItem = commentsCache[cacheKey] else { return }
        cacheItem.deleteComment(commentID)
        commentsCache[cacheKey] = cacheItem
    }

    func getCommentsCursorForEntity(_ entityId: String, entityType: CommentEntity.CommentEntityType) async -> String? {
        let cacheKey = "\(entityType.rawValue):\(entityId)"
        return commentsCache[cacheKey]?.nextPageCursor
    }

    func getCachedCommentsForEntity(_ entityId: String, entityType: CommentEntity.CommentEntityType) async -> CommentsSheet? {
        let cacheKey = "\(entityType.rawValue):\(entityId)"
        return commentsCache[cacheKey]
    }

    func getReplyCursorForComment(_ commentID: String, entityId: String, entityType: CommentEntity.CommentEntityType) async -> String? {
        let cacheKey = "\(entityType.rawValue):\(entityId)"
        return commentsCache[cacheKey]?.replyCursorForComment(commentID)
    }
}
