//
//  CachedChannelData.swift
//  Yattee
//
//  Cached channel data from Subscription or RecentChannel for instant display.
//

import Foundation

/// Cached channel data loaded from local SwiftData stores (Subscription or RecentChannel).
/// Used to show channel info immediately while API responses are loading.
struct CachedChannelData: Codable {
    let name: String
    let thumbnailURL: URL?
    let bannerURL: URL?
    let subscriberCount: Int?
    let description: String?

    /// In-memory cache of author data from video detail API responses.
    @MainActor
    private static var authorCache: [String: CachedChannelData] = [:]

    /// Whether the disk cache has been loaded into memory.
    @MainActor
    private static var diskLoaded = false

    /// Maximum number of cached author entries.
    private static let maxCacheSize = 500

    private static var cacheFileURL: URL {
        let caches = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
        return caches.appendingPathComponent("AuthorCache", isDirectory: true)
            .appendingPathComponent("authors.json")
    }

    init(name: String, thumbnailURL: URL?, bannerURL: URL?, subscriberCount: Int?, description: String? = nil) {
        self.name = name
        self.thumbnailURL = thumbnailURL
        self.bannerURL = bannerURL
        self.subscriberCount = subscriberCount
        self.description = description
    }

    @MainActor
    static func cacheAuthor(_ author: Author) {
        guard !author.id.isEmpty, !author.name.isEmpty else { return }
        loadFromDiskIfNeeded()
        let existing = authorCache[author.id]
        authorCache[author.id] = CachedChannelData(
            name: author.name,
            thumbnailURL: author.thumbnailURL ?? existing?.thumbnailURL,
            bannerURL: existing?.bannerURL,
            subscriberCount: author.subscriberCount ?? existing?.subscriberCount,
            description: existing?.description
        )

        // Evict oldest entries if over limit
        if authorCache.count > maxCacheSize {
            let excess = authorCache.count - maxCacheSize
            let keysToRemove = Array(authorCache.keys.prefix(excess))
            for key in keysToRemove {
                authorCache.removeValue(forKey: key)
            }
        }

        saveToDisk()
    }

    // MARK: - Disk Persistence

    @MainActor
    private static func loadFromDiskIfNeeded() {
        guard !diskLoaded else { return }
        diskLoaded = true

        let url = cacheFileURL
        guard FileManager.default.fileExists(atPath: url.path) else { return }

        do {
            let data = try Data(contentsOf: url)
            let decoded = try JSONDecoder().decode([String: CachedChannelData].self, from: data)
            // Only fill entries not already present in memory
            for (key, value) in decoded where authorCache[key] == nil {
                authorCache[key] = value
            }
        } catch {
            try? FileManager.default.removeItem(at: url)
        }
    }

    @MainActor
    private static func saveToDisk() {
        let snapshot = authorCache
        Task.detached(priority: .utility) {
            let url = cacheFileURL
            let directory = url.deletingLastPathComponent()
            try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
            if let data = try? JSONEncoder().encode(snapshot) {
                try? data.write(to: url, options: .atomic)
            }
        }
    }

    init(from subscription: Subscription) {
        name = subscription.name
        thumbnailURL = subscription.avatarURL
        bannerURL = subscription.bannerURL
        subscriberCount = subscription.subscriberCount
        description = subscription.channelDescription
    }

    init(from recentChannel: RecentChannel) {
        name = recentChannel.name
        thumbnailURL = recentChannel.thumbnailURLString.flatMap { URL(string: $0) }
        bannerURL = nil // RecentChannel doesn't store banner
        subscriberCount = recentChannel.subscriberCount
        description = nil
    }

    /// Load cached data for a channel ID from Subscription or RecentChannel.
    @MainActor
    static func load(for channelID: String, using dataManager: DataManager) -> CachedChannelData? {
        loadFromDiskIfNeeded()
        if let subscription = dataManager.subscription(for: channelID) {
            return CachedChannelData(from: subscription)
        }
        if let recentChannel = dataManager.recentChannelEntry(forChannelID: channelID) {
            return CachedChannelData(from: recentChannel)
        }
        // Finally, check in-memory cache from video detail API responses
        return authorCache[channelID]
    }
}

// MARK: - Author Enrichment

extension Author {
    /// Returns a new Author with missing fields filled in from cached channel data.
    func enriched(from cached: CachedChannelData) -> Author {
        Author(
            id: id,
            name: name,
            thumbnailURL: thumbnailURL ?? cached.thumbnailURL,
            subscriberCount: subscriberCount ?? cached.subscriberCount,
            instance: instance,
            url: url,
            hasRealChannelInfo: hasRealChannelInfo
        )
    }

    /// Convenience: looks up cached data for this author's ID and enriches if found.
    @MainActor
    func enriched(using dataManager: DataManager) -> Author {
        guard let cached = CachedChannelData.load(for: id, using: dataManager) else {
            return self
        }
        return enriched(from: cached)
    }
}
