import AVFoundation
import Foundation
import UIKit
import Utilities

/*
 Keeps a total of `maxCachedItems` (20) video covers in memory.
 */
public final actor VideoCoverCacheManager: NSObject {
    private let log = Logger(category: "VideoCoverCacheManager")

    // In-memory cache for AVAssets
    private let memoryCache: NSCache<NSString, Box<AVAsset>> = {
        let cache = NSCache<NSString, Box<AVAsset>>()
        cache.name = "com.suno.videoCoverCache"
        cache.countLimit = 6 // Keep 6 most recent videos in memory
        cache.totalCostLimit = 100 * 1024 * 1024 // 100MB limit
        return cache
    }()

    private var memoryWarningObserver: NSObjectProtocol?

    override public init() {
        super.init()
        memoryCache.delegate = self

        memoryWarningObserver = NotificationCenter.default.addObserver(
            forName: UIApplication.didReceiveMemoryWarningNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            Task { [weak self] in
                await self?.clearMemoryCache()
            }
        }
    }

    deinit {
        memoryCache.delegate = nil
        // Clean up observer if still registered
        if let observer = memoryWarningObserver {
            NotificationCenter.default.removeObserver(observer)
        }
    }

    private func clearMemoryCache() {
        log.debug("Clearing memory cache")
        memoryCache.removeAllObjects()
    }

    // Get cached asset from memory
    private func cachedAsset(for remoteUrl: String) -> AVAsset? {
        let key = NSString(string: remoteUrl)

        if let assetBox = memoryCache.object(forKey: key) {
            log.debug("\(remoteUrl): Found in cache")
            return assetBox.value
        }

        return nil
    }

    // Cache an asset in memory
    public func cacheAsset(for remoteUrl: String) {
        let key = NSString(string: remoteUrl)

        // Don't cache if already exists
        guard memoryCache.object(forKey: key) == nil,
              let url = URL(string: remoteUrl) else { return }

        let asset = AVAsset(url: url)
        memoryCache.setObject(Box(asset), forKey: key)
        log.debug("\(remoteUrl): Added to cache")
    }

    private func removeAsset(for remoteUrl: String) {
        let key = NSString(string: remoteUrl)
        memoryCache.removeObject(forKey: key)
        log.debug("\(remoteUrl): Removed from cache")
    }

    // Safe methods for setting and getting cache entries from non-isolated contexts
    public func getCachedAsset(_ remoteUrl: String) -> AVAsset? {
        return self[remoteUrl]
    }

    public func setCachedAsset(_ remoteUrl: String, asset: AVAsset?) {
        self[remoteUrl] = asset
    }
}

extension VideoCoverCacheManager: NSCacheDelegate {
    private func cache(_: NSCache<AnyObject, AnyObject>, willEvictObject obj: Any) async {
        if let assetBox = obj as? Box<AVAsset> {
            log.debug("\(assetBox.value): Evicted from cache")
        }
    }
}

extension VideoCoverCacheManager {
    private subscript(remoteUrl: String) -> AVAsset? {
        get {
            cachedAsset(for: remoteUrl)
        }
        set {
            if let newValue = newValue {
                let key = NSString(string: remoteUrl)
                memoryCache.setObject(Box(newValue), forKey: key)
                log.debug("\(remoteUrl): Added to cache")
            } else {
                removeAsset(for: remoteUrl)
            }
        }
    }
}
