import UIKit

/// MaterialMonitor SDK 入口单例
///
/// ## 使用方式
///
/// **初始化（AppDelegate.didFinishLaunching）：**
/// ```swift
/// MaterialMonitor.initialize(
///     application: application,
///     config: MaterialMonitorConfig.Builder()
///         .appId("your_app_id")
///         .appKey("your_app_key")
///         .build()
/// )
/// ```
///
/// **采集（广告展示回调中）：**
/// ```swift
/// func onBannerAdShow(_ bannerView: ATBannerView, adInfo: MMAdInfo) {
///     MaterialMonitor.shared.collectFrameFromAdContainer(
///         bannerView, adInfo: buildMMAdInfo(adInfo),
///         timing: AdTimingInput(...), options: FrameOptions(), callback: self
///     )
/// }
/// ```
///
/// - Note: `@param application` iOS 端暂未使用，保留以对齐 Android API
@objcMembers
public final class MaterialMonitor: NSObject {

    // MARK: - Singleton

    public static let shared = MaterialMonitor()
    private override init() { super.init() }

    // MARK: - Internal State

    /// SDK 生命周期状态
    private enum State {
        case idle       // 未初始化 / 已关闭
        case running    // initialize 后正常工作
    }

    private var config: MaterialMonitorConfig?
    private var store: TaskStore?
    private var scheduler: TaskScheduler?
    private let collector = DeviceInfoCollector()

    /// 当前生命周期状态（受 stateLock 保护）
    private var state: State = .idle

    /// 冷启扫描完成标记（扫描期间新入队请求缓冲到 pendingEnqueue）
    private var scanCompleted = false
    private var pendingEnqueue: [() -> Void] = []
    private let stateLock = NSLock()

    // MARK: - Initialize

    /// 仅采集落盘，不上传
    ///
    /// - Parameter application: iOS 端暂未使用，保留以对齐 Android API
    public static func initialize(application: UIApplication) {
        shared.setup(config: nil)
    }

    /// 全功能：采集 + 上传
    ///
    /// - Parameters:
    ///   - application: iOS 端暂未使用，保留以对齐 Android API
    ///   - config: SDK 配置（含上传地址、appId、appKey）
    public static func initialize(config: MaterialMonitorConfig) {
        shared.setup(config: config)
    }

    /// 关闭 SDK：停止全部功能并释放资源（定时器 / 网络监听 / 在途上传 / 回调注册表 / config）
    ///
    /// - 已落盘但未上传的记录**保留在沙盒**，下次 `initialize` 冷启扫描时继续上传
    /// - 调用后采集 / 上报方法均返回 `uninitialized`
    /// - 幂等：重复调用安全
    /// - 可重启：之后再次 `initialize` 可恢复全部功能
    public static func shutdown() {
        shared.teardown()
    }

    private func teardown() {
        stateLock.lock()
        guard state == .running else { stateLock.unlock(); return }   // 幂等 + 未初始化保护
        state = .idle
        let oldScheduler = scheduler
        scheduler = nil
        store     = nil
        config    = nil
        scanCompleted = false
        pendingEnqueue.removeAll()
        stateLock.unlock()

        mmLog("🛑 MaterialMonitor shutdown")
        oldScheduler?.teardown()
        _mmDebugLogging = false
    }

    private func setup(config: MaterialMonitorConfig?) {
        // 同步更新模块级 Debug 日志开关
        _mmDebugLogging = config?.debugPerfLogging ?? false

        let newStore = TaskStore()
        let newScheduler = TaskScheduler(store: newStore, config: config, collector: collector)

        // 重复 initialize：先释放旧调度器（停循环 / 取消定时器与监听 / 中断在途上传）
        stateLock.lock()
        let oldScheduler = scheduler
        self.config   = config
        store         = newStore
        scheduler     = newScheduler
        state         = .running
        scanCompleted = false
        pendingEnqueue.removeAll()
        stateLock.unlock()

        oldScheduler?.teardown()

        newStore.scanOnColdStart { [weak self] in
            guard let self else { return }
            self.stateLock.lock()
            self.scanCompleted = true
            let queued = self.pendingEnqueue
            self.pendingEnqueue.removeAll()
            self.stateLock.unlock()

            queued.forEach { $0() }
            newScheduler.trigger()
        }
    }

    // MARK: - Core Collection Methods

    /// 有容器广告截图（开屏 / Banner）
    ///
    /// - Parameters:
    ///   - view: 广告容器视图（必须已 addSubview 到 window）
    ///   - adInfo: 广告信息
    ///   - timing: 广告时间戳
    ///   - options: 截图参数（延迟、压缩限制等）
    ///   - callback: 采集 + 上报回调（主线程回调）
    /// - Note: 必须在主线程调用
    public func collectFrameFromAdContainer(
        _ view: UIView,
        adInfo: MMAdInfo,
        timing: AdTimingInput,
        options: FrameOptions,
        callback: FrameCallback
    ) {
        collectInternal(view: view, adInfo: adInfo, timing: timing,
                        options: options, creativeAssetUrl: nil, callback: callback)
    }

    /// 原生广告截图（附素材原始 URL）
    ///
    /// - Parameters:
    ///   - view: 业务方自渲染的广告容器视图
    ///   - creativeAssetUrl: 广告素材原始 URL（服务端可下载原图）
    /// - Note: 必须在主线程调用
    public func collectFrameFromNativeAdContainer(
        _ view: UIView,
        adInfo: MMAdInfo,
        timing: AdTimingInput,
        options: FrameOptions,
        creativeAssetUrl: String,
        callback: FrameCallback
    ) {
        let url = creativeAssetUrl.isEmpty ? nil : creativeAssetUrl
        collectInternal(view: view, adInfo: adInfo, timing: timing,
                        options: options, creativeAssetUrl: url, callback: callback)
    }

    /// 无容器广告截图（插屏 / 激励）—— 优先截取最顶层 `presentedViewController` 的视图，否则截取当前 `keyWindow`
    ///
    /// - Note: 必须在主线程调用
    public func collectFrameFromAdPage(
        adInfo: MMAdInfo,
        timing: AdTimingInput,
        options: FrameOptions,
        callback: FrameCallback
    ) {
        collectInternal(view: nil, adInfo: adInfo, timing: timing,
                        options: options, creativeAssetUrl: nil, callback: callback)
    }

    // MARK: - Report

    /// 手动入队上报已有的 FrameRecord
    ///
    /// - Parameters:
    ///   - record: 之前通过 `collectFrame*` 获得的采集记录
    ///   - adInfo: 原广告信息（用于补全 meta JSON）
    ///   - callback: 上报回调（可选；主线程回调）
    public func reportFrameRecord(
        _ record: FrameRecord,
        adInfo: MMAdInfo,
        callback: ReportCallback?
    ) {
        guard store != nil else {
            callback?.onReportFailure(
                errorCode: MMErrorCode.uninitialized.rawValue,
                errorName: MMErrorCode.uninitialized.errorName,
                message:   MMErrorCode.uninitialized.defaultMessage,
                record: nil
            )
            return
        }
        enqueueAfterScan { [weak self] in
            self?.enqueueRecord(record, adInfo: adInfo, callback: callback)
        }
    }

    // MARK: - Queue Management

    /// 立即触发所有待上传任务
    public func flushPendingUploads() {
        scheduler?.trigger()
    }

    /// 返回指定样式下所有非 dead 状态的记录（Swift 专属）
    @nonobjc
    public func getRecords(style: MaterialStyle) -> [FrameRecord] {
        var result: [FrameRecord] = []
        TaskStore.ioQueue.sync {
            result = store?.getRecords(style: style) ?? []
        }
        return result
    }

    /// 返回指定 adType 下所有非 dead 状态的记录（ObjC 友好）
    ///
    /// - Parameter adType: 广告类型整数（0=原生、1=激励、2=Banner、3=插屏、4=开屏）
    @objc(getRecordsForAdType:)
    public func getRecords(forAdType adType: Int) -> [FrameRecord] {
        guard let style = MaterialStyle(adType: adType) else { return [] }
        return getRecords(style: style)
    }

    /// 删除指定记录
    ///
    /// - `pending / retryWaiting`：立即删除文件对，不触发回调
    /// - `uploading`：标记为 dead，HTTP 请求完成后静默删除
    /// - `dead`：立即删除文件对
    public func deleteRecord(_ record: FrameRecord) {
        TaskStore.ioQueue.async { [weak self] in
            _ = self?.store?.deleteRecord(uploadTaskId: record.uploadTaskId)
        }
    }

    /// 获取当前 SDK 配置
    public func getConfig() -> MaterialMonitorConfig? {
        config
    }

    // MARK: - Private: Collect Core

    private func collectInternal(
        view: UIView?,
        adInfo: MMAdInfo,
        timing: AdTimingInput,
        options: FrameOptions,
        creativeAssetUrl: String?,
        callback: FrameCallback
    ) {
        // 必须主线程
        assert(Thread.isMainThread, "collectFrame* must be called on the main thread")

        // 局部包装：触发 onCollectFailure 前统一打印 SDK 内部日志
        func failCollect(code: MMErrorCode, msg: String) {
            mmLog("📤 → onCollectFailure  code=\(code.rawValue)(\(code.errorName)) msg=\(msg)")
            callback.onCollectFailure(errorCode: code.rawValue, errorName: code.errorName, message: msg)
        }

        // 初始化检查
        guard store != nil else {
            failCollect(code: .uninitialized, msg: MMErrorCode.uninitialized.defaultMessage)
            return
        }

        // adType 整数 → MaterialStyle
        guard let style = MaterialStyle(adType: adInfo.adType) else {
            failCollect(code: .paramsInvalid,
                        msg: "Unsupported adType: \(adInfo.adType). Expected: 0=Native 1=RewardedVideo 2=Banner 3=Interstitial 4=Splash")
            return
        }

        // 策略感知
        guard StrategyGate.shared.isEnabled(
            placementId:   adInfo.placementId,
            networkFirmId: adInfo.adNetworkFirmId,
            style:         style
        ) else {
            failCollect(code: .strategyDisabled,
                        msg: "Disabled for placement=\(adInfo.placementId) style=\(style.rawValue)")
            return
        }

        // View 有效性（提前校验）
        if let v = view, v.window == nil {
            failCollect(code: .viewInvalid, msg: "View not attached to a window")
            return
        }

        // 延迟截图
        let delay = Double(options.delayMs) / 1000.0
        let viewDesc = view.map { "\(type(of: $0))(\(Int($0.bounds.width))×\(Int($0.bounds.height)))" } ?? "adPage"
        mmLog("▶ 开始采集 style=\(style.rawValue) placement=\(adInfo.placementId) format=\(adInfo.format) view=\(viewDesc) delayMs=\(options.delayMs)")
        DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
            self?.performCapture(
                view: view, adInfo: adInfo, timing: timing,
                options: options, style: style,
                creativeAssetUrl: creativeAssetUrl, callback: callback
            )
        }
    }

    /// 无容器广告截图目标：沿 `keyWindow.rootViewController` 的 `presentedViewController` 链取最顶层，
    /// 若存在 modal 且其 `view` 已上屏则截取该 view；否则截取 `keyWindow`。
    private func resolveAdPageTargetView() -> UIView? {
        guard let window = UIApplication.shared.mm_keyWindow else { return nil }
        if let viewController = window.rootViewController?.presentedViewController {
            return viewController.view
        }
        return window
    }

    // MARK: - Private: Perform Capture（主线程）

    private func performCapture(
        view: UIView?,
        adInfo: MMAdInfo,
        timing: AdTimingInput,
        options: FrameOptions,
        style: MaterialStyle,
        creativeAssetUrl: String?,
        callback: FrameCallback
    ) {
        func failCollect(code: MMErrorCode, msg: String) {
            mmLog("📤 → onCollectFailure  code=\(code.rawValue)(\(code.errorName)) msg=\(msg)")
            callback.onCollectFailure(errorCode: code.rawValue, errorName: code.errorName, message: msg)
        }

        // 确定目标视图（无容器：顶层 presented VC 的 view，否则 keyWindow）
        let targetView: UIView
        if let v = view {
            guard v.window != nil else {
                failCollect(code: .viewInvalid, msg: "View detached from window during capture delay")
                return
            }
            targetView = v
        } else {
            guard let resolved = resolveAdPageTargetView() else {
                failCollect(code: .sceneInvalid, msg: "keyWindow not found")
                return
            }
            targetView = resolved
        }

        // 截图（主线程，UIKit 强约束）
        // AVPlayerLayer 路径只提取 CVPixelBuffer，CIContext 渲染移至 ioQueue（减轻主线程压力）
        let targetDesc = "targetView = \(targetView)(\(Int(targetView.bounds.width))×\(Int(targetView.bounds.height)))"
        mmLog("📸 开始截图 view=\(targetDesc)")
        guard let captureIntermediate = ImageProcessor.captureViewIntermediate(
            targetView, excludeViews: options.excludeViews
        ) else {
            failCollect(code: .collectFailed, msg: "captureView returned nil")
            return
        }
        mmLog("✅ 截图成功（中间结果已就绪，视频帧将在 ioQueue 完成 CIContext 渲染）")

        let uploadTaskId = UUID().uuidString
        let configCopy   = config
        let collectorRef = collector

        // 切到 IO 队列：CIContext 渲染（视频帧）+ 压缩 + pHash + 落盘 + 入队
        TaskStore.ioQueue.async { [weak self] in
            guard let self, let currentStore = self.store else { return }

            // 在 ioQueue 完成 CIContext 渲染（drawHierarchy 路径为空操作）
            guard let screenshot = captureIntermediate.toImage() else {
                mmLog("📤 → onCollectFailure  code=\(MMErrorCode.collectFailed.rawValue)(\(MMErrorCode.collectFailed.errorName)) msg=CaptureIntermediate.toImage() failed")
                DispatchQueue.main.async {
                    callback.onCollectFailure(
                        errorCode: MMErrorCode.collectFailed.rawValue,
                        errorName: MMErrorCode.collectFailed.errorName,
                        message:   "Frame rendering failed (toImage)"
                    )
                }
                return
            }
            mmLog("✅ 截图渲染完成 size=\(Int(screenshot.size.width * screenshot.scale))×\(Int(screenshot.size.height * screenshot.scale))px")

            // show_id 去重
            if currentStore.containsShowId(adInfo.showId) {
                mmLog("⏭ 跳过：show_id 已存在（duplicated_show_id）showId=\(adInfo.showId)")
                let record = FrameRecord(
                    showId: adInfo.showId, uploadTaskId: uploadTaskId,
                    placementId: adInfo.placementId, style: style,
                    requestTimeMs: timing.requestTimeMs,
                    fillTimeMs:    timing.fillTimeMs,
                    showTimeMs:    timing.showTimeMs,
                    imageUrl:      creativeAssetUrl,
                    localFilePath: "", imageHash: "",
                    createdAt:     Int64(Date().timeIntervalSince1970 * 1000)
                )
                DispatchQueue.main.async {
                    callback.onReportSkip(record, reason: "duplicated_show_id")
                }
                return
            }

            // 压缩
            mmLog("🗜 开始压缩 maxLongEdge=\(options.maxLongEdge) maxSizeKb=\(options.maxSizeKb)")
            guard let jpegData = ImageProcessor.compressAndScale(screenshot, options: options) else {
                mmLog("📤 → onCollectFailure  code=\(MMErrorCode.compressFailed.rawValue)(\(MMErrorCode.compressFailed.errorName)) msg=JPEG compression failed")
                DispatchQueue.main.async {
                    callback.onCollectFailure(
                        errorCode: MMErrorCode.compressFailed.rawValue,
                        errorName: MMErrorCode.compressFailed.errorName,
                        message:   "JPEG compression failed"
                    )
                }
                return
            }
            mmLog("✅ 压缩成功 size=\(jpegData.count / 1024)KB")

            // pHash
            let imageHash = PHashCalculator.calculate(jpegData)
            mmLog("🔑 pHash=\(imageHash)")

            // 采集设备信息 + 序列化 meta JSON（一次性，冷启复用）
            let deviceInfo = collectorRef.collect(
                idfa:       configCopy?.idfa,
                idfv:       configCopy?.idfv,
                sdkVersion: configCopy?.sdkVersion,
                caid:       configCopy?.caid
            )
            let metaJson = MetaBuilder.build(
                adInfo: adInfo, timing: timing, style: style,
                imageHash: imageHash,
                imageUrl:  creativeAssetUrl ?? "",
                deviceInfo: deviceInfo
            ) ?? ""

            // 落盘（.jpg）
            mmLog("💾 开始写盘…")
            guard let paths = currentStore.saveImage(
                jpegData, style: style,
                showTimeMs: timing.showTimeMs,
                uploadTaskId: uploadTaskId
            ) else {
                mmLog("📤 → onCollectFailure  code=\(MMErrorCode.collectFailed.rawValue)(\(MMErrorCode.collectFailed.errorName)) msg=Failed to save image to disk")
                DispatchQueue.main.async {
                    callback.onCollectFailure(
                        errorCode: MMErrorCode.collectFailed.rawValue,
                        errorName: MMErrorCode.collectFailed.errorName,
                        message:   "Failed to save image to disk"
                    )
                }
                return
            }
            mmLog("✅ 写盘成功 path=\(paths.relative)")

            let createdAt = Int64(Date().timeIntervalSince1970 * 1000)
            let record = FrameRecord(
                showId:        adInfo.showId,
                uploadTaskId:  uploadTaskId,
                placementId:   adInfo.placementId,
                style:         style,
                requestTimeMs: timing.requestTimeMs,
                fillTimeMs:    timing.fillTimeMs,
                showTimeMs:    timing.showTimeMs,
                imageUrl:      creativeAssetUrl,
                localFilePath: paths.absolute,
                imageHash:     imageHash,
                createdAt:     createdAt
            )

            // 写 sidecar JSON + 加入内存索引
            let sidecar = SidecarData(
                schemaVersion: 1,
                uploadTaskId:  uploadTaskId,
                showId:        adInfo.showId,
                placementId:   adInfo.placementId,
                networkFirmId: adInfo.adNetworkFirmId,
                style:         style.rawValue,
                imagePath:     paths.relative,
                imageHash:     imageHash,
                imageUrl:      creativeAssetUrl,
                metaJson:      metaJson,
                status:        TaskStatus.pending.rawValue,
                attemptCount:  0,
                nextAttemptAt: createdAt,
                lastErrorCode: nil, lastErrorMsg: nil,
                createdAt:     createdAt,
                updatedAt:     createdAt
            )
            currentStore.enqueue(sidecar)

            // 注册回调
            self.scheduler?.registerCallback(callback, for: uploadTaskId)

            // 通知业务方采集成功
            mmLog("📤 → onCollectSuccess  taskId=\(uploadTaskId) hash=\(record.imageHash)")
            DispatchQueue.main.async {
                callback.onCollectSuccess(record)
            }

            // 触发调度器
            self.scheduler?.trigger()
        }
    }

    // MARK: - Private: Manual Enqueue

    private func enqueueRecord(
        _ record: FrameRecord,
        adInfo: MMAdInfo,
        callback: ReportCallback?
    ) {
        guard let currentStore = store else { return }

        TaskStore.ioQueue.async { [weak self] in
            guard let self else { return }

            // show_id 去重
            if currentStore.containsShowId(record.showId) {
                DispatchQueue.main.async {
                    callback?.onReportSkip(record, reason: "duplicated_show_id")
                }
                return
            }

            // upload_task_id 去重
            if currentStore.tasks[record.uploadTaskId] != nil {
                DispatchQueue.main.async {
                    callback?.onReportSkip(record, reason: "duplicated_upload_task_id")
                }
                return
            }

            let configCopy   = self.config
            let collectorRef = self.collector
            let deviceInfo   = collectorRef.collect(
                idfa:       configCopy?.idfa,
                idfv:       configCopy?.idfv,
                sdkVersion: configCopy?.sdkVersion,
                caid:       configCopy?.caid
            )
            let style        = record.style
            let adTimingInput = AdTimingInput(
                requestTimeMs: record.requestTimeMs,
                fillTimeMs:    record.fillTimeMs,
                showTimeMs:    record.showTimeMs
            )
            let metaJson = MetaBuilder.build(
                adInfo: adInfo, timing: adTimingInput, style: style,
                imageHash: record.imageHash, imageUrl: record.imageUrl ?? "",
                deviceInfo: deviceInfo
            ) ?? ""

            let createdAt = Int64(Date().timeIntervalSince1970 * 1000)
            let relativePath = style.rawValue + "/" + URL(fileURLWithPath: record.localFilePath).lastPathComponent
            let sidecar = SidecarData(
                schemaVersion: 1,
                uploadTaskId:  record.uploadTaskId,
                showId:        record.showId,
                placementId:   record.placementId,
                networkFirmId: adInfo.adNetworkFirmId,
                style:         style.rawValue,
                imagePath:     relativePath,
                imageHash:     record.imageHash,
                imageUrl:      record.imageUrl,
                metaJson:      metaJson,
                status:        TaskStatus.pending.rawValue,
                attemptCount:  0,
                nextAttemptAt: createdAt,
                lastErrorCode: nil, lastErrorMsg: nil,
                createdAt:     createdAt,
                updatedAt:     createdAt
            )
            currentStore.enqueue(sidecar)

            if let cb = callback {
                self.scheduler?.registerReportCallback(cb, for: record.uploadTaskId)
            }
            self.scheduler?.trigger()
        }
    }

    // MARK: - Scan Buffer

    private func enqueueAfterScan(_ block: @escaping () -> Void) {
        stateLock.lock()
        if scanCompleted {
            stateLock.unlock()
            block()
        } else {
            pendingEnqueue.append(block)
            stateLock.unlock()
        }
    }
}
