import Foundation
import UIKit
import Combine

// MARK: - Async Semaphore

/// A semaphore for limiting concurrent async operations
actor AsyncSemaphore {
    private var value: Int
    private var waiters: [CheckedContinuation<Void, Never>] = []

    init(value: Int) {
        self.value = value
    }

    func wait() async {
        if value > 0 {
            value -= 1
        } else {
            await withCheckedContinuation { continuation in
                waiters.append(continuation)
            }
        }
    }

    func signal() {
        if let waiter = waiters.first {
            waiters.removeFirst()
            waiter.resume()
        } else {
            value += 1
        }
    }
}

// MARK: - Thread-safe counter for async contexts

/// A thread-safe counter using an actor
actor Counter {
    private var value: Int = 0

    func increment() -> Int {
        value += 1
        return value
    }

    func get() -> Int {
        return value
    }

    func reset() {
        value = 0
    }
}

/// Estado de una descarga
enum DownloadState: Equatable {
    case pending
    case downloading(progress: Double)
    case completed
    case failed(error: String)
    case cancelled

    var isDownloading: Bool {
        if case .downloading = self { return true }
        return false
    }

    var isCompleted: Bool {
        if case .completed = self { return true }
        return false
    }

    var isTerminal: Bool {
        switch self {
        case .completed, .failed, .cancelled:
            return true
        default:
            return false
        }
    }
}

/// Información de progreso de descarga
struct DownloadProgress {
    let chapterId: String
    let downloadedPages: Int
    let totalPages: Int
    let currentProgress: Double
    let state: DownloadState

    var progressFraction: Double {
        return Double(downloadedPages) / Double(max(totalPages, 1))
    }
}

/// Tarea de descarga individual
class DownloadTask: ObservableObject, Identifiable {
    let id: String
    let mangaSlug: String
    let mangaTitle: String
    let chapterNumber: Int
    let chapterTitle: String
    let imageURLs: [String]

    @Published var state: DownloadState = .pending
    @Published var downloadedPages: Int = 0
    @Published var error: String?

    internal var cancellationToken: CancellationChecker = CancellationChecker()

    var progress: Double {
        return Double(downloadedPages) / Double(max(imageURLs.count, 1))
    }

    var isCancelled: Bool {
        cancellationToken.isCancelled
    }

    init(mangaSlug: String, mangaTitle: String, chapterNumber: Int, chapterTitle: String, imageURLs: [String]) {
        self.id = "\(mangaSlug)-\(chapterNumber)"
        self.mangaSlug = mangaSlug
        self.mangaTitle = mangaTitle
        self.chapterNumber = chapterNumber
        self.chapterTitle = chapterTitle
        self.imageURLs = imageURLs
    }

    func cancel() {
        cancellationToken.cancel()
        state = .cancelled
    }

    func updateProgress(downloaded: Int, total: Int) {
        downloadedPages = downloaded
        state = .downloading(progress: Double(downloaded) / Double(max(total, 1)))
    }

    func complete() {
        state = .completed
    }

    func fail(_ error: String) {
        self.error = error
        state = .failed(error: error)
    }
}

/// Checker para cancelación asíncrona
final class CancellationChecker: @unchecked Sendable {
    private var _isCancelled = false
    private let lock = NSLock()

    var isCancelled: Bool {
        lock.lock()
        defer { lock.unlock() }
        return _isCancelled
    }

    func cancel() {
        lock.lock()
        defer { lock.unlock() }
        _isCancelled = true
    }
}

/// Gerente de descargas de capítulos
@MainActor
class DownloadManager: ObservableObject {
    static let shared = DownloadManager()

    // MARK: - Published Properties

    @Published var activeDownloads: [DownloadTask] = []
    @Published var completedDownloads: [DownloadTask] = []
    @Published var failedDownloads: [DownloadTask] = []
    @Published var totalProgress: Double = 0.0

    // MARK: - Dependencies

    private let storage = StorageService.shared
    private let scraper = ManhwaWebScraper.shared
    private var downloadCancellations: [String: CancellationChecker] = [:]

    // MARK: - Configuration

    private let maxConcurrentDownloads = 3
    private let maxConcurrentImagesPerChapter = 5

    private init() {}

    // MARK: - Public Methods

    /// Descarga un capítulo completo
    func downloadChapter(mangaSlug: String, mangaTitle: String, chapter: Chapter) async throws {
        // Verificar si ya está descargado
        if storage.isChapterDownloaded(mangaSlug: mangaSlug, chapterNumber: chapter.number) {
            throw DownloadError.alreadyDownloaded
        }

        // Obtener URLs de imágenes
        let imageURLs = try await scraper.scrapeChapterImages(chapterSlug: chapter.slug)

        guard !imageURLs.isEmpty else {
            throw DownloadError.noImagesFound
        }

        // Crear tarea de descarga
        let task = DownloadTask(
            mangaSlug: mangaSlug,
            mangaTitle: mangaTitle,
            chapterNumber: chapter.number,
            chapterTitle: chapter.title,
            imageURLs: imageURLs
        )

        activeDownloads.append(task)
        downloadCancellations[task.id] = task.cancellationToken

        do {
            // Descargar imágenes con concurrencia limitada
            try await downloadImages(for: task)

            // Guardar metadata del capítulo descargado
            let pages = imageURLs.enumerated().map { index, url in
                MangaPage(url: url, index: index)
            }

            let downloadedChapter = DownloadedChapter(
                mangaSlug: mangaSlug,
                mangaTitle: mangaTitle,
                chapterNumber: chapter.number,
                pages: pages,
                downloadedAt: Date(),
                totalSize: 0 // Se calcula después
            )

            storage.saveDownloadedChapter(downloadedChapter)

            // Mover a completados
            task.complete()
            moveTaskToCompleted(task)

        } catch {
            task.fail(error.localizedDescription)
            moveTaskToFailed(task)
            throw error
        }
    }

    /// Descarga múltiples capítulos en paralelo
    func downloadChapters(mangaSlug: String, mangaTitle: String, chapters: [Chapter]) async {
        let limitedChapters = Array(chapters.prefix(maxConcurrentDownloads))

        await withTaskGroup(of: Void.self) { group in
            for chapter in limitedChapters {
                group.addTask {
                    do {
                        try await self.downloadChapter(
                            mangaSlug: mangaSlug,
                            mangaTitle: mangaTitle,
                            chapter: chapter
                        )
                    } catch {
                        print("Error downloading chapter \(chapter.number): \(error.localizedDescription)")
                    }
                }
            }
        }
    }

    /// Cancela una descarga activa
    func cancelDownload(taskId: String) {
        guard let index = activeDownloads.firstIndex(where: { $0.id == taskId }),
              let canceller = downloadCancellations[taskId] else {
            return
        }

        let task = activeDownloads[index]
        task.cancel()
        canceller.cancel()

        // Remover de activos
        activeDownloads.remove(at: index)
        downloadCancellations.removeValue(forKey: taskId)

        // Limpiar archivos parciales
        Task {
            _ = try? await storage.deleteDownloadedChapter(
                mangaSlug: task.mangaSlug,
                chapterNumber: task.chapterNumber
            )
        }
    }

    /// Cancela todas las descargas activas
    func cancelAllDownloads() {
        let tasks = activeDownloads
        for task in tasks {
            cancelDownload(taskId: task.id)
        }
    }

    /// Limpia el historial de descargas completadas
    func clearCompletedHistory() {
        completedDownloads.removeAll()
    }

    /// Limpia el historial de descargas fallidas
    func clearFailedHistory() {
        failedDownloads.removeAll()
    }

    /// Reintenta una descarga fallida
    func retryDownload(task: DownloadTask, chapter: Chapter) async throws {
        // Remover de fallidos
        failedDownloads.removeAll { $0.id == task.id }

        // Reiniciar descarga
        try await downloadChapter(
            mangaSlug: task.mangaSlug,
            mangaTitle: task.mangaTitle,
            chapter: chapter
        )
    }

    /// Obtiene el progreso general de descargas
    func updateTotalProgress() {
        guard !activeDownloads.isEmpty else {
            totalProgress = 0.0
            return
        }

        let totalProgress = activeDownloads.reduce(0.0) { sum, task in
            return sum + task.progress
        }

        self.totalProgress = totalProgress / Double(activeDownloads.count)
    }

    // MARK: - Private Methods

    private func downloadImages(for task: DownloadTask) async throws {
        let imageURLs = task.imageURLs
        let totalImages = imageURLs.count

        // Usar concurrencia limitada para no saturar la red
        let semaphore = AsyncSemaphore(value: maxConcurrentImagesPerChapter)
        let counter = Counter()

        try await withThrowingTaskGroup(of: (Int, UIImage).self) { group in
            for (index, imageURL) in imageURLs.enumerated() {
                // Verificar cancelación
                if task.isCancelled {
                    group.cancelAll()
                    throw DownloadError.cancelled
                }

                await semaphore.wait()

                group.addTask {
                    let image = try await self.downloadImage(from: imageURL)
                    await semaphore.signal()
                    return (index, image)
                }
            }

            // Procesar imágenes completadas
            for try await (index, image) in group {
                let currentCount = await counter.increment()

                // Guardar imagen
                _ = try? await storage.saveImage(
                    image,
                    mangaSlug: task.mangaSlug,
                    chapterNumber: task.chapterNumber,
                    pageIndex: index
                )

                // Actualizar progreso
                await MainActor.run {
                    task.updateProgress(downloaded: currentCount, total: totalImages)
                    self.updateTotalProgress()
                }
            }
        }
    }

    private func downloadImage(from urlString: String) async throws -> UIImage {
        guard let url = URL(string: urlString) else {
            throw DownloadError.invalidURL
        }

        let (data, response) = try await URLSession.shared.data(from: url)

        guard let httpResponse = response as? HTTPURLResponse else {
            throw DownloadError.invalidResponse
        }

        guard httpResponse.statusCode == 200 else {
            throw DownloadError.httpError(statusCode: httpResponse.statusCode)
        }

        guard let image = UIImage(data: data) else {
            throw DownloadError.invalidImageData
        }

        return image
    }

    private func moveTaskToCompleted(_ task: DownloadTask) {
        activeDownloads.removeAll { $0.id == task.id }
        downloadCancellations.removeValue(forKey: task.id)

        // Limitar historial a últimas 50 descargas
        if completedDownloads.count >= 50 {
            completedDownloads.removeFirst()
        }

        completedDownloads.append(task)
        updateTotalProgress()
    }

    private func moveTaskToFailed(_ task: DownloadTask) {
        activeDownloads.removeAll { $0.id == task.id }
        downloadCancellations.removeValue(forKey: task.id)

        // Limitar historial a últimos 20 fallos
        if failedDownloads.count >= 20 {
            failedDownloads.removeFirst()
        }

        failedDownloads.append(task)
        updateTotalProgress()
    }
}

// MARK: - Download Errors

enum DownloadError: LocalizedError {
    case alreadyDownloaded
    case noImagesFound
    case invalidURL
    case invalidResponse
    case httpError(statusCode: Int)
    case invalidImageData
    case cancelled
    case storageError(String)

    var errorDescription: String? {
        switch self {
        case .alreadyDownloaded:
            return "El capítulo ya está descargado"
        case .noImagesFound:
            return "No se encontraron imágenes"
        case .invalidURL:
            return "URL inválida"
        case .invalidResponse:
            return "Respuesta inválida del servidor"
        case .httpError(let statusCode):
            return "Error HTTP \(statusCode)"
        case .invalidImageData:
            return "Datos de imagen inválidos"
        case .cancelled:
            return "Descarga cancelada"
        case .storageError(let message):
            return "Error de almacenamiento: \(message)"
        }
    }
}
