import APIClient
import Combine
import ComposableArchitecture
import Foundation
import Utilities
/*
 Simple following status cache that keeps up to 500 handles in memory.
 - Handles are evicted in LRU order.
 - Handles are fetched in batches of 10.
 - Handles both fetching and toggling follow status.
 - Automatically syncs with profile events from other parts of the app.
 */
final actor SimpleFollowCache: Sendable {
    private var followStatus: [String: Bool] = [:]
    private var accessOrder: [String] = [] // LRU tracking
    private let maxCacheSize = 500
    // Number of profiles to fetch at once to determine follow status
    private let followingStatusBatchSize = 10

    // Keep track of follow request tasks by handle to prevent duplicates
    private var inProgressFollowRequests: [String: Task<Void, Never>] = [:]

    // Profile events subscription task
    private var profileEventsTask: Task<Void, Never>?

    init() {
        startProfileEventsSubscription()
    }

    private nonisolated func startProfileEventsSubscription() {
        let task = Task { [weak self] in
            guard let self else { return }
            @Dependency(\.eventBus.getProfilePublisher) var getProfilePublisher

            for await profileEvent in getProfilePublisher().values {
                if case .profileUpdated(let profile) = profileEvent {
                    await self.setFollowStatus(profile.isFollowing, for: profile.handle)
                }
            }
        }

        Task { [weak self] in
            guard let self else { return }
            await self.setProfileEventsTask(task)
        }
    }

    private func setProfileEventsTask(_ task: Task<Void, Never>) {
        profileEventsTask = task
    }

    deinit {
        profileEventsTask?.cancel()
    }

    func getFollowStatus(for handle: String) -> Bool {
        // Update access order for LRU
        if followStatus[handle] != nil {
            updateAccessOrder(for: handle)
        }
        return followStatus[handle] ?? false
    }

    func hasBeenFetched(for handle: String) -> Bool {
        let exists = followStatus[handle] != nil
        if exists {
            updateAccessOrder(for: handle)
        }
        return exists
    }

    func setFollowStatus(_ isFollowing: Bool, for handle: String) {
        followStatus[handle] = isFollowing
        updateAccessOrder(for: handle)
        evictIfNeeded()
    }

    func setMultipleFollowStatus(_ statuses: [String: Bool]) {
        for (handle, isFollowing) in statuses {
            followStatus[handle] = isFollowing
            updateAccessOrder(for: handle)
        }
        evictIfNeeded()
    }

    func hydrateFromHooks(_ hooks: [Hook]) {
        for hook in hooks {
            guard let handle = hook.user?.handle else { continue }
            followStatus[handle] = hook.currentUserFollowsCreator
            updateAccessOrder(for: handle)
        }
        evictIfNeeded()
    }

    func removeHandle(_ handle: String) {
        followStatus.removeValue(forKey: handle)
        accessOrder.removeAll { $0 == handle }
    }

    private func updateAccessOrder(for handle: String) {
        // Remove from current position
        accessOrder.removeAll { $0 == handle }
        // Add to end (most recently used)
        accessOrder.append(handle)
    }

    private func evictIfNeeded() {
        while followStatus.count > maxCacheSize {
            guard let leastRecentlyUsed = accessOrder.first else { break }
            followStatus.removeValue(forKey: leastRecentlyUsed)
            accessOrder.removeFirst()
        }
    }

    func loadFollowStatusBatch(
        handles: [String],
        eventSubject: PassthroughSubject<HooksPlayerEvent, Never>
    ) async {
        let batch = Array(handles.prefix(followingStatusBatchSize))

        var results: [String: Bool] = [:]

        for handle in batch {
            do {
                @Dependency(\.apiClientV2) var api
                let profile = try await api.getProfile(handle, 0, .playCount, false, false)
                results[handle] = profile.isFollowing
            } catch {
                log.telemetry.error(error, message: "Failed to fetch follow status for \(handle).")
            }
        }

        // Update cache and send events
        setMultipleFollowStatus(results)

        for (handle, isFollowing) in results {
            eventSubject.send(.followStatusUpdated(handle, isFollowing))
        }
    }

    func toggleFollow(
        handle: String,
        eventSubject: PassthroughSubject<HooksPlayerEvent, Never>,
        recommendationMetadata: HooksRecommendationMetadata? = nil
    ) async {
        // If the follow request is already in progress, don't make duplicate requests
        guard !hasInProgressRequest(for: handle) else { return }

        let currentStatus = getFollowStatus(for: handle)
        let newStatus = !currentStatus

        // Update cache immediately for optimistic UI
        setFollowStatus(newStatus, for: handle)
        eventSubject.send(.followStatusUpdated(handle, newStatus))

        let task = Task { [weak self] in
            defer {
                Task {
                    await self?.removeInProgressRequest(for: handle)
                }
            }

            do {
                @Dependency(\.apiClientV2) var api
                try await api.followProfile(handle, currentStatus, recommendationMetadata)
            } catch {
                log.telemetry.error(error, message: "Failed to toggle follow for \(handle).")
                await self?.revertFollowStatus(handle: handle, to: currentStatus, eventSubject: eventSubject)
            }
        }

        inProgressFollowRequests[handle] = task
    }

    private func hasInProgressRequest(for handle: String) -> Bool {
        inProgressFollowRequests[handle] != nil
    }

    private func removeInProgressRequest(for handle: String) {
        inProgressFollowRequests.removeValue(forKey: handle)
    }

    private func revertFollowStatus(
        handle: String,
        to status: Bool,
        eventSubject: PassthroughSubject<HooksPlayerEvent, Never>
    ) {
        setFollowStatus(status, for: handle)
        eventSubject.send(.followStatusUpdated(handle, status))
    }
}
