import Foundation
import os

/// Кэш иконок монет: память → диск → CDN.
///
/// Диск — контейнер App Group, поэтому кэш общий для приложения и виджета:
/// иконка скачивается один раз, а не при каждом обновлении таймлайна.
actor IconStore {
    static let shared = IconStore()

    /// Иконки монет практически не меняются — неделя жизни файла достаточна.
    private let maxAge: TimeInterval
    private let directory: URL?
    private var memory: [String: Data] = [:]

    private static let logger = Logger(subsystem: AppGroup.id, category: "icon-store")

    init(maxAge: TimeInterval = 7 * 24 * 60 * 60) {
        self.maxAge = maxAge
        self.directory = Self.makeDirectory()
    }

    // MARK: - Публичное API

    /// Иконка валюты; nil — если её нет ни в кэше, ни на CDN.
    func data(for currency: String) async -> Data? {
        if let data = memory[currency] {
            return data
        }

        let file = fileURL(for: currency)
        if let file, let data = freshData(at: file) {
            memory[currency] = data
            return data
        }

        do {
            let data = try await RapiraAPI.shared.iconData(for: currency)
            memory[currency] = data
            if let file {
                try? data.write(to: file, options: .atomic)
            }
            return data
        } catch {
            Self.logger.notice(
                "Иконка \(currency, privacy: .public) недоступна: \(String(describing: error), privacy: .public)"
            )
            // Просроченный файл лучше пустого кружка.
            if let file, let stale = try? Data(contentsOf: file) {
                memory[currency] = stale
                return stale
            }
            return nil
        }
    }

    /// Пакетная загрузка для провайдера виджета.
    func icons(for currencies: some Sequence<String>) async -> [String: Data] {
        await withTaskGroup(of: (String, Data?).self) { group in
            for currency in Set(currencies) {
                group.addTask { (currency, await self.data(for: currency)) }
            }

            var result: [String: Data] = [:]
            for await (currency, data) in group {
                if let data {
                    result[currency] = data
                }
            }
            return result
        }
    }

    // MARK: - Диск

    private func fileURL(for currency: String) -> URL? {
        directory?.appendingPathComponent("\(currency).svg")
    }

    /// Данные файла, если он ещё не устарел.
    private func freshData(at url: URL) -> Data? {
        guard
            let attributes = try? FileManager.default.attributesOfItem(atPath: url.path),
            let modified = attributes[.modificationDate] as? Date,
            Date.now.timeIntervalSince(modified) < maxAge
        else { return nil }
        return try? Data(contentsOf: url)
    }

    private static func makeDirectory() -> URL? {
        let base = AppGroup.cachesURL
            ?? FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first
        guard let base else { return nil }

        let directory = base.appendingPathComponent("coin-icons", isDirectory: true)
        do {
            try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
            return directory
        } catch {
            logger.error("Не удалось создать кэш иконок: \(String(describing: error), privacy: .public)")
            return nil
        }
    }
}
