//
//  CloudTransferManager.swift
//  suixinkan
//

import Foundation

@MainActor
/// 云盘上传/下载传输管理器，负责本地任务、进度和服务端登记。
final class CloudTransferManager {
    static let shared = CloudTransferManager(
        store: CloudTransferJSONStore(),
        uploader: NetworkServices.shared.ossUploadService,
        api: NetworkServices.shared.cloudDriveAPI,
        downloader: CloudURLSessionDownloader(),
        photoSaver: CloudPhotoLibrarySaver(),
        scenicIdProvider: { AppStore.shared.session.currentScenicId }
    )

    private(set) var uploadTasks: [CloudTransferTask] = []
    private(set) var downloadTasks: [CloudTransferTask] = []

    var onTasksChange: (() -> Void)?
    var onUploadCompleted: ((Int) -> Void)?
    var onShowMessage: ((String) -> Void)?

    private let store: CloudTransferTaskStoring
    private let uploader: any CloudDriveUploading
    private let api: any CloudDriveServing
    private let downloader: any CloudFileDownloading
    private let photoSaver: any CloudPhotoSaving
    private let scenicIdProvider: () -> Int
    private var runningTasks: [String: Task<Void, Never>] = [:]
    private var operationGenerations: [String: Int] = [:]

    /// 初始化传输管理器。
    init(
        store: CloudTransferTaskStoring,
        uploader: any CloudDriveUploading,
        api: any CloudDriveServing,
        downloader: any CloudFileDownloading,
        photoSaver: any CloudPhotoSaving,
        scenicIdProvider: @escaping () -> Int
    ) {
        self.store = store
        self.uploader = uploader
        self.api = api
        self.downloader = downloader
        self.photoSaver = photoSaver
        self.scenicIdProvider = scenicIdProvider
        let tasks = store.loadTasks()
        uploadTasks = tasks.filter { $0.kind == .upload }
        downloadTasks = tasks.filter { $0.kind == .download }
    }

    /// 添加上传任务并立即开始。
    func addUploadTask(
        localFileURL: URL,
        fileName: String,
        fileType: Int,
        fileSize: Int64,
        parentFolderId: Int
    ) {
        let task = CloudTransferTask(
            kind: .upload,
            fileName: fileName,
            fileType: fileType,
            fileSize: fileSize,
            parentFolderId: parentFolderId,
            localPath: localFileURL.path,
            status: .pending
        )
        uploadTasks.insert(task, at: 0)
        persistAndNotify()
        startUpload(task.id)
        onShowMessage?("已添加上传任务")
    }

    /// 添加下载任务并立即开始。
    func addDownloadTask(fileURL: String, fileName: String, fileType: Int, fileSize: Int64 = 0) {
        let task = CloudTransferTask(
            kind: .download,
            fileName: fileName,
            fileType: fileType,
            fileSize: fileSize,
            remoteURL: fileURL,
            status: .pending
        )
        downloadTasks.insert(task, at: 0)
        persistAndNotify()
        startDownload(task.id)
        onShowMessage?("已添加下载任务，可在传输管理查看")
    }

    /// 暂停上传任务。
    func pauseUpload(id: String) {
        runningTasks[id]?.cancel()
        runningTasks[id] = nil
        invalidateOperation(id: id)
        updateUploadTask(id: id) { task in
            task.status = .paused
            task.updatedAt = Date().timeIntervalSince1970
        }
    }

    /// 继续上传任务。
    func resumeUpload(id: String) {
        startUpload(id)
    }

    /// 取消上传任务。
    func cancelUpload(id: String) {
        runningTasks[id]?.cancel()
        runningTasks[id] = nil
        operationGenerations[id] = nil
        if let task = uploadTasks.first(where: { $0.id == id }), !task.localPath.isEmpty {
            try? FileManager.default.removeItem(atPath: task.localPath)
        }
        uploadTasks.removeAll { $0.id == id }
        persistAndNotify()
    }

    /// 暂停下载任务。
    func pauseDownload(id: String) {
        runningTasks[id]?.cancel()
        runningTasks[id] = nil
        invalidateOperation(id: id)
        updateDownloadTask(id: id) { task in
            task.status = .paused
            task.updatedAt = Date().timeIntervalSince1970
        }
    }

    /// 继续下载任务。
    func resumeDownload(id: String) {
        startDownload(id)
    }

    /// 取消下载任务。
    func cancelDownload(id: String) {
        runningTasks[id]?.cancel()
        runningTasks[id] = nil
        operationGenerations[id] = nil
        downloadTasks.removeAll { $0.id == id }
        persistAndNotify()
    }

    private func startUpload(_ id: String) {
        guard runningTasks[id] == nil,
              let index = uploadTasks.firstIndex(where: { $0.id == id }) else { return }

        uploadTasks[index].status = .uploading
        uploadTasks[index].errorMessage = ""
        uploadTasks[index].progress = max(uploadTasks[index].progress, 1)
        uploadTasks[index].updatedAt = Date().timeIntervalSince1970
        let seed = uploadTasks[index]
        let generation = nextOperationGeneration(id: id)
        persistAndNotify()

        runningTasks[id] = Task { [weak self] in
            guard let self else { return }
            do {
                guard FileManager.default.fileExists(atPath: seed.localPath) else {
                    throw CloudTransferError.localFileMissing
                }
                let data = try Data(contentsOf: URL(fileURLWithPath: seed.localPath))
                try Task.checkCancellation()
                let uploadedURL = try await uploader.uploadCloudDriveFile(
                    data: data,
                    fileName: seed.fileName,
                    fileType: seed.fileType,
                    scenicId: scenicIdProvider()
                ) { progress in
                    Task { @MainActor [weak self] in
                        guard let self, self.isCurrentOperation(id: id, generation: generation) else { return }
                        self.updateUploadProgress(id: id, progress: progress)
                    }
                }
                try Task.checkCancellation()
                guard isCurrentOperation(id: id, generation: generation) else { return }
                try await api.uploadCloudFile(
                    parentFolderId: seed.parentFolderId,
                    fileUrl: uploadedURL,
                    fileName: seed.fileName
                )
                guard isCurrentOperation(id: id, generation: generation) else { return }
                try? FileManager.default.removeItem(atPath: seed.localPath)
                runningTasks[id] = nil
                operationGenerations[id] = nil
                uploadTasks.removeAll { $0.id == id }
                persistAndNotify()
                onUploadCompleted?(seed.parentFolderId)
            } catch is CancellationError {
                guard isCurrentOperation(id: id, generation: generation) else { return }
                runningTasks[id] = nil
                markUploadPaused(id: id)
            } catch {
                guard isCurrentOperation(id: id, generation: generation) else { return }
                runningTasks[id] = nil
                markUploadFailed(id: id, message: error.localizedDescription)
            }
        }
    }

    private func startDownload(_ id: String) {
        guard runningTasks[id] == nil,
              let index = downloadTasks.firstIndex(where: { $0.id == id }) else { return }

        downloadTasks[index].status = .downloading
        downloadTasks[index].errorMessage = ""
        downloadTasks[index].progress = max(downloadTasks[index].progress, 1)
        downloadTasks[index].updatedAt = Date().timeIntervalSince1970
        let seed = downloadTasks[index]
        let generation = nextOperationGeneration(id: id)
        persistAndNotify()

        runningTasks[id] = Task { [weak self] in
            guard let self else { return }
            do {
                let localURL = try await downloader.downloadFile(
                    remoteURL: seed.remoteURL,
                    fileName: seed.fileName
                ) { progress in
                    Task { @MainActor [weak self] in
                        guard let self, self.isCurrentOperation(id: id, generation: generation) else { return }
                        self.updateDownloadProgress(id: id, progress: progress)
                    }
                }
                try Task.checkCancellation()
                guard isCurrentOperation(id: id, generation: generation) else { return }
                try await photoSaver.saveToPhotoLibrary(fileURL: localURL, fileType: seed.fileType)
                guard isCurrentOperation(id: id, generation: generation) else { return }
                try? FileManager.default.removeItem(at: localURL)
                runningTasks[id] = nil
                operationGenerations[id] = nil
                updateDownloadTask(id: id) { task in
                    task.status = .completed
                    task.progress = 100
                    task.updatedAt = Date().timeIntervalSince1970
                }
            } catch is CancellationError {
                guard isCurrentOperation(id: id, generation: generation) else { return }
                runningTasks[id] = nil
                markDownloadPaused(id: id)
            } catch {
                guard isCurrentOperation(id: id, generation: generation) else { return }
                runningTasks[id] = nil
                markDownloadFailed(id: id, message: error.localizedDescription)
            }
        }
    }

    private func updateUploadProgress(id: String, progress: Int) {
        updateUploadTask(id: id) { task in
            task.progress = max(task.progress, progress.coerceIn(0 ... 100))
            task.updatedAt = Date().timeIntervalSince1970
        }
    }

    private func updateDownloadProgress(id: String, progress: Int) {
        updateDownloadTask(id: id) { task in
            task.progress = max(task.progress, progress.coerceIn(0 ... 100))
            task.updatedAt = Date().timeIntervalSince1970
        }
    }

    private func markUploadPaused(id: String) {
        updateUploadTask(id: id) { task in
            task.status = .paused
            task.updatedAt = Date().timeIntervalSince1970
        }
    }

    private func markDownloadPaused(id: String) {
        updateDownloadTask(id: id) { task in
            task.status = .paused
            task.updatedAt = Date().timeIntervalSince1970
        }
    }

    private func markUploadFailed(id: String, message: String) {
        updateUploadTask(id: id) { task in
            task.status = .failed
            task.errorMessage = message
            task.updatedAt = Date().timeIntervalSince1970
        }
    }

    private func markDownloadFailed(id: String, message: String) {
        updateDownloadTask(id: id) { task in
            task.status = .failed
            task.errorMessage = message
            task.updatedAt = Date().timeIntervalSince1970
        }
    }

    private func updateUploadTask(id: String, mutation: (inout CloudTransferTask) -> Void) {
        guard let index = uploadTasks.firstIndex(where: { $0.id == id }) else { return }
        mutation(&uploadTasks[index])
        persistAndNotify()
    }

    private func updateDownloadTask(id: String, mutation: (inout CloudTransferTask) -> Void) {
        guard let index = downloadTasks.firstIndex(where: { $0.id == id }) else { return }
        mutation(&downloadTasks[index])
        persistAndNotify()
    }

    private func persistAndNotify() {
        store.saveTasks(uploadTasks + downloadTasks)
        onTasksChange?()
    }

    private func nextOperationGeneration(id: String) -> Int {
        let generation = (operationGenerations[id] ?? 0) + 1
        operationGenerations[id] = generation
        return generation
    }

    private func invalidateOperation(id: String) {
        operationGenerations[id] = (operationGenerations[id] ?? 0) + 1
    }

    private func isCurrentOperation(id: String, generation: Int) -> Bool {
        operationGenerations[id] == generation
    }
}

private extension Comparable {
    func coerceIn(_ range: ClosedRange<Self>) -> Self {
        min(max(self, range.lowerBound), range.upperBound)
    }
}
