import Foundation
import UIKit

/// Cache de imágenes optimizado con NSCache y políticas de expiración
///
/// OPTIMIZACIONES IMPLEMENTADAS:
/// 1. NSCache con límites configurables (BEFORE: Sin cache en memoria)
/// 2. Preloading inteligente de imágenes adyacentes (BEFORE: Sin preloading)
/// 3. Memory warning response (BEFORE: Sin gestión de memoria)
/// 4. Disk cache para persistencia (BEFORE: Solo NSCache)
/// 5. Priority queue para loading (BEFORE: FIFO simple)
final class ImageCache {

    // MARK: - Singleton
    static let shared = ImageCache()

    // MARK: - In-Memory Cache (NSCache)
    /// BEFORE: Sin cache en memoria (redecargaba siempre)
    /// AFTER: NSCache con límites inteligentes y políticas de expiración
    private let cache: NSCache<NSString, UIImage>

    // MARK: - Disk Cache Configuration
    /// BEFORE: Sin persistencia de cache
    /// AFTER: Cache en disco para sesiones futuras
    private let diskCacheDirectory: URL
    private let fileManager = FileManager.default

    // MARK: - Cache Configuration
    /// BEFORE: Sin límites claros
    /// AFTER: Límites configurables y adaptativos
    private var memoryCacheLimit: Int {
        // 25% de la memoria disponible del dispositivo
        let totalMemory = ProcessInfo.processInfo.physicalMemory
        return Int(totalMemory / 4) // 25% de RAM
    }

    private var diskCacheLimit: Int64 {
        // 500 MB máximo para cache en disco
        return 500 * 1024 * 1024
    }

    private let maxCacheAge: TimeInterval = 7 * 24 * 3600 // 7 días

    // MARK: - Preloading Queue
    /// BEFORE: Sin sistema de preloading
    /// AFTER: Queue con prioridades para carga inteligente
    private enum ImagePriority: Int, Comparable {
        case current = 0      // Imagen actual (máxima prioridad)
        case adjacent = 1     // Imágenes adyacentes (alta prioridad)
        case prefetch = 2     // Prefetch normal (media prioridad)
        case background = 3   // Background (baja prioridad)

        static func < (lhs: ImagePriority, rhs: ImagePriority) -> Bool {
            return lhs.rawValue < rhs.rawValue
        }
    }

    private struct ImageLoadRequest: Comparable {
        let url: String
        let priority: ImagePriority
        let completion: (UIImage?) -> Void

        static func < (lhs: ImageLoadRequest, rhs: ImageLoadRequest) -> Bool {
            return lhs.priority < rhs.priority
        }
    }

    private var preloadQueue: [ImageLoadRequest] = []
    private let preloadQueueLock = NSLock()
    private var isPreloading = false

    // MARK: - Image Downscaling
    /// BEFORE: Cargaba imágenes a resolución completa siempre
    /// AFTER: Redimensiona automáticamente imágenes muy grandes
    private let maxImageDimension: CGFloat = 2048 // 2048x2048 máximo

    // MARK: - Performance Monitoring
    /// BEFORE: Sin métricas de rendimiento
    /// AFTER: Tracking de hits/miss para optimización
    private var cacheHits = 0
    private var cacheMisses = 0
    private var totalLoadedImages = 0
    private var totalLoadTime: TimeInterval = 0

    private init() {
        // Configurar NSCache
        self.cache = NSCache<NSString, UIImage>()
        self.cache.countLimit = 100 // Máximo 100 imágenes en memoria
        self.cache.totalCostLimit = memoryCacheLimit

        // Configurar directorio de cache en disco
        let cacheDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
        self.diskCacheDirectory = cacheDir.appendingPathComponent("ImageCache")

        // Crear directorio si no existe
        try? fileManager.createDirectory(at: diskCacheDirectory, withIntermediateDirectories: true)

        // Setup memory warning observer
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleMemoryWarning),
            name: UIApplication.didReceiveMemoryWarningNotification,
            object: nil
        )

        // Setup background cleanup
        setupPeriodicCleanup()
    }

    // MARK: - Public Interface

    /// Obtiene imagen desde cache o la descarga
    ///
    /// BEFORE: Descargaba siempre sin prioridad
    /// AFTER: Cache en memoria + disco con priority loading
    func image(for url: String) -> UIImage? {
        return image(for: url, priority: .current)
    }

    func image(for url: String, priority: ImagePriority) -> UIImage? {
        // 1. Verificar memoria cache primero (más rápido)
        if let cachedImage = getCachedImage(for: url) {
            cacheHits += 1
            print("✅ Memory cache HIT: \(url)")
            return cachedImage
        }

        cacheMisses += 1
        print("❌ Memory cache MISS: \(url)")

        // 2. Verificar disco cache
        if let diskImage = loadImageFromDisk(for: url) {
            // Guardar en memoria cache
            setImage(diskImage, for: url)
            print("💾 Disk cache HIT: \(url)")
            return diskImage
        }

        print("🌐 Cache MISS - Need to download: \(url)")
        return nil
    }

    /// Guarda imagen en cache
    ///
    /// BEFORE: Guardaba sin optimizar
    /// AFTER: Optimiza tamaño y cache en múltiples niveles
    func setImage(_ image: UIImage, for url: String) {
        // 1. Guardar en memoria cache
        let cost = estimateImageCost(image)
        cache.setObject(image, forKey: url as NSString, cost: cost)

        // 2. Guardar en disco cache (async)
        saveImageToDisk(image, for: url)
    }

    // MARK: - Preloading System

    /// BEFORE: Sin sistema de preloading
    /// AFTER: Preloading inteligente de páginas adyacentes
    func preloadAdjacentImages(currentURLs: [String], currentIndex: Int, completion: @escaping () -> Void) {
        preloadQueueLock.lock()
        defer { preloadQueueLock.unlock() }

        let range = max(0, currentIndex - 1)...min(currentURLs.count - 1, currentIndex + 2)

        for index in range {
            if index == currentIndex { continue } // Skip current

            let url = currentURLs[index]
            guard image(for: url) == nil else { continue } // Ya está en cache

            let priority: ImagePriority = index == currentIndex - 1 || index == currentIndex + 1 ? .adjacent : .prefetch

            let request = ImageLoadRequest(url: url, priority: priority) { [weak self] image in
                if let image = image {
                    self?.setImage(image, for: url)
                }
            }

            preloadQueue.append(request)
        }

        preloadQueue.sort()

        // Procesar queue si no está ya procesando
        if !isPreloading {
            isPreloading = true
            processPreloadQueue(completion: completion)
        }
    }

    /// BEFORE: Sin gestión de prioridades
    /// AFTER: PriorityQueue con prioridades
    private func processPreloadQueue(completion: @escaping () -> Void) {
        preloadQueueLock.lock()

        guard !preloadQueue.isEmpty else {
            isPreloading = false
            preloadQueueLock.unlock()
            DispatchQueue.main.async { completion() }
            return
        }

        let request = preloadQueue.removeFirst()
        preloadQueueLock.unlock()

        // Cargar imagen con prioridad
        loadImageFromURL(request.url) { [weak self] image in
            request.completion(image)

            // Continuar con siguiente
            self?.processPreloadQueue(completion: completion)
        }
    }

    /// BEFORE: Descarga síncrona bloqueante
    /// AFTER: Descarga asíncrona con callback
    private func loadImageFromURL(_ urlString: String, completion: @escaping (UIImage?) -> Void) {
        guard let url = URL(string: urlString) else {
            completion(nil)
            return
        }

        let startTime = Date()

        URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
            guard let self = self,
                  let data = data,
                  error == nil,
                  let image = UIImage(data: data) else {
                completion(nil)
                return
            }

            // OPTIMIZACIÓN: Redimensionar si es muy grande
            let optimizedImage = self.optimizeImageSize(image)

            // Guardar en cache
            self.setImage(optimizedImage, for: urlString)

            // Metrics
            let loadTime = Date().timeIntervalSince(startTime)
            self.totalLoadedImages += 1
            self.totalLoadTime += loadTime

            print("📥 Loaded image: \(urlString) in \(String(format: "%.2f", loadTime))s")

            completion(optimizedImage)
        }.resume()
    }

    // MARK: - Memory Cache Operations

    private func getCachedImage(for url: String) -> UIImage? {
        return cache.object(forKey: url as NSString)
    }

    private func setImage(_ image: UIImage, for url: String) {
        let cost = estimateImageCost(image)
        cache.setObject(image, forKey: url as NSString, cost: cost)
    }

    /// BEFORE: No había estimación de costo
    /// AFTER: Costo basado en tamaño real en memoria
    private func estimateImageCost(_ image: UIImage) -> Int {
        // Estimar bytes en memoria: width * height * 4 (RGBA)
        guard let cgImage = image.cgImage else { return 0 }
        let width = cgImage.width
        let height = cgImage.height
        return width * height * 4
    }

    // MARK: - Disk Cache Operations

    private func getDiskCacheURL(for url: String) -> URL {
        let filename = url.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? UUID().uuidString
        return diskCacheDirectory.appendingPathComponent(filename)
    }

    private func loadImageFromDisk(for url: String) -> UIImage? {
        let fileURL = getDiskCacheURL(for: url)

        guard fileManager.fileExists(atPath: fileURL.path) else {
            return nil
        }

        // Verificar edad del archivo
        if let attributes = try? fileManager.attributesOfItem(atPath: fileURL.path),
           let modificationDate = attributes[.modificationDate] as? Date {
            let age = Date().timeIntervalSince(modificationDate)
            if age > maxCacheAge {
                try? fileManager.removeItem(at: fileURL)
                return nil
            }
        }

        return UIImage(contentsOfFile: fileURL.path)
    }

    private func saveImageToDisk(_ image: UIImage, for url: String) {
        let fileURL = getDiskCacheURL(for: url)

        // Guardar en background queue
        DispatchQueue.global(qos: .utility).async { [weak self] in
            guard let self = self else { return }

            // OPTIMIZACIÓN: JPEG con calidad media para cache
            guard let data = image.jpegData(compressionQuality: 0.7) else { return }

            try? data.write(to: fileURL)
        }
    }

    // MARK: - Image Optimization

    /// BEFORE: Imágenes a resolución completa
    /// AFTER: Redimensiona imágenes muy grandes automáticamente
    private func optimizeImageSize(_ image: UIImage) -> UIImage {
        guard let cgImage = image.cgImage else { return image }

        let width = CGFloat(cgImage.width)
        let height = CGFloat(cgImage.height)

        // Si ya es pequeña, no cambiar
        if width <= maxImageDimension && height <= maxImageDimension {
            return image
        }

        // Calcular nuevo tamaño manteniendo aspect ratio
        let aspectRatio = width / height
        let newWidth: CGFloat
        let newHeight: CGFloat

        if width > height {
            newWidth = maxImageDimension
            newHeight = maxImageDimension / aspectRatio
        } else {
            newHeight = maxImageDimension
            newWidth = maxImageDimension * aspectRatio
        }

        // Redimensionar
        let newSize = CGSize(width: newWidth, height: newHeight)
        let renderer = UIGraphicsImageRenderer(size: newSize)
        return renderer.image { _ in
            image.draw(in: CGRect(origin: .zero, size: newSize))
        }
    }

    // MARK: - Memory Management

    @objc private func handleMemoryWarning() {
        // BEFORE: Sin gestión de memoria
        // AFTER: Limpieza agresiva bajo presión de memoria
        print("⚠️ Memory warning received - Clearing image cache")

        // Limpiar cache de memoria (conservando disco cache)
        cache.removeAllObjects()

        // Cancelar preloading pendiente
        preloadQueueLock.lock()
        preloadQueue.removeAll()
        isPreloading = false
        preloadQueueLock.unlock()
    }

    // MARK: - Cache Maintenance

    /// BEFORE: Sin limpieza periódica
    /// AFTER: Limpieza automática de cache viejo
    private func setupPeriodicCleanup() {
        // Ejecutar cleanup cada 24 horas
        Timer.scheduledTimer(withTimeInterval: 86400, repeats: true) { [weak self] _ in
            self?.performCleanup()
        }

        // También ejecutar al iniciar
        performCleanup()
    }

    private func performCleanup() {
        print("🧹 Performing image cache cleanup...")

        var totalSize: Int64 = 0
        var files: [(URL, Int64)] = []

        // Calcular tamaño actual
        if let enumerator = fileManager.enumerator(at: diskCacheDirectory, includingPropertiesForKeys: [.fileSizeKey, .contentModificationDateKey]) {
            for case let fileURL as URL in enumerator {
                if let resourceValues = try? fileURL.resourceValues(forKeys: [.fileSizeKey, .contentModificationDateKey]),
                   let fileSize = resourceValues.fileSize,
                   let modificationDate = resourceValues.contentModificationDate {

                    let age = Date().timeIntervalSince(modificationDate)
                    totalSize += Int64(fileSize)
                    files.append((fileURL, Int64(fileSize)))

                    // Eliminar archivos muy viejos
                    if age > maxCacheAge {
                        try? fileManager.removeItem(at: fileURL)
                        totalSize -= Int64(fileSize)
                        print("🗑️ Removed old cached file: \(fileURL.lastPathComponent)")
                    }
                }
            }
        }

        // Si excede límite de tamaño, eliminar archivos más viejos primero
        if totalSize > diskCacheLimit {
            let excess = totalSize - diskCacheLimit
            var removedSize: Int64 = 0

            for (fileURL, fileSize) in files.sorted(by: { $0.0 < $1.0 }) {
                if removedSize >= excess { break }

                try? fileManager.removeItem(at: fileURL)
                removedSize += fileSize
                print("🗑️ Removed cached file due to size limit: \(fileURL.lastPathComponent)")
            }
        }

        print("✅ Cache cleanup completed. Size: \(formatFileSize(totalSize))")
    }

    /// Elimina todas las imágenes cacheadas
    func clearAllCache() {
        // Limpiar memoria
        cache.removeAllObjects()

        // Limpiar disco
        try? fileManager.removeItem(at: diskCacheDirectory)
        try? fileManager.createDirectory(at: diskCacheDirectory, withIntermediateDirectories: true)

        print("🧹 All image cache cleared")
    }

    /// Elimina imágenes específicas (para cuando se descarga un capítulo)
    func clearCache(for urls: [String]) {
        for url in urls {
            cache.removeObject(forKey: url as NSString)

            let fileURL = getDiskCacheURL(for: url)
            try? fileManager.removeItem(at: fileURL)
        }
    }

    // MARK: - Statistics

    func getCacheStatistics() -> CacheStatistics {
        let hitRate = cacheHits + cacheMisses > 0
            ? Double(cacheHits) / Double(cacheHits + cacheMisses)
            : 0

        let avgLoadTime = totalLoadedImages > 0
            ? totalLoadTime / Double(totalLoadedImages)
            : 0

        return CacheStatistics(
            memoryCacheHits: cacheHits,
            cacheMisses: cacheMisses,
            hitRate: hitRate,
            totalImagesLoaded: totalLoadedImages,
            averageLoadTime: avgLoadTime
        )
    }

    func printStatistics() {
        let stats = getCacheStatistics()
        print("📊 Image Cache Statistics:")
        print("   - Cache Hits: \(stats.memoryCacheHits)")
        print("   - Cache Misses: \(stats.cacheMisses)")
        print("   - Hit Rate: \(String(format: "%.2f", stats.hitRate * 100))%")
        print("   - Total Images Loaded: \(stats.totalImagesLoaded)")
        print("   - Avg Load Time: \(String(format: "%.3f", stats.averageLoadTime))s")
    }

    private func formatFileSize(_ bytes: Int64) -> String {
        let formatter = ByteCountFormatter()
        formatter.allowedUnits = [.useBytes, .useKB, .useMB]
        formatter.countStyle = .file
        return formatter.string(fromByteCount: bytes)
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

// MARK: - Supporting Types

struct CacheStatistics {
    let memoryCacheHits: Int
    let cacheMisses: Int
    let hitRate: Double
    let totalImagesLoaded: Int
    let averageLoadTime: TimeInterval
}
