//
//  LNFileDownloader.swift
//  Lanu
//
//  Created by OneeChan on 2025/12/5.
//

import Foundation


private class LNFileDownloadTask {
    let sourceUrl: String
    let destinationPath: URL
    var isRunning = false
    var progressHandler: [((Float) -> Void)] = []
    var completionHandler: [((Result<URL, Error>) -> Void)] = []
    var totalBytesWritten: Int64 = 0
    var totalBytesExpectedToWrite: Int64 = 0
    
    init(url: String, destinationPath: URL) {
        self.sourceUrl = url
        self.destinationPath = destinationPath
    }
}

class LNFileDownloader: NSObject {
    static let shared = LNFileDownloader()
    private let maxConcurrentCount: Int = 3
    
    private lazy var session: URLSession = {
        let config = URLSessionConfiguration.default
        config.httpMaximumConnectionsPerHost = maxConcurrentCount
        return URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
    }()
    
    private let lock = NSLock()
    private var taskList: [LNFileDownloadTask] = []
    private var runningTaskCount: Int = 0
    
    enum LNFileDownloadError: Error {
        case invalidURL
        case fileMoveFailed
        case beCancelled
        case networkError(Error)
        
        var errorDesc: String? {
            switch self {
            case .invalidURL: return "无效的下载链接"
            case .fileMoveFailed: return "文件保存失败"
            case .beCancelled: return "被取消"
            case .networkError: return "网络错误"
            }
        }
    }
    
    func startDownload(
        from urlString: String,
        destinationPath: URL? = nil,
        progressHandler: ((Float) -> Void)? = nil,
        completionHandler: ((Result<URL, Error>) -> Void)? = nil
    ) {
        guard let url = URL(string: urlString) else {
            completionHandler?(.failure(LNFileDownloadError.invalidURL))
            return
        }
        
        DispatchQueue.global().async { [weak self] in
            guard let self else { return }
            lock.lock()
            defer { lock.unlock() }
            let task = taskList.first { $0.sourceUrl == urlString }
            if let task {
                if let progressHandler {
                    task.progressHandler.append(progressHandler)
                }
                if let completionHandler {
                    task.completionHandler.append(completionHandler)
                }
                return
            }
            let targetPath = destinationPath ?? defaultDestinationPath(for: url)
            let newTask = LNFileDownloadTask(url: urlString, destinationPath: targetPath)
            if let progressHandler {
                newTask.progressHandler.append(progressHandler)
            }
            if let completionHandler {
                newTask.completionHandler.append(completionHandler)
            }
            taskList.append(newTask)
            
            if runningTaskCount >= maxConcurrentCount { return }
            
            let downloadTask = session.downloadTask(with: url)
            downloadTask.taskDescription = urlString
            runningTaskCount += 1
            newTask.isRunning = true
            downloadTask.resume()
        }
    }
    
    func cancelDownload(urlString: String) {
        DispatchQueue.global().async { [weak self] in
            guard let self else { return }
            lock.lock()
            defer { lock.unlock() }
            
            guard let task = taskList.first(where: { $0.sourceUrl == urlString }) else { return }
            session.getAllTasks(completionHandler: { tasks in
                tasks.first { $0.taskDescription == urlString }?.cancel()
            })
            removeTask(urlString: urlString)
            
            let handlers = task.completionHandler
            runOnMain {
                handlers.forEach { $0(.failure(LNFileDownloadError.beCancelled)) }
            }
        }
    }
    
    /// 取消所有下载任务
    func cancelAllDownloads() {
        DispatchQueue.global().async { [weak self] in
            guard let self else { return }
            lock.lock()
            defer { lock.unlock() }
            
            session.getAllTasks(completionHandler: { tasks in
                tasks.forEach { $0.cancel() }
            })
            taskList.removeAll()
            runningTaskCount = 0
        }
    }
}

extension LNFileDownloader {
    /// 生成默认保存路径（Documents目录+原文件名）
    private func defaultDestinationPath(for url: URL) -> URL {
        let fileName = url.lastPathComponent
        return URL.documentsDir.appendingPathComponent(fileName)
    }
    
    private func removeTask(urlString: String) {
        DispatchQueue.global().async { [weak self] in
            guard let self else { return }
            lock.lock()
            defer { lock.unlock() }
            taskList.removeAll(where: { $0.sourceUrl == urlString })
            runningTaskCount = max(0, self.runningTaskCount - 1)
            
            if let nextTask = taskList.first(where: { $0.isRunning == false }) {
                let downloadTask = session.downloadTask(with: URL(string: nextTask.sourceUrl)!)
                downloadTask.taskDescription = urlString
                runningTaskCount += 1
                nextTask.isRunning = true
                downloadTask.resume()
            }
        }
    }
}

extension LNFileDownloader: URLSessionDownloadDelegate {
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
        lock.lock()
        defer { lock.unlock() }
        guard let urlString = downloadTask.taskDescription,
              let taskModel = taskList.first(where: { $0.sourceUrl == urlString }) else {
            return
        }
        
        taskModel.totalBytesWritten = totalBytesWritten
        taskModel.totalBytesExpectedToWrite = totalBytesExpectedToWrite
        
        let progress = totalBytesExpectedToWrite > 0 ? Float(totalBytesWritten) / Float(totalBytesExpectedToWrite) : 0.0
        let handlers = taskModel.progressHandler
        runOnMain {
            handlers.forEach { $0(progress) }
        }
    }
    
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        lock.lock()
        defer { lock.unlock() }
        
        guard FileManager.default.fileExists(atPath: location.path) else { return }
        
        guard let urlString = downloadTask.taskDescription,
              let taskModel = taskList.first(where: { $0.sourceUrl == urlString }) else {
            return
        }
        
        // 2. 移动临时文件到目标路径
        do {
            let destinationPath = taskModel.destinationPath
            
            // 检查目标文件是否存在，存在则删除（避免覆盖失败）
            if FileManager.default.fileExists(atPath: destinationPath.path) {
                try FileManager.default.removeItem(at: destinationPath)
            }
            
            // 移动文件
            let destinationDir = destinationPath.deletingLastPathComponent()
            if !FileManager.default.fileExists(atPath: destinationDir.path) {
                try FileManager.default.createDirectory(
                    at: destinationDir,
                    withIntermediateDirectories: true,
                    attributes: nil
                )
            }
            try FileManager.default.moveItem(at: location, to: destinationPath)
            
            // 回调成功结果
            let handlers = taskModel.completionHandler
            runOnMain {
                handlers.forEach { $0(.success(destinationPath)) }
            }
        } catch {
            // 回调文件移动失败
            let handlers = taskModel.completionHandler
            runOnMain {
                handlers.forEach { $0(.failure(LNFileDownloadError.fileMoveFailed)) }
            }
        }
        removeTask(urlString: urlString)
    }
}

extension LNFileDownloader: URLSessionTaskDelegate {
    /// 任务完成（含错误处理）
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        lock.lock()
        defer { lock.unlock() }
        guard let urlString = task.taskDescription,
              let taskModel = taskList.first(where: { $0.sourceUrl == urlString }) else {
            return
        }
        
        // 处理错误（排除用户取消的情况）
        if let error = error {
            let nsError = error as NSError
            // 取消下载在取消操作进行了回调，这里不处理
            if nsError.code == NSURLErrorCancelled {
                removeTask(urlString: urlString)
                return
            }
            // 其他错误：回调网络错误
            let handlers = taskModel.completionHandler
            runOnMain {
                handlers.forEach { $0(.failure(LNFileDownloadError.networkError(error))) }
            }
        }
        
        // 移除任务（无论成功与否，任务完成后都要移除）
        removeTask(urlString: urlString)
    }
}
