import Foundation
import Network

// MARK: - Internal Debug Logging
// 受 MaterialMonitorConfig.debugPerfLogging 开关控制，在 MaterialMonitor.setup() 中设置

/// 模块级 Debug 日志开关（internal，由 MaterialMonitor 在 setup 时写入）
var _mmDebugLogging = false

/// 轻量 Debug 日志：仅在 _mmDebugLogging == true 时求值消息字符串并输出
/// @autoclosure 保证未开启时不产生字符串拼接开销
@inline(__always)
func mmLog(_ message: @autoclosure () -> String) {
    guard _mmDebugLogging else { return }
    print("[Debug log] " + message())
}

// MARK: - CallbackBox（弱引用包装，防止持有 ViewController 导致泄漏）

final class CallbackBox {
    weak var frameCallback:  FrameCallback?
    weak var reportCallback: ReportCallback?

    init(frameCallback: FrameCallback? = nil, reportCallback: ReportCallback? = nil) {
        self.frameCallback  = frameCallback
        self.reportCallback = reportCallback
    }
}

// MARK: - TaskScheduler

/// 上传任务调度器
///
/// - 运行于 `com.materialmonitor.upload` 串行队列
/// - 保证任意时刻最多 1 个 HTTP 请求在飞（DispatchSemaphore 同步）
/// - retryWaiting 任务通过 DispatchSourceTimer 定时唤醒
/// - 网络恢复（NWPathMonitor）自动触发调度循环
final class TaskScheduler {

    // MARK: - Upload Queue

    private let uploadQueue = DispatchQueue(
        label: "com.materialmonitor.upload",
        qos: .utility
    )

    // MARK: - Dependencies

    private let store:     TaskStore
    private let uploader:  Uploader
    private var config:    MaterialMonitorConfig?
    private let collector: DeviceInfoCollector

    // MARK: - Callback Registry

    /// uploadTaskId → CallbackBox（弱引用，回调消失时自动失效）
    private var callbackMap: [String: CallbackBox] = [:]
    private let callbackLock = NSLock()

    // MARK: - Network Monitor

    private let pathMonitor = NWPathMonitor()
    private let networkQueue = DispatchQueue(
        label: "com.materialmonitor.netmonitor",
        qos: .utility
    )
    private var isNetworkAvailable = true

    // MARK: - Retry Timer

    private var retryTimer: DispatchSourceTimer?

    // MARK: - Stop Flag

    /// 关闭标志：teardown 后置 true，drainQueue 循环据此尽快退出
    private var _isStopped = false
    private let stopLock = NSLock()
    private var isStopped: Bool {
        get { stopLock.lock(); defer { stopLock.unlock() }; return _isStopped }
        set { stopLock.lock(); _isStopped = newValue; stopLock.unlock() }
    }

    /// 记录本次会话内曾经重试过的 uploadTaskId（用于区分冷启动恢复 vs 当次重试）
    private var inSessionRetries = Set<String>()

    // MARK: - Init

    init(store: TaskStore, config: MaterialMonitorConfig?, collector: DeviceInfoCollector) {
        self.store     = store
        self.config    = config
        self.collector = collector
        self.uploader  = Uploader()
        startNetworkMonitor()
    }

    deinit {
        retryTimer?.cancel()
        pathMonitor.cancel()
    }

    // MARK: - Public API

    /// 更新配置（重新调用 initialize 时使用）
    func updateConfig(_ config: MaterialMonitorConfig?) {
        self.config = config
    }

    /// 关闭调度器：停止循环、取消定时器与网络监听、中断在途上传、清空回调注册表
    /// 磁盘文件保留（由 MaterialMonitor.shutdown 语义决定）
    func teardown() {
        isStopped = true
        retryTimer?.cancel()
        retryTimer = nil
        pathMonitor.cancel()
        uploader.cancelInFlight()
        callbackLock.lock()
        callbackMap.removeAll()
        callbackLock.unlock()
        store.invalidateTimers()
    }

    /// 注册 FrameCallback
    func registerCallback(_ callback: FrameCallback, for uploadTaskId: String) {
        callbackLock.lock()
        callbackMap[uploadTaskId] = CallbackBox(frameCallback: callback)
        callbackLock.unlock()
    }

    /// 注册 ReportCallback
    func registerReportCallback(_ callback: ReportCallback, for uploadTaskId: String) {
        callbackLock.lock()
        callbackMap[uploadTaskId] = CallbackBox(reportCallback: callback)
        callbackLock.unlock()
    }

    /// 触发调度循环（新任务入队、网络恢复、flush、冷启动 均调用此方法）
    func trigger() {
        uploadQueue.async { [weak self] in
            self?.drainQueue()
        }
    }

    // MARK: - Core Drain Loop

    private func drainQueue() {
        guard !isStopped else { return }
        guard let cfg = config, cfg.isUploadReady else { return }

        while true {
            if isStopped { return }
            let now = Int64(Date().timeIntervalSince1970 * 1000)

            // 从 IO 队列取任务（同步）
            var task: TaskMeta?
            TaskStore.ioQueue.sync {
                // 配额耗尽：所有任务跳过
                guard !self.store.quotaExhaustedUntilMidnight else { return }
                task = self.store.pickNextRunnable(now: now)
            }

            guard let t = task else {
                // 没有可执行任务，为 retryWaiting 设置定时唤醒
                scheduleRetryTimerIfNeeded()
                return
            }

            let attempt = t.attemptCount + 1

            // Step 1: 状态 → uploading
            TaskStore.ioQueue.sync {
                self.store.updateStatus(uploadTaskId: t.uploadTaskId, status: .uploading)
            }

            // Step 2: 校验 .jpg 存在
            var imageData: Data?
            TaskStore.ioQueue.sync {
                imageData = self.store.readImageData(for: t)
            }
            guard let data = imageData else {
                mmLog("❌ 图片文件丢失 taskId=\(t.uploadTaskId) → 标记 dead")
                TaskStore.ioQueue.sync {
                    self.store.updateStatus(uploadTaskId: t.uploadTaskId, status: .dead,
                                            lastErrorCode: MMErrorCode.paramsInvalid.rawValue,
                                            lastErrorMsg: "image_file_missing")
                    self.store.removeTask(uploadTaskId: t.uploadTaskId)
                }
                fireFailureCallback(for: t, code: .paramsInvalid, message: "image_file_missing", record: nil)
                continue
            }

            // Step 3: 上传（在 uploadQueue 上同步等待，不阻塞主线程）
            if attempt == 1 {
                mmLog("🚀 开始上传 taskId=\(t.uploadTaskId) style=\(t.style.rawValue) imageSize=\(data.count / 1024)KB")
            } else if t.attemptCount > 0 && !inSessionRetries.contains(t.uploadTaskId) {
                mmLog("🔄 冷启动恢复（历史第\(t.attemptCount)次失败后继续）taskId=\(t.uploadTaskId)")
            } else {
                mmLog("🔁 第\(attempt - 1)次重试上传 taskId=\(t.uploadTaskId)")
            }
            inSessionRetries.insert(t.uploadTaskId)
            let result = uploader.upload(task: t, config: cfg, imageData: data)

            // Step 4: 处理响应
            handleUploadResult(result, task: t, config: cfg)

            // goto 1（继续拉下一条）
        }
    }

    // MARK: - Result Handling

    private func handleUploadResult(_ result: UploadResult, task: TaskMeta, config: MaterialMonitorConfig) {
        let now = Int64(Date().timeIntervalSince1970 * 1000)

        switch result {
        case .success(let traceId):
            mmLog("✅ 上传成功 taskId=\(task.uploadTaskId) traceId=\(traceId)")
            TaskStore.ioQueue.sync {
                self.store.removeTask(uploadTaskId: task.uploadTaskId)
            }
            fireSuccessCallback(for: task)

        case .serverError(let code, let msg, let traceId):
            switch code {
            case 0:
                mmLog("✅ 上传成功（code=0）taskId=\(task.uploadTaskId) traceId=\(traceId)")
                TaskStore.ioQueue.sync { self.store.removeTask(uploadTaskId: task.uploadTaskId) }
                fireSuccessCallback(for: task)

            case 1001, 1002:
                mmLog("❌ 鉴权失败 code=\(code) msg=\(msg) taskId=\(task.uploadTaskId) → 不重试")
                markDead(task: task, errorCode: .authFailed, msg: msg)

            case 2001, 2002, 2003, 2005:
                mmLog("❌ 参数错误 code=\(code) msg=\(msg) taskId=\(task.uploadTaskId) → 不重试")
                markDead(task: task, errorCode: .paramsInvalid, msg: msg)

            case 3001, 3002:
                mmLog("⛔ 配额耗尽 code=\(code) msg=\(msg) taskId=\(task.uploadTaskId) → 当天停止上报")
                TaskStore.ioQueue.sync {
                    self.store.markQuotaExhausted()
                    self.store.updateStatus(uploadTaskId: task.uploadTaskId, status: .dead,
                                            lastErrorCode: code, lastErrorMsg: msg)
                    self.store.removeTask(uploadTaskId: task.uploadTaskId)
                }
                fireFailureCallback(for: task, code: .retryExhausted,
                                    message: "Quota exhausted: \(msg)", record: makeRecord(task))

            case 4001:
                let retryAfterMs = uploader.lastRetryAfterSeconds.map { Int64($0) * 1000 }
                    ?? Int64(config.retryDelayMs.first ?? 5_000)
                mmLog("🚦 限流 code=4001 → \(retryAfterMs / 1000)秒后重试 taskId=\(task.uploadTaskId)")
                TaskStore.ioQueue.sync {
                    self.store.updateStatus(
                        uploadTaskId: task.uploadTaskId,
                        status: .retryWaiting,
                        nextAttemptAt: now + retryAfterMs,
                        lastErrorCode: code,
                        lastErrorMsg: "rate_limited"
                    )
                }
                scheduleRetryTimerIfNeeded()

            case 5003:
                mmLog("❌ image_url 下载失败 code=5003 taskId=\(task.uploadTaskId) → 不重试")
                markDead(task: task, errorCode: .paramsInvalid, msg: "image_url_download_failed")

            default:
                mmLog("⚠️ 服务端错误 code=\(code) msg=\(msg) taskId=\(task.uploadTaskId)")
                retryOrDead(task: task, config: config, now: now,
                            errorCode: .networkError, msg: msg)
            }

        case .networkError(let error):
            mmLog("⚠️ 网络错误：\(error.localizedDescription) taskId=\(task.uploadTaskId)")
            retryOrDead(task: task, config: config, now: now,
                        errorCode: .networkError, msg: error.localizedDescription)

        case .timeout:
            mmLog("⚠️ 请求超时 taskId=\(task.uploadTaskId)")
            retryOrDead(task: task, config: config, now: now,
                        errorCode: .networkError, msg: "request timed out")
        }
    }

    // MARK: - Retry / Dead helpers

    private func retryOrDead(task: TaskMeta, config: MaterialMonitorConfig,
                             now: Int64, errorCode: MMErrorCode, msg: String) {
        let nextAttempt  = task.attemptCount + 1
        if nextAttempt >= config.maxUploadAttempts {
            mmLog("❌ 重试耗尽（已尝试 \(nextAttempt) 次）taskId=\(task.uploadTaskId) → 标记 dead")
            markDead(task: task, errorCode: .retryExhausted, msg: "max retries reached")
        } else {
            let idx        = min(nextAttempt - 1, config.retryDelayMs.count - 1)
            let delay      = Int64(config.retryDelayMs[max(0, idx)])
            let delaySecs  = delay / 1000
            mmLog("⏱ 第\(nextAttempt)次重试将在 \(delaySecs) 秒后执行 taskId=\(task.uploadTaskId)")
            TaskStore.ioQueue.sync {
                self.store.updateStatus(
                    uploadTaskId: task.uploadTaskId,
                    status: .retryWaiting,
                    attemptCount: nextAttempt,
                    nextAttemptAt: now + delay,
                    lastErrorCode: errorCode.rawValue,
                    lastErrorMsg: msg
                )
            }
            scheduleRetryTimerIfNeeded()
        }
    }

    private func markDead(task: TaskMeta, errorCode: MMErrorCode, msg: String) {
        TaskStore.ioQueue.sync {
            self.store.updateStatus(
                uploadTaskId: task.uploadTaskId,
                status: .dead,
                lastErrorCode: errorCode.rawValue,
                lastErrorMsg: msg
            )
            self.store.removeTask(uploadTaskId: task.uploadTaskId)
        }
        fireFailureCallback(for: task, code: errorCode, message: msg, record: makeRecord(task))
    }

    // MARK: - Retry Timer

    private func scheduleRetryTimerIfNeeded() {
        guard !isStopped else { return }
        var nextTime: Int64?
        TaskStore.ioQueue.sync {
            nextTime = self.store.nextRetryWaitingTime()
        }
        guard let t = nextTime else { return }

        let now = Int64(Date().timeIntervalSince1970 * 1000)
        let delay = max(0, t - now)

        retryTimer?.cancel()
        let timer = DispatchSource.makeTimerSource(queue: uploadQueue)
        timer.schedule(deadline: .now() + .milliseconds(Int(delay)))
        timer.setEventHandler { [weak self] in
            self?.drainQueue()
        }
        timer.resume()
        retryTimer = timer
    }

    // MARK: - Network Monitor

    private func startNetworkMonitor() {
        pathMonitor.pathUpdateHandler = { [weak self] path in
            let available = path.status == .satisfied
            if !self!.isNetworkAvailable && available {
                self?.trigger()
            }
            self?.isNetworkAvailable = available
        }
        pathMonitor.start(queue: networkQueue)
    }

    // MARK: - Callback Dispatch

    private func fireSuccessCallback(for task: TaskMeta) {
        let record = makeRecord(task)
        let box    = popCallback(for: task.uploadTaskId)
        mmLog("📤 → onReportSuccess  taskId=\(task.uploadTaskId) showId=\(task.showId)")
        DispatchQueue.main.async {
            box?.frameCallback?.onReportSuccess(record)
            box?.reportCallback?.onReportSuccess(record)
        }
    }

    private func fireFailureCallback(for task: TaskMeta, code: MMErrorCode,
                                     message: String, record: FrameRecord?) {
        let box = popCallback(for: task.uploadTaskId)
        mmLog("📤 → onReportFailure  taskId=\(task.uploadTaskId) code=\(code.rawValue)(\(code.errorName)) msg=\(message)")
        DispatchQueue.main.async {
            box?.frameCallback?.onReportFailure(
                errorCode: code.rawValue, errorName: code.errorName,
                message: message, record: record)
            box?.reportCallback?.onReportFailure(
                errorCode: code.rawValue, errorName: code.errorName,
                message: message, record: record)
        }
    }

    func fireSkipCallback(for record: FrameRecord, reason: String,
                          frameCallback: FrameCallback?,
                          reportCallback: ReportCallback?) {
        mmLog("📤 → onReportSkip  taskId=\(record.uploadTaskId) reason=\(reason)")
        DispatchQueue.main.async {
            frameCallback?.onReportSkip(record, reason: reason)
            reportCallback?.onReportSkip(record, reason: reason)
        }
    }

    private func popCallback(for uploadTaskId: String) -> CallbackBox? {
        callbackLock.lock()
        let box = callbackMap.removeValue(forKey: uploadTaskId)
        callbackLock.unlock()
        return box
    }

    private func makeRecord(_ task: TaskMeta) -> FrameRecord {
        FrameRecord(
            showId:        task.showId,
            uploadTaskId:  task.uploadTaskId,
            placementId:   task.placementId,
            style:         task.style,
            requestTimeMs: 0, fillTimeMs: 0,
            showTimeMs:    task.nextAttemptAt,
            imageUrl:      task.imageUrl,
            localFilePath: "",
            imageHash:     task.imageHash,
            createdAt:     task.createdAt
        )
    }
}
