import Foundation
import CommonCrypto

// MARK: - UploadResult

enum UploadResult {
    case success(traceId: String)
    case serverError(code: Int, msg: String, traceId: String)
    case networkError(Error)
    case timeout
}

// MARK: - Uploader

/// HTTP 上报器
///
/// - 签名算法：HMAC-MD5，对 meta JSON 原始字符串签名，结果转大写 hex
/// - 请求格式：multipart/form-data，字段顺序：app_id → sign → meta → image
/// - URLSession 以 DispatchSemaphore 同步（在 uploadQueue 串行队列上调用，不阻塞主线程）
/// - 上传地址由 `MaterialMonitorConfig.baseUploadUrl` 指定，未配置时使用内置默认地址
final class Uploader {

    /// 内置默认上传地址（当 config.baseUploadUrl 为空时使用）
    private static let defaultBaseHost = ""
    private static let uploadPath      = "/v1/creative/upload"

    // MARK: - Shared URLSession

    private lazy var session: URLSession = {
        let cfg = URLSessionConfiguration.ephemeral
        cfg.httpMaximumConnectionsPerHost = 1
        if #available(iOS 11.0, *) { cfg.waitsForConnectivity = true }
        let q = OperationQueue()
        q.maxConcurrentOperationCount = 1
        q.name = "com.materialmonitor.session"
        return URLSession(configuration: cfg, delegate: nil, delegateQueue: q)
    }()

    /// 最近一次 4001 响应中的 Retry-After 秒数
    private(set) var lastRetryAfterSeconds: Int?

    /// 中断在途上传请求（shutdown 调用）
    /// invalidateAndCancel 后 session 不可复用——Uploader 随 scheduler 一并重建，故无影响
    func cancelInFlight() {
        session.invalidateAndCancel()
    }

    // MARK: - Upload（同步，在 uploadQueue 调用）

    /// 同步发起上传请求
    ///
    /// - Parameters:
    ///   - task: 内存任务元数据（含预序列化 metaJson）
    ///   - config: SDK 配置（含 appId / appKey / baseUploadUrl / timeout）
    ///   - imageData: JPEG 图片数据（原生自渲染时为 nil）
    /// - Returns: 上传结果（同步返回，调用方在 uploadQueue 上阻塞等待）
    func upload(task: TaskMeta, config: MaterialMonitorConfig, imageData: Data?) -> UploadResult {
        let metaJsonString = task.metaJson
        guard !metaJsonString.isEmpty else {
            return .serverError(code: 2001, msg: "meta_json is empty", traceId: "")
        }

        // 计算签名
        let sign = hmacMD5(key: config.appKey, message: metaJsonString)

        // 构建 multipart
        var form = MultipartForm()
        form.addTextField(name: "app_id", value: config.appId)
        form.addTextField(name: "sign",   value: sign)
        form.addTextField(name: "meta",   value: metaJsonString)
        if let data = imageData {
            form.addFileField(name: "image", filename: "screenshot.jpg",
                              data: data, mimeType: "image/jpeg")
        }
        let bodyData    = form.finalize()
        let contentType = form.contentType

        // 解析上传 URL（config.baseUploadUrl 为空时回退内置默认地址）
        let baseHost = config.baseUploadUrl.isEmpty ? Uploader.defaultBaseHost : config.baseUploadUrl
        let endpointStr = baseHost + Uploader.uploadPath
        guard let url = URL(string: endpointStr) else {
            return .serverError(code: 2001, msg: "invalid upload endpoint: \(endpointStr)", traceId: "")
        }
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue(contentType, forHTTPHeaderField: "Content-Type")
        // 每个请求独立设置超时，覆盖 session 层默认值（对齐 Android connectTimeoutMs / readTimeoutMs）
        request.timeoutInterval = Double(config.connectTimeoutMs) / 1000.0

        let imageSizeDesc = imageData.map { "\($0.count / 1024)KB" } ?? "nil(image_url模式)"
        mmLog("""
        🌐 开始上传请求
             taskId        : \(task.uploadTaskId)
             URL           : \(endpointStr)
             app_id        : \(config.appId)
             sign          : \(sign)
             image         : \(imageSizeDesc)
             connectTimeout: \(config.connectTimeoutMs)ms  readTimeout: \(config.readTimeoutMs)ms
             meta          : \(metaJsonString)
        """)

        // DispatchSemaphore 同步等待（uploadQueue 串行，不阻塞主线程）
        let sem    = DispatchSemaphore(value: 0)
        var result = UploadResult.timeout
        lastRetryAfterSeconds = nil

        let dataTask = session.uploadTask(with: request, from: bodyData) { [weak self] data, response, error in
            defer { sem.signal() }

            if let error = error {
                mmLog("🌐 请求失败（网络错误）taskId=\(task.uploadTaskId) error=\(error.localizedDescription)")
                result = .networkError(error)
                return
            }
            guard let httpResp = response as? HTTPURLResponse else {
                mmLog("🌐 请求失败（响应格式异常）taskId=\(task.uploadTaskId)")
                result = .networkError(URLError(.badServerResponse))
                return
            }

            // 读取 Retry-After header（allHeaderFields 兼容 iOS 12）
            if httpResp.statusCode == 429,
               let ra = httpResp.allHeaderFields["Retry-After"] as? String,
               let secs = Int(ra) {
                self?.lastRetryAfterSeconds = secs
            }

            guard let data = data,
                  let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
            else {
                mmLog("🌐 请求失败（响应体解析失败）taskId=\(task.uploadTaskId) httpStatus=\(httpResp.statusCode)")
                result = .networkError(URLError(.cannotParseResponse))
                return
            }

            let code    = json["code"]     as? Int    ?? -1
            let msg     = json["msg"]      as? String ?? ""
            let traceId = json["trace_id"] as? String ?? ""

            if code == 0 {
                mmLog("🌐 请求成功 taskId=\(task.uploadTaskId) httpStatus=\(httpResp.statusCode) traceId=\(traceId)")
                result = .success(traceId: traceId)
            } else {
                mmLog("🌐 请求失败（服务端错误）taskId=\(task.uploadTaskId) httpStatus=\(httpResp.statusCode) code=\(code) msg=\(msg) traceId=\(traceId)")
                result = .serverError(code: code, msg: msg, traceId: traceId)
            }
        }
        dataTask.resume()
        sem.wait()
        return result
    }

    // MARK: - HMAC-MD5（CommonCrypto，兼容 iOS 12）

    /// 计算 HMAC-MD5，返回 32 字符大写 hex
    private func hmacMD5(key: String, message: String) -> String {
        let keyBytes  = Array(key.utf8)
        let msgBytes  = Array(message.utf8)
        var digest    = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
        CCHmac(CCHmacAlgorithm(kCCHmacAlgMD5),
               keyBytes, keyBytes.count,
               msgBytes, msgBytes.count,
               &digest)
        return digest.map { String(format: "%02X", $0) }.joined()
    }
}

// MARK: - MultipartForm

/// 手动构建 multipart/form-data 请求体（不依赖第三方库）
private struct MultipartForm {
    private var body = Data()
    private let boundary: String

    init() {
        boundary = "MMBoundary-\(UUID().uuidString.replacingOccurrences(of: "-", with: ""))"
    }

    mutating func addTextField(name: String, value: String) {
        append("--\(boundary)\r\n")
        append("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n")
        append(value)
        append("\r\n")
    }

    mutating func addFileField(name: String, filename: String, data: Data, mimeType: String) {
        append("--\(boundary)\r\n")
        append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(filename)\"\r\n")
        append("Content-Type: \(mimeType)\r\n\r\n")
        body.append(data)
        append("\r\n")
    }

    mutating func finalize() -> Data {
        append("--\(boundary)--\r\n")
        return body
    }

    var contentType: String { "multipart/form-data; boundary=\(boundary)" }

    private mutating func append(_ string: String) {
        if let d = string.data(using: .utf8) { body.append(d) }
    }
}

// MARK: - MetaBuilder

/// 构建上报 meta JSON 字符串（首次入队时序列化一次，重试时直接复用）
enum MetaBuilder {

    struct MetaPayload: Encodable {
        struct SupplyInfo: Encodable {
            let placement_id: String
        }
        struct AdInfo: Encodable {
            let ad_source_id: String
            let ad_network_firm_id: Int
            let creative_id: String
            let ad_type: Int
            let ecpm: Double?
        }
        struct EventInfo: Encodable {
            let show_id: String
            let request_id: String
            let request_ts: Int64
            let fill_ts: Int64
            let show_ts: Int64
        }
        struct DeviceInfoPayload: Encodable {
            let idfa: String
            let idfv: String
            let os: String
            let os_version: String
            let device_model: String
            let package_name: String
            let app_version: String
            let sdk_version: String
            let network_type: String
            let screen_resolution: String
        }

        let supply_info: SupplyInfo
        let ad_info: AdInfo
        let event_info: EventInfo
        let device_info: DeviceInfoPayload
        let image_hash: String
        let image_url: String
    }

    /// 构建 meta JSON 字符串
    ///
    /// - Parameters:
    ///   - adInfo: 广告信息
    ///   - timing: 广告时间戳（毫秒 → 除以 1000 写入 meta）
    ///   - style: 广告样式（提供 ad_type）
    ///   - imageHash: pHash hex
    ///   - imageUrl: 原生自渲染 URL，截图场景传 `""`
    ///   - deviceInfo: 设备信息
    /// - Returns: JSON 字符串；序列化失败返回 `nil`
    static func build(
        adInfo: MMAdInfo,
        timing: AdTimingInput,
        style: MaterialStyle,
        imageHash: String,
        imageUrl: String,
        deviceInfo: DeviceInfo
    ) -> String? {
        let payload = MetaPayload(
            supply_info: .init(placement_id: adInfo.placementId),
            ad_info: .init(
                ad_source_id:       adInfo.adSourceId,
                ad_network_firm_id: adInfo.adNetworkFirmId,
                creative_id:        adInfo.creativeId ?? "",
                ad_type:            style.adType,
                ecpm:               adInfo.ecpm?.doubleValue
            ),
            event_info: .init(
                show_id:     adInfo.showId,
                request_id:  adInfo.requestId,
                request_ts:  timing.requestTimeMs / 1000,   // ms → 秒
                fill_ts:     timing.fillTimeMs    / 1000,
                show_ts:     timing.showTimeMs    / 1000
            ),
            device_info: .init(
                idfa:              deviceInfo.idfa,
                idfv:              deviceInfo.idfv,
                os:                deviceInfo.os,
                os_version:        deviceInfo.osVersion,
                device_model:      deviceInfo.deviceModel,
                package_name:      deviceInfo.packageName,
                app_version:       deviceInfo.appVersion,
                sdk_version:       deviceInfo.sdkVersion,
                network_type:      deviceInfo.networkType,
                screen_resolution: deviceInfo.screenResolution
            ),
            image_hash: imageHash,
            image_url:  imageUrl
        )

        let encoder = JSONEncoder()
        encoder.outputFormatting = []    // 紧凑输出，不排序 key
        guard let data = try? encoder.encode(payload) else { return nil }
        return String(data: data, encoding: .utf8)
    }
}
