import Foundation
import SwiftUI

/// Servicio para manejar el almacenamiento local de capítulos y progreso de lectura.
///
/// `StorageService` centraliza todas las operaciones de persistencia de la aplicación,
/// incluyendo:
/// - Gestión de favoritos (UserDefaults)
/// - Seguimiento de progreso de lectura (UserDefaults)
/// - Metadata de capítulos descargados (JSON en disco)
/// - Almacenamiento de imágenes (FileManager)
///
/// El servicio usa UserDefaults para datos pequeños y simples, y FileManager para
/// almacenamiento de archivos binarios como imágenes.
///
/// # Example
/// ```swift
/// let storage = StorageService.shared
///
/// // Guardar favorito
/// storage.saveFavorite(mangaSlug: "one-piece")
///
/// // Guardar progreso
/// let progress = ReadingProgress(
///     mangaSlug: "one-piece",
///     chapterNumber: 1,
///     pageNumber: 15,
///     timestamp: Date()
/// )
/// storage.saveReadingProgress(progress)
///
/// // Verificar tamaño usado
/// let size = storage.getStorageSize()
/// print("Used: \(storage.formatFileSize(size))")
/// ```
class StorageService {
    // MARK: - Singleton

    /// Instancia compartida del servicio (Singleton pattern)
    static let shared = StorageService()

    // MARK: - Properties

    /// FileManager para operaciones de sistema de archivos
    private let fileManager = FileManager.default

    /// Directorio de Documents de la app
    private let documentsDirectory: URL

    /// Subdirectorio para capítulos descargados
    private let chaptersDirectory: URL

    /// URL del archivo de metadata de descargas
    private let metadataURL: URL

    /// Claves para UserDefaults
    private let favoritesKey = "favoriteMangas"
    private let readingProgressKey = "readingProgress"
    private let downloadedChaptersKey = "downloadedChaptersMetadata"

    // MARK: - Initialization

    /// Inicializador privado para implementar Singleton.
    ///
    /// Configura las rutas de directorios y crea la estructura necesaria
    /// si no existe.
    private init() {
        documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
        chaptersDirectory = documentsDirectory.appendingPathComponent("Chapters")
        metadataURL = documentsDirectory.appendingPathComponent("metadata.json")

        createDirectoriesIfNeeded()
    }

    // MARK: - Directory Management

    /// Crea el directorio de capítulos si no existe.
    private func createDirectoriesIfNeeded() {
        if !fileManager.fileExists(atPath: chaptersDirectory.path) {
            try? fileManager.createDirectory(at: chaptersDirectory, withIntermediateDirectories: true)
        }
    }

    func getChapterDirectory(mangaSlug: String, chapterNumber: Int) -> URL {
        let chapterPath = "\(mangaSlug)/Chapter\(chapterNumber)"
        return chaptersDirectory.appendingPathComponent(chapterPath)
    }

    // MARK: - Favorites

    /// Retorna la lista de slugs de mangas favoritos.
    ///
    /// - Returns: Array de strings con los slugs de mangas marcados como favoritos
    func getFavorites() -> [String] {
        UserDefaults.standard.stringArray(forKey: favoritesKey) ?? []
    }

    /// Guarda un manga como favorito.
    ///
    /// Si el manga ya está en favoritos, no hace nada (no duplica).
    ///
    /// - Parameter mangaSlug: Slug del manga a marcar como favorito
    ///
    /// # Example
    /// ```swift
    /// storage.saveFavorite(mangaSlug: "one-piece_1695365223767")
    /// ```
    func saveFavorite(mangaSlug: String) {
        var favorites = getFavorites()
        if !favorites.contains(mangaSlug) {
            favorites.append(mangaSlug)
            UserDefaults.standard.set(favorites, forKey: favoritesKey)
        }
    }

    /// Elimina un manga de favoritos.
    ///
    /// Si el manga no está en favoritos, no hace nada.
    ///
    /// - Parameter mangaSlug: Slug del manga a eliminar de favoritos
    ///
    /// # Example
    /// ```swift
    /// storage.removeFavorite(mangaSlug: "one-piece_1695365223767")
    /// ```
    func removeFavorite(mangaSlug: String) {
        var favorites = getFavorites()
        favorites.removeAll { $0 == mangaSlug }
        UserDefaults.standard.set(favorites, forKey: favoritesKey)
    }

    /// Verifica si un manga está marcado como favorito.
    ///
    /// - Parameter mangaSlug: Slug del manga a verificar
    /// - Returns: `true` si el manga está en favoritos, `false` en caso contrario
    ///
    /// # Example
    /// ```swift
    /// if storage.isFavorite(mangaSlug: "one-piece_1695365223767") {
    ///     print("Este manga es favorito")
    /// }
    /// ```
    func isFavorite(mangaSlug: String) -> Bool {
        getFavorites().contains(mangaSlug)
    }

    // MARK: - Reading Progress

    /// Guarda o actualiza el progreso de lectura de un capítulo.
    ///
    /// Si ya existe progreso para el mismo manga y capítulo, lo actualiza.
    /// Si no existe, agrega un nuevo registro.
    ///
    /// - Parameter progress: Objeto `ReadingProgress` con la información a guardar
    ///
    /// # Example
    /// ```swift
    /// let progress = ReadingProgress(
    ///     mangaSlug: "one-piece",
    ///     chapterNumber: 1,
    ///     pageNumber: 15,
    ///     timestamp: Date()
    /// )
    /// storage.saveReadingProgress(progress)
    /// ```
    func saveReadingProgress(_ progress: ReadingProgress) {
        var allProgress = getAllReadingProgress()

        // Actualizar o agregar el progreso
        if let index = allProgress.firstIndex(where: { $0.mangaSlug == progress.mangaSlug && $0.chapterNumber == progress.chapterNumber }) {
            allProgress[index] = progress
        } else {
            allProgress.append(progress)
        }

        saveProgressToDisk(allProgress)
    }

    /// Retorna el progreso de lectura de un capítulo específico.
    ///
    /// - Parameters:
    ///   - mangaSlug: Slug del manga
    ///   - chapterNumber: Número del capítulo
    /// - Returns: Objeto `ReadingProgress` si existe, `nil` en caso contrario
    ///
    /// # Example
    /// ```swift
    /// if let progress = storage.getReadingProgress(
    ///     mangaSlug: "one-piece",
    ///     chapterNumber: 1
    /// ) {
    ///     print("Última página: \(progress.pageNumber)")
    /// }
    /// ```
    func getReadingProgress(mangaSlug: String, chapterNumber: Int) -> ReadingProgress? {
        getAllReadingProgress().first { $0.mangaSlug == mangaSlug && $0.chapterNumber == chapterNumber }
    }

    /// Retorna todo el progreso de lectura almacenado.
    ///
    /// - Returns: Array de todos los objetos `ReadingProgress` almacenados
    func getAllReadingProgress() -> [ReadingProgress] {
        guard let data = UserDefaults.standard.data(forKey: readingProgressKey),
              let progress = try? JSONDecoder().decode([ReadingProgress].self, from: data) else {
            return []
        }
        return progress
    }

    /// Retorna el capítulo más recientemente leído de un manga.
    ///
    /// Busca entre todos los progresos del manga y retorna el que tiene
    /// el timestamp más reciente.
    ///
    /// - Parameter mangaSlug: Slug del manga
    /// - Returns: Objeto `ReadingProgress` más reciente, o `nil` si no hay progreso
    ///
    /// # Example
    /// ```swift
    /// if let lastRead = storage.getLastReadChapter(mangaSlug: "one-piece") {
    ///     print("Último capítulo leído: \(lastRead.chapterNumber)")
    /// }
    /// ```
    func getLastReadChapter(mangaSlug: String) -> ReadingProgress? {
        let progress = getAllReadingProgress().filter { $0.mangaSlug == mangaSlug }
        return progress.max { $0.timestamp < $1.timestamp }
    }

    /// Guarda el array de progresos en UserDefaults.
    ///
    /// - Parameter progress: Array de `ReadingProgress` a guardar
    private func saveProgressToDisk(_ progress: [ReadingProgress]) {
        if let data = try? JSONEncoder().encode(progress) {
            UserDefaults.standard.set(data, forKey: readingProgressKey)
        }
    }

    // MARK: - Downloaded Chapters

    /// Guarda la metadata de un capítulo descargado.
    ///
    /// Si el capítulo ya existe en la metadata, lo actualiza.
    /// Si no existe, agrega un nuevo registro.
    ///
    /// - Parameter chapter: Objeto `DownloadedChapter` con la metadata
    ///
    /// # Example
    /// ```swift
    /// let downloaded = DownloadedChapter(
    ///     mangaSlug: "one-piece",
    ///     mangaTitle: "One Piece",
    ///     chapterNumber: 1,
    ///     pages: pages,
    ///     downloadedAt: Date()
    /// )
    /// storage.saveDownloadedChapter(downloaded)
    /// ```
    func saveDownloadedChapter(_ chapter: DownloadedChapter) {
        var downloaded = getDownloadedChapters()

        if let index = downloaded.firstIndex(where: { $0.id == chapter.id }) {
            downloaded[index] = chapter
        } else {
            downloaded.append(chapter)
        }

        if let data = try? JSONEncoder().encode(downloaded) {
            try? data.write(to: metadataURL)
        }
    }

    /// Retorna todos los capítulos descargados.
    ///
    /// - Returns: Array de objetos `DownloadedChapter`
    ///
    /// # Example
    /// ```swift
    /// let downloads = storage.getDownloadedChapters()
    /// print("Tienes \(downloads.count) capítulos descargados")
    /// for chapter in downloads {
    ///     print("- \(chapter.displayTitle)")
    /// }
    /// ```
    func getDownloadedChapters() -> [DownloadedChapter] {
        guard let data = try? Data(contentsOf: metadataURL),
              let downloaded = try? JSONDecoder().decode([DownloadedChapter].self, from: data) else {
            return []
        }
        return downloaded
    }

    /// Retorna un capítulo descargado específico.
    ///
    /// - Parameters:
    ///   - mangaSlug: Slug del manga
    ///   - chapterNumber: Número del capítulo
    /// - Returns: Objeto `DownloadedChapter` si existe, `nil` en caso contrario
    func getDownloadedChapter(mangaSlug: String, chapterNumber: Int) -> DownloadedChapter? {
        getDownloadedChapters().first { $0.mangaSlug == mangaSlug && $0.chapterNumber == chapterNumber }
    }

    /// Verifica si un capítulo está descargado.
    ///
    /// - Parameters:
    ///   - mangaSlug: Slug del manga
    ///   - chapterNumber: Número del capítulo
    /// - Returns: `true` si el capítulo está descargado, `false` en caso contrario
    ///
    /// # Example
    /// ```swift
    /// if storage.isChapterDownloaded(
    ///     mangaSlug: "one-piece",
    ///     chapterNumber: 1
    /// ) {
    ///     print("Capítulo ya descargado")
    /// }
    /// ```
    func isChapterDownloaded(mangaSlug: String, chapterNumber: Int) -> Bool {
        getDownloadedChapter(mangaSlug: mangaSlug, chapterNumber: chapterNumber) != nil
    }

    /// Elimina un capítulo descargado (archivos y metadata).
    ///
    /// Elimina todos los archivos de imagen del capítulo del disco
    /// y remueve la metadata del registro de descargas.
    ///
    /// - Parameters:
    ///   - mangaSlug: Slug del manga
    ///   - chapterNumber: Número del capítulo a eliminar
    ///
    /// # Example
    /// ```swift
    /// storage.deleteDownloadedChapter(
    ///     mangaSlug: "one-piece",
    ///     chapterNumber: 1
    /// )
    /// ```
    func deleteDownloadedChapter(mangaSlug: String, chapterNumber: Int) {
        // Eliminar archivos
        let chapterDir = getChapterDirectory(mangaSlug: mangaSlug, chapterNumber: chapterNumber)
        try? fileManager.removeItem(at: chapterDir)

        // Eliminar metadata
        var downloaded = getDownloadedChapters()
        downloaded.removeAll { $0.mangaSlug == mangaSlug && $0.chapterNumber == chapterNumber }

        if let data = try? JSONEncoder().encode(downloaded) {
            try? data.write(to: metadataURL)
        }
    }

    // MARK: - Image Caching

    /// Guarda una imagen en disco local.
    ///
    /// Comprime la imagen como JPEG con 80% de calidad y la guarda
    /// en el directorio del capítulo correspondiente.
    ///
    /// - Parameters:
    ///   - image: Imagen (UIImage) a guardar
    ///   - mangaSlug: Slug del manga
    ///   - chapterNumber: Número del capítulo
    ///   - pageIndex: Índice de la página (para nombre de archivo)
    /// - Returns: URL del archivo guardado
    /// - Throws: Error si no se puede crear el directorio o guardar la imagen
    ///
    /// # Example
    /// ```swift
    /// do {
    ///     let imageURL = try await storage.saveImage(
    ///         image: myUIImage,
    ///         mangaSlug: "one-piece",
    ///         chapterNumber: 1,
    ///         pageIndex: 0
    ///     )
    ///     print("Imagen guardada en: \(imageURL.path)")
    /// } catch {
    ///     print("Error guardando imagen: \(error)")
    /// }
    /// ```
    func saveImage(_ image: UIImage, mangaSlug: String, chapterNumber: Int, pageIndex: Int) async throws -> URL {
        let chapterDir = getChapterDirectory(mangaSlug: mangaSlug, chapterNumber: chapterNumber)

        // Crear directorio si no existe
        if !fileManager.fileExists(atPath: chapterDir.path) {
            try fileManager.createDirectory(at: chapterDir, withIntermediateDirectories: true)
        }

        let filename = "page_\(pageIndex).jpg"
        let fileURL = chapterDir.appendingPathComponent(filename)

        // Guardar imagen
        if let data = image.jpegData(compressionQuality: 0.8) {
            try data.write(to: fileURL)
            return fileURL
        }

        throw NSError(domain: "StorageService", code: 1, userInfo: [NSLocalizedDescriptionKey: "Error saving image"])
    }

    /// Carga una imagen desde disco local.
    ///
    /// - Parameters:
    ///   - mangaSlug: Slug del manga
    ///   - chapterNumber: Número del capítulo
    ///   - pageIndex: Índice de la página
    /// - Returns: Objeto `UIImage` si el archivo existe, `nil` en caso contrario
    ///
    /// # Example
    /// ```swift
    /// if let image = storage.loadImage(
    ///     mangaSlug: "one-piece",
    ///     chapterNumber: 1,
    ///     pageIndex: 0
    /// ) {
    ///     print("Imagen cargada: \(image.size)")
    /// }
    /// ```
    func loadImage(mangaSlug: String, chapterNumber: Int, pageIndex: Int) -> UIImage? {
        let chapterDir = getChapterDirectory(mangaSlug: mangaSlug, chapterNumber: chapterNumber)
        let filename = "page_\(pageIndex).jpg"
        let fileURL = chapterDir.appendingPathComponent(filename)

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

        return UIImage(contentsOfFile: fileURL.path)
    }

    /// Retorna la URL local de una imagen si está cacheada.
    ///
    /// - Parameters:
    ///   - mangaSlug: Slug del manga
    ///   - chapterNumber: Número del capítulo
    ///   - pageIndex: Índice de la página
    /// - Returns: URL del archivo si existe, `nil` en caso contrario
    ///
    /// # Example
    /// ```swift
    /// if let imageURL = storage.getImageURL(
    ///     mangaSlug: "one-piece",
    ///     chapterNumber: 1,
    ///     pageIndex: 0
    /// ) {
    ///     print("Imagen cacheada en: \(imageURL.path)")
    /// }
    /// ```
    func getImageURL(mangaSlug: String, chapterNumber: Int, pageIndex: Int) -> URL? {
        let chapterDir = getChapterDirectory(mangaSlug: mangaSlug, chapterNumber: chapterNumber)
        let filename = "page_\(pageIndex).jpg"
        let fileURL = chapterDir.appendingPathComponent(filename)

        return fileManager.fileExists(atPath: fileURL.path) ? fileURL : nil
    }

    // MARK: - Storage Management

    /// Calcula el tamaño total usado por los capítulos descargados.
    ///
    /// Recursivamente suma el tamaño de todos los archivos en el
    /// directorio de capítulos.
    ///
    /// - Returns: Tamaño total en bytes
    ///
    /// # Example
    /// ```swift
    /// let bytes = storage.getStorageSize()
    /// let formatted = storage.formatFileSize(bytes)
    /// print("Usando \(formatted) de espacio")
    /// ```
    func getStorageSize() -> Int64 {
        var totalSize: Int64 = 0

        if let enumerator = fileManager.enumerator(at: chaptersDirectory, includingPropertiesForKeys: [.fileSizeKey]) {
            for case let fileURL as URL in enumerator {
                if let resourceValues = try? fileURL.resourceValues(forKeys: [.fileSizeKey]),
                   let fileSize = resourceValues.fileSize {
                    totalSize += Int64(fileSize)
                }
            }
        }

        return totalSize
    }

    /// Elimina todos los capítulos descargados y su metadata.
    ///
    /// Elimina completamente el directorio de capítulos y el archivo
    /// de metadata, liberando todo el espacio usado.
    ///
    /// # Example
    /// ```swift
    /// storage.clearAllDownloads()
    /// print("Todos los descargados han sido eliminados")
    /// ```
    func clearAllDownloads() {
        try? fileManager.removeItem(at: chaptersDirectory)
        createDirectoriesIfNeeded()

        // Limpiar metadata
        try? fileManager.removeItem(at: metadataURL)
    }

    /// Formatea un tamaño en bytes a un string legible.
    ///
    /// Usa `ByteCountFormatter` para convertir bytes a KB, MB, GB según
    /// corresponda, con el formato apropiado para archivos.
    ///
    /// - Parameter bytes: Tamaño en bytes
    /// - Returns: String formateado (ej: "15.2 MB", "1.3 GB")
    ///
    /// # Example
    /// ```swift
    /// print(storage.formatFileSize(1024)) // "1 KB"
    /// print(storage.formatFileSize(15728640)) // "15 MB"
    /// print(storage.formatFileSize(2147483648)) // "2 GB"
    /// ```
    func formatFileSize(_ bytes: Int64) -> String {
        let formatter = ByteCountFormatter()
        formatter.allowedUnits = [.useBytes, .useKB, .useMB, .useGB]
        formatter.countStyle = .file
        return formatter.string(fromByteCount: bytes)
    }
}
