//
//  LNFileUploader.swift
//  Lanu
//
//  Created by OneeChan on 2025/12/2.
//

import Foundation


private class LNFileUploadTask {
    let id: String
    let fileUrl: String
    let task: URLSessionUploadTask
    let progress: ((Float) -> Void)?
    let completion: ((String?, String?) -> Void)?
    
    init(id: String,
         fileUrl: String,
         task: URLSessionUploadTask,
         progress: ((Float) -> Void)? = nil,
         completion: ((String?, String?) -> Void)? = nil) {
        self.id = id
        self.fileUrl = fileUrl
        self.task = task
        self.progress = progress
        self.completion = completion
    }
}

class LNFileUploader: NSObject {
    static let shared = LNFileUploader()
    
    private lazy var session: URLSession = {
        let config = URLSessionConfiguration.default
        return URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
    }()
    private var uploadTasks: [String: LNFileUploadTask] = [:]
    private let lock = NSLock()
    
    @discardableResult
    func startUpload(
        type: LNUploadFileType,
        fileURL: URL,
        headers: [String: String] = [:],
        progressHandler: ((Float) -> Void)?,
        completionHandler: ((String?, String?) -> Void)?
    ) -> String? {
        lock.lock()
        defer { lock.unlock() }
        
        let taskId = "\(type.rawValue)-\(fileURL.absoluteString.md5)"
        
        guard uploadTasks[taskId] == nil else {
            completionHandler?(nil, .init(key: "B00017"))
            return nil
        }
        
        guard FileManager.default.fileExists(atPath: fileURL.path) else {
            completionHandler?(nil, .init(key: "B00014"))
            return nil
        }
        
        LNHttpManager.shared.getUploadOssUrl(type: type, suffix: fileURL.pathExtension) { [weak self] res, err in
            guard let self else { return }
            guard err == nil, let res, let url = URL(string: res.preSignUrl) else {
                completionHandler?(nil, err?.errorDesc ?? LNHttpError.invalidResponse.errorDesc)
                return
            }
            var request = URLRequest(url: url)
            request.httpMethod = "PUT"
            let defaultHeaders: [String: String] = ["Content-Type": "application/octet-stream"]
            let allHeaders = defaultHeaders.merging(headers) { $1 }
            allHeaders.forEach { key, value in
                request.setValue(value, forHTTPHeaderField: key)
            }
            
            let task = session.uploadTask(with: request, fromFile: fileURL)
            
            let uploadTask = LNFileUploadTask(
                id: taskId, fileUrl: res.fileUrl,
                task: task, progress: progressHandler,
                completion: completionHandler)
            uploadTasks[taskId] = uploadTask
            
            task.resume()
        }
        
        return taskId
    }
    
    @discardableResult
    func startUpload(
        type: LNUploadFileType,
        fileData: Data,
        suffix: String,
        headers: [String: String] = [:],
        progressHandler: ((Float) -> Void)?,
        completionHandler: ((String?, String?) -> Void)?
    ) -> String? {
        lock.lock()
        defer { lock.unlock() }
        
        let taskId = "\(type.rawValue)-\(fileData.md5)"
        
        guard uploadTasks[taskId] == nil else {
            completionHandler?(nil, .init(key: "B00017"))
            return nil
        }
        
        LNHttpManager.shared.getUploadOssUrl(type: type, suffix: suffix) { [weak self] res, err in
            guard let self else { return }
            guard err == nil, let res, let url = URL(string: res.preSignUrl) else {
                runOnMain {
                    completionHandler?(nil, err?.errorDesc ?? LNHttpError.invalidResponse.errorDesc)
                }
                return
            }
            var request = URLRequest(url: url)
            request.httpMethod = "PUT"
            let defaultHeaders: [String: String] = ["Content-Type": "application/octet-stream"]
            let allHeaders = defaultHeaders.merging(headers) { $1 }
            allHeaders.forEach { key, value in
                request.setValue(value, forHTTPHeaderField: key)
            }
            
            let task = session.uploadTask(with: request, from: fileData)
            
            let uploadTask = LNFileUploadTask(
                id: taskId, fileUrl: res.fileUrl,
                task: task, progress: progressHandler,
                completion: completionHandler)
            uploadTasks[taskId] = uploadTask
            
            task.resume()
        }
        return taskId
    }
    
    func cancelUpload(taskID: String) {
        lock.lock()
        defer { lock.unlock() }
        
        // 清理任务和回调
        let uploadTask = uploadTasks.removeValue(forKey: taskID)
        uploadTask?.task.cancel()
    }
    
    /// 取消所有上传任务
    func cancelAllUploads() {
        lock.lock()
        defer { lock.unlock() }
        
        uploadTasks.values.forEach { $0.task.cancel() }
        uploadTasks.removeAll()
    }
}

extension LNFileUploader: URLSessionTaskDelegate, URLSessionDataDelegate {
    func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
        lock.lock()
        defer { lock.unlock() }
        
        guard let progressHandler = uploadTasks.first(where: { $0.value.task == task })?.value.progress else { return }
        guard totalBytesExpectedToSend > 0 else { return }
        let progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
        runOnMain {
            progressHandler(progress)
        }
    }
    
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        lock.lock()
        defer { lock.unlock() }
        
        guard let uploadTask = uploadTasks.first(where: { $0.value.task == task })?.value else { return }
        uploadTasks.removeValue(forKey: uploadTask.id)
        guard let completionHandler = uploadTask.completion else { return }
        
        runOnMain {
            if let error = error {
                if (error as NSError).code == NSURLErrorCancelled {
                    completionHandler(nil, .init(key: "B00015"))
                } else {
                    completionHandler(nil, error.localizedDescription)
                }
            } else {
                if let httpResponse = task.response as? HTTPURLResponse, (200...201).contains(httpResponse.statusCode) {
                    completionHandler(uploadTask.fileUrl, nil)
                } else {
                    let statusCode = (task.response as? HTTPURLResponse)?.statusCode ?? -4
                    completionHandler(nil, .init(key: "B00016"))
                }
            }
        }
    }
}
