//
//  OSSUploadService.swift
//  suixinkan
//

import Foundation
import UniformTypeIdentifiers

#if canImport(AlibabaCloudOSS)
import AlibabaCloudOSS
#endif

/// OSS 上传服务协议，定义 Profile 模块可复用的上传入口。
@MainActor
protocol OSSUploadServing {
    func uploadUserAvatar(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String
    func uploadRealNameImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String
    func uploadBankCardImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String
}

/// OSS 上传服务，负责获取 STS 配置、校验文件、调用阿里云 SDK 并返回最终 URL。
@MainActor
final class OSSUploadService {
    private let configService: any OSSConfigServing

    init(configService: any OSSConfigServing) {
        self.configService = configService
    }

    func uploadUserAvatar(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
        try await uploadFile(data: data, fileName: fileName, fileType: 2, scenicId: scenicId, moduleType: "user_avatar", onProgress: onProgress)
    }

    func uploadRealNameImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
        try await uploadFile(data: data, fileName: fileName, fileType: 2, scenicId: scenicId, moduleType: "real_name_auth", onProgress: onProgress)
    }

    func uploadBankCardImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
        try await uploadFile(data: data, fileName: fileName, fileType: 2, scenicId: scenicId, moduleType: "bank_card", onProgress: onProgress)
    }

    /// 上传景区申请图片，对齐 Android `moduleType = scenic_apply`。
    func uploadScenicApplyImage(
        data: Data,
        fileName: String,
        scenicId: Int,
        onProgress: @escaping @MainActor (Int) -> Void
    ) async throws -> String {
        try await uploadFile(
            data: data,
            fileName: fileName,
            fileType: 2,
            scenicId: scenicId,
            moduleType: "scenic_apply",
            onProgress: { progress in
                Task { @MainActor in
                    onProgress(progress)
                }
            }
        )
    }

    /// 上传任务素材，对齐 Android `moduleType = task_upload`。
    func uploadTaskFile(
        data: Data,
        fileName: String,
        fileType: Int,
        scenicId: Int,
        onProgress: @escaping (Int) -> Void
    ) async throws -> String {
        try await uploadFile(
            data: data,
            fileName: fileName,
            fileType: fileType,
            scenicId: scenicId,
            moduleType: "task_upload",
            onProgress: onProgress
        )
    }

    /// 上传旅拍相册素材，对齐 Android `moduleType = album`。
    func uploadTravelAlbumMaterial(
        data: Data,
        fileName: String,
        scenicId: Int,
        onProgress: @escaping (Int) -> Void
    ) async throws -> String {
        try await uploadFile(
            data: data,
            fileName: fileName,
            fileType: 2,
            scenicId: scenicId,
            moduleType: "album",
            onProgress: onProgress
        )
    }

    /// 上传样片素材，对齐 Android `moduleType = sample`。
    func uploadSampleFile(
        data: Data,
        fileName: String,
        fileType: Int,
        scenicId: Int,
        onProgress: @escaping (Int) -> Void
    ) async throws -> String {
        try await uploadFile(
            data: data,
            fileName: fileName,
            fileType: fileType,
            scenicId: scenicId,
            moduleType: "sample",
            onProgress: onProgress
        )
    }

    /// 上传素材管理素材，对齐 Android `moduleType = material`。
    func uploadMaterialFile(
        data: Data,
        fileName: String,
        fileType: Int,
        scenicId: Int,
        onProgress: @escaping (Int) -> Void
    ) async throws -> String {
        try await uploadFile(
            data: data,
            fileName: fileName,
            fileType: fileType,
            scenicId: scenicId,
            moduleType: "material",
            onProgress: onProgress
        )
    }

    /// 上传打卡点图片，对齐 Android `moduleType = punch_point`。
    func uploadPunchPointImage(
        data: Data,
        fileName: String,
        scenicId: Int,
        onProgress: @escaping (Int) -> Void
    ) async throws -> String {
        try await uploadFile(
            data: data,
            fileName: fileName,
            fileType: 2,
            scenicId: scenicId,
            moduleType: "punch_point",
            onProgress: onProgress
        )
    }

    /// 上传直播封面，对齐 Android `moduleType = live_covers`。
    func uploadLiveCover(
        data: Data,
        fileName: String,
        scenicId: Int,
        onProgress: @escaping (Int) -> Void
    ) async throws -> String {
        try await uploadFile(
            data: data,
            fileName: fileName,
            fileType: 2,
            scenicId: scenicId,
            moduleType: "live_covers",
            onProgress: onProgress
        )
    }

    /// 上传直播相册素材，对齐 Android `moduleType = alive_album`。
    func uploadLiveAlbumFile(
        data: Data,
        fileName: String,
        fileType: Int,
        scenicId: Int,
        onProgress: @escaping (Int) -> Void
    ) async throws -> String {
        try await uploadFile(
            data: data,
            fileName: fileName,
            fileType: fileType,
            scenicId: scenicId,
            moduleType: "alive_album",
            onProgress: onProgress
        )
    }

    /// 上传相册云盘文件，对齐 Android `cloud_driver` / `cloud_driver_test` 路径。
    func uploadCloudDriveFile(
        data: Data,
        fileName: String,
        fileType: Int,
        scenicId: Int,
        onProgress: @escaping (Int) -> Void
    ) async throws -> String {
        try await uploadFile(
            data: data,
            fileName: fileName,
            fileType: fileType,
            scenicId: scenicId,
            moduleType: "cloud_driver",
            onProgress: onProgress
        )
    }

    /// 上传举报摄影师附件，对齐举报提交接口的证据与支付截图。
    func uploadWildReportAttachment(
        data: Data,
        fileName: String,
        fileType: Int,
        scenicId: Int,
        onProgress: @escaping (Int) -> Void
    ) async throws -> String {
        try await uploadFile(
            data: data,
            fileName: fileName,
            fileType: fileType,
            scenicId: scenicId,
            moduleType: "photog_report",
            onProgress: onProgress
        )
    }

    private func uploadFile(
        data: Data,
        fileName: String,
        fileType: Int,
        scenicId: Int,
        moduleType: String,
        onProgress: @escaping (Int) -> Void
    ) async throws -> String {
        onProgress(1)
        try OSSUploadPolicy.validate(dataSize: data.count, fileName: fileName)

        let config = try await configService.aliyunOSSBucket(bucket: "vipsky")
        let objectKey = OSSUploadPolicy.objectKey(fileName: fileName, scenicId: scenicId, moduleType: moduleType)
        onProgress(12)

        #if canImport(AlibabaCloudOSS)
        let credentialsProvider = StaticCredentialsProvider(
            accessKeyId: config.credentials.accessKeyId,
            accessKeySecret: config.credentials.accessKeySecret,
            securityToken: config.credentials.securityToken
        )
        let clientConfig = Configuration.default()
            .withRegion(config.region)
            .withCredentialsProvider(credentialsProvider)
        if !config.endpoint.isEmpty {
            clientConfig.withEndpoint(config.endpoint)
        }
        onProgress(25)
        let client = Client(clientConfig)
        _ = try await client.putObject(
            PutObjectRequest(
                bucket: config.bucket,
                key: objectKey,
                contentType: OSSUploadPolicy.contentType(for: fileName, fileType: fileType),
                body: .data(data),
                progress: ProgressClosure { _, transferred, expected in
                    guard expected > 0 else { return }
                    let uploadProgress = Double(transferred) / Double(expected)
                    let progress = max(26, min(99, 25 + Int(uploadProgress * 74)))
                    onProgress(progress)
                }
            )
        )
        onProgress(100)
        return OSSUploadPolicy.joinURL(baseURL: config.baseUrl, objectKey: objectKey)
        #else
        throw OSSUploadError.sdkUnavailable
        #endif
    }
}

extension OSSUploadService: OSSUploadServing {}

extension OSSUploadService: SampleOSSUploading {}

extension OSSUploadService: MaterialOSSUploading {}

extension OSSUploadService: PunchPointImageUploading {}

enum OSSUploadPolicy {
    static let maxFileSize = 2_048 * 1_024 * 1_024
    private static let allowedExtensions: Set<String> = ["mp4", "mov", "m4v", "avi", "png", "jpg", "jpeg", "heic", "heif"]

    static func validate(dataSize: Int, fileName: String) throws {
        guard dataSize > 0 else { throw OSSUploadError.emptyFile }
        guard dataSize <= maxFileSize else { throw OSSUploadError.fileTooLarge }
        let ext = URL(fileURLWithPath: fileName).pathExtension.lowercased()
        guard allowedExtensions.contains(ext) else { throw OSSUploadError.unsupportedFileType }
    }

    static func objectKey(
        fileName: String,
        scenicId: Int,
        moduleType: String,
        date: Date = Date(),
        uuid: UUID = UUID(),
        timeZone: TimeZone = .current
    ) -> String {
        let formatter = DateFormatter()
        formatter.calendar = Calendar(identifier: .gregorian)
        formatter.timeZone = timeZone
        formatter.dateFormat = "yyyyMMdd"
        let currentDate = formatter.string(from: date)
        let uploadId = uuid.uuidString.replacingOccurrences(of: "-", with: "").lowercased()
        let safeName = sanitizedFileName(fileName)
        switch moduleType {
        case "user_avatar":
            return "avatar/\(currentDate)/\(scenicId)/\(uploadId)_\(safeName)"
        case "real_name_auth":
            let userId = AppStore.shared.session.userId.trimmingCharacters(in: .whitespacesAndNewlines)
            let ownerId = userId.isEmpty ? String(scenicId) : userId
            return "real_name_auth/\(currentDate)/\(ownerId)/\(uploadId)_\(safeName)"
        case "bank_card":
            return "bank_card/\(currentDate)/\(scenicId)/\(uploadId)_\(safeName)"
        case "task_upload":
            return "task/\(currentDate)/\(scenicId)/\(uploadId)_\(safeName)"
        case "album":
            return "album/\(currentDate)/\(scenicId)/\(uploadId)_\(safeName)"
        case "sample":
            return "sample/\(currentDate)/\(scenicId)/\(uploadId)_\(safeName)"
        case "material":
            // Android 素材任务固定 folderId = 0，后端会按此层级解析对象信息并写入视频元数据。
            return "material/\(scenicId)/0/\(uploadId)_\(safeName)"
        case "punch_point":
            let userId = AppStore.shared.session.userId.trimmingCharacters(in: .whitespacesAndNewlines)
            let ownerId = userId.isEmpty ? String(scenicId) : userId
            return "punch_point/\(currentDate)/\(ownerId)/\(uploadId)_\(safeName)"
        case "scenic_apply":
            return "scenic_apply/\(currentDate)/\(scenicId)/\(uploadId)_\(safeName)"
        case "live_covers":
            return "live_covers/\(currentDate)/\(scenicId)/\(uploadId)_\(safeName)"
        case "alive_album":
            return "alive_album/\(currentDate)/\(scenicId)/\(uploadId)_\(safeName)"
        case "cloud_driver":
            let userId = AppStore.shared.session.userId.trimmingCharacters(in: .whitespacesAndNewlines)
            let ownerId = userId.isEmpty ? String(scenicId) : userId
            let month = String(currentDate.dropFirst(4).prefix(2))
            #if DEBUG
            let root = "cloud_driver_test"
            #else
            let root = "cloud_driver"
            #endif
            return "\(root)/\(ownerId)/\(String(currentDate.prefix(4)))/\(month)/\(safeName)"
        case "photog_report":
            return "photog_report/\(currentDate)/\(scenicId)/\(uploadId)_\(safeName)"
        default:
            return "task/\(currentDate)/\(scenicId)/\(uploadId)_\(safeName)"
        }
    }

    static func contentType(for fileName: String, fileType: Int) -> String {
        let ext = URL(fileURLWithPath: fileName).pathExtension
        if let type = UTType(filenameExtension: ext)?.preferredMIMEType {
            return type
        }
        return fileType == 1 ? "video/mp4" : "image/jpeg"
    }

    static func joinURL(baseURL: String, objectKey: String) -> String {
        if baseURL.hasSuffix("/") {
            return baseURL + objectKey
        }
        return baseURL + "/" + objectKey
    }

    private static func sanitizedFileName(_ fileName: String) -> String {
        let trimmedName = fileName.trimmingCharacters(in: .whitespacesAndNewlines)
        var unsafeCharacters = CharacterSet.controlCharacters
        unsafeCharacters.formUnion(CharacterSet(charactersIn: "/\\"))

        let safeName = trimmedName.unicodeScalars.reduce(into: "") { result, scalar in
            result += unsafeCharacters.contains(scalar) ? "_" : String(scalar)
        }
        return safeName.isEmpty ? "upload" : safeName
    }
}

enum OSSUploadError: LocalizedError, Equatable {
    case sdkUnavailable
    case emptyFile
    case fileTooLarge
    case unsupportedFileType

    var errorDescription: String? {
        switch self {
        case .sdkUnavailable:
            "阿里云 OSS Swift SDK 尚未链接到当前 iOS target"
        case .emptyFile:
            "文件内容不能为空"
        case .fileTooLarge:
            "文件大小不能超过2048MB"
        case .unsupportedFileType:
            "仅支持.mp4,.mov,.m4v,.avi,.png,.jpg,.jpeg,.heic,.heif格式"
        }
    }
}
