// ATDemoViewController.swift
//
// 使用步骤：
//   1. pod install 后用 .xcworkspace 打开项目
//   2. 在 Xcode Build Settings → Objective-C Bridging Header 填入
//      MaterialMonitorDemo iOS/MaterialMonitorDemo-Bridging-Header.h
//   3. 替换下方 kAppID / kAppKey / 各广告位 Placement ID
//   4. 运行即可在日志区查看完整的广告加载 → 展示 → 采集链路

import UIKit
import Masonry        // AutoLayout DSL（ObjC category，需显式 import）
import SDWebImage     // 异步图片加载
import MJRefresh      // 下拉刷新
import MaterialMonitorSDK
 

// MARK: - ATDemoViewController

final class ViewController: UIViewController {

    // ──────────────────────────────────────────────────────────
    // MARK: 广告位 配置（替换为真实值）
    // ──────────────────────────────────────────────────────────
  
    private let kBannerPlacementID       = ""       
    private let kInterstitialPlacementID = ""       
    private let kRewardedPlacementID     = ""       
    private let kNativePlacementID       = ""       
    private let kSplashPlacementID       = ""       

    // ──────────────────────────────────────────────────────────
    // MARK: 时间戳缓存（ms）
    // ──────────────────────────────────────────────────────────

    private var requestTs: Int64 = 0   // 每次 tap 时记录
    private var fillTs:    Int64 = 0   // didFinishLoadingAD 时记录

    // ──────────────────────────────────────────────────────────
    // MARK: UI
    // ──────────────────────────────────────────────────────────

    private let scrollView  = UIScrollView()
    private let contentView = UIView()

    /// 共用广告展位（Banner / Native 共用，可垂直滑动）
    /// 高度为原 nativeHolder 的两倍（240pt），位置承接原 nativeHolder
    private let adsHolder: UIView = {
        let v = UIView()
        v.backgroundColor = UIColor(white: 0.95, alpha: 1)
        v.layer.cornerRadius = 8
        v.layer.borderColor = UIColor.darkGray.cgColor
        v.layer.borderWidth = 0.5
        v.clipsToBounds = true
        return v
    }()

    /// 共用展位内的滚动视图（垂直方向）
    private let adsScrollView: UIScrollView = {
        let sv = UIScrollView()
        sv.showsVerticalScrollIndicator = true
        sv.alwaysBounceVertical = true
        return sv
    }()

    /// 广告堆叠容器：每展示一个 Banner / Native 就 addArrangedSubview 追加到末尾
    private let adsStackView: UIStackView = {
        let sv = UIStackView()
        sv.axis = .vertical
        sv.spacing = 18
        sv.alignment = .fill
        return sv
    }()

    private let adsPlaceholder: UILabel = {
        let l = UILabel()
        l.text = "广告区域（Banner / Native 加载后自上而下追加，可滑动）"
        l.textColor = .systemGray
        l.font = .systemFont(ofSize: 12)
        l.textAlignment = .center
        l.numberOfLines = 2
        return l
    }()

    private let logTextView: UITextView = {
        let tv = UITextView()
        tv.isEditable = false
        tv.isSelectable = true
        tv.font = .systemFont(ofSize: 13)
        tv.textColor = UIColor(white: 0.2, alpha: 1)
        tv.backgroundColor = UIColor(white: 0.97, alpha: 1)
        tv.layer.cornerRadius = 8
        tv.layer.borderWidth = 0.5
        tv.layer.borderColor = UIColor.darkGray.cgColor
        return tv
    }()

    // ──────────────────────────────────────────────────────────
    // MARK: Lifecycle
    // ──────────────────────────────────────────────────────────

    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Material Monitor Demo"
        view.backgroundColor = .white
        setupUI() 
    }

 
    // ──────────────────────────────────────────────────────────
    // MARK: UI 搭建
    // ──────────────────────────────────────────────────────────

    private func setupUI() {
        view.addSubview(scrollView)
        scrollView.mas_makeConstraints { make in
            make?.top.equalTo()(self.view.mas_safeAreaLayoutGuideTop)
            make?.bottom.left().equalTo()(self.view)
            make?.width.equalTo()(self.view)
        }
        scrollView.addSubview(contentView)
        contentView.mas_makeConstraints { make in
            make?.edges.equalTo()(self.scrollView)
            make?.width.equalTo()(self.scrollView)
        }

        // 按钮区（顶部）
        let buttonStack = UIStackView()
        buttonStack.axis         = .vertical
        buttonStack.spacing      = 10
        buttonStack.distribution = .fillEqually
        contentView.addSubview(buttonStack)
        buttonStack.mas_makeConstraints { make in
            make?.top.equalTo()(self.contentView)?.offset()(16)
            make?.left.equalTo()(self.contentView)?.offset()(16)
            make?.right.equalTo()(self.contentView)?.offset()(-16)
        }
        let adButtons: [(String, Selector)] = [
            ("📢  加载 Banner         (ad_type=2)", #selector(tapBanner)),
            ("🎬  加载插屏 Interstitial  (ad_type=3)", #selector(tapInterstitial)),
            ("🏆  加载激励视频 Rewarded  (ad_type=1)", #selector(tapRewarded)),
            ("🖼   加载原生广告 Native   (ad_type=0)", #selector(tapNative)),
            ("🚀  加载开屏广告 Splash    (ad_type=4)", #selector(tapSplash)),
        ]
        adButtons.forEach { (title, sel) in
            buttonStack.addArrangedSubview(makeButton(title: title, action: sel))
        }

        // 共用广告展位（Banner / Native 共用；高度 240 = 原 nativeHolder 的 2 倍）
        contentView.addSubview(adsHolder)
        adsHolder.mas_makeConstraints { make in
            make?.top.equalTo()(buttonStack.mas_bottom)?.offset()(14)
            make?.left.equalTo()(self.contentView)?.offset()(16)
            make?.right.equalTo()(self.contentView)?.offset()(-16)
            make?.height.equalTo()(340)
        }
        // ScrollView（限制在 adsHolder 内部，垂直滚动）
        adsHolder.addSubview(adsScrollView)
        adsScrollView.mas_makeConstraints { make in
            make?.edges.equalTo()(self.adsHolder)
        }
        // StackView（自上而下垂直堆叠，宽度 = scrollView，高度由 arranged subviews 自动累加）
        adsScrollView.addSubview(adsStackView)
        adsStackView.mas_makeConstraints { make in
            make?.edges.equalTo()(self.adsScrollView)
            make?.width.equalTo()(self.adsScrollView)
        }
        // 占位文案（盖在 scrollView 之上，加载第一个广告后隐藏）
        adsHolder.addSubview(adsPlaceholder)
        adsPlaceholder.mas_makeConstraints { make in
            make?.center.equalTo()(self.adsHolder)
            make?.left.equalTo()(self.adsHolder)?.offset()(12)
            make?.right.equalTo()(self.adsHolder)?.offset()(-12)
        }

        // 日志区（下拉清空）
        contentView.addSubview(logTextView)
        logTextView.mas_makeConstraints { make in
            make?.top.equalTo()(self.adsHolder.mas_bottom)?.offset()(14)
            make?.left.equalTo()(self.contentView)?.offset()(16)
            make?.right.equalTo()(self.contentView)?.offset()(-16)
            make?.height.equalTo()(280)
            make?.bottom.equalTo()(self.contentView)?.offset()(-24)
        }
        logTextView.mj_header = MJRefreshNormalHeader { [weak self] in
            self?.logTextView.text = ""
            self?.logTextView.mj_header?.endRefreshing()
            self?.log("日志已清空  ↑ 下拉可再次清空")
        }
        (logTextView.mj_header as? MJRefreshNormalHeader)?.lastUpdatedTimeLabel?.isHidden = true
//        logTextView.mj_header?.setTitle("↓ 下拉清空日志", for: .idle)
//        logTextView.mj_header?.setTitle("松开清空", for: .pulling)
//        logTextView.mj_header?.setTitle("清空中…", for: .refreshing)
    }

    // ──────────────────────────────────────────────────────────
    // MARK: 按钮动作 —— 发起加载
    // ──────────────────────────────────────────────────────────

    @objc private func tapBanner() {
        requestTs = nowMs()
        log("━━━ [Banner] 发起加载  placementId=\(kBannerPlacementID)")
        
    }

    @objc private func tapInterstitial() {
        requestTs = nowMs()
        log("━━━ [Interstitial] 发起加载  placementId=\(kInterstitialPlacementID)")
        
    }

    @objc private func tapRewarded() {
        requestTs = nowMs()
        log("━━━ [Rewarded] 发起加载  placementId=\(kRewardedPlacementID)")
        
    }

    @objc private func tapNative() {
        requestTs = nowMs()
        log("━━━ [Native] 发起加载  placementId=\(kNativePlacementID)")
        
    }

    @objc private func tapSplash() {
        requestTs = nowMs()
        log("━━━ [Splash] 发起加载  placementId=\(kSplashPlacementID)")
        // Splash 使用独立的带 containerView 的加载接口
         
    }

    // ──────────────────────────────────────────────────────────
    // MARK: 广告展示（加载成功后调用）
    // ──────────────────────────────────────────────────────────

    private func showBannerAd() {
        guard Thread.isMainThread else {
            DispatchQueue.main.async { [weak self] in self?.showBannerAd() }
            return
        }
        
        let bv = UIView() /// banner 广告view
        appendAd(view: bv, fixedHeight: 60)
        log("👁 [Banner] BannerView 已追加到共用展位，等待 didShowAd 回调")
    }

    private func showInterstitialAd() {
        guard Thread.isMainThread else {
            DispatchQueue.main.async { [weak self] in self?.showInterstitialAd() }
            return
        }
        log("→ [Interstitial] 调用展示接口")
        
    }

    private func showRewardedAd() {
        guard Thread.isMainThread else {
            DispatchQueue.main.async { [weak self] in self?.showRewardedAd() }
            return
        }
        log("→ [Rewarded] 调用展示接口")
        
    }

    private func showNativeAd() {
        guard Thread.isMainThread else {
            DispatchQueue.main.async { [weak self] in self?.showNativeAd() }
            return
        }
        
        let nv = UIView() /// natiview广告view
        let nativeHeight = 250.0
        appendAd(view: nv, fixedHeight: nativeHeight)
        log("🖼 [Native] ATNativeADView 已追加到共用展位，等待 didShowNativeAd 回调")
    }

    /// 把广告 view 追加到共用展位 stackView 末尾，固定高度，自上而下排列
    /// 第一次追加时自动隐藏占位文案；追加完成后自动滚到底部展示最新广告
    private func appendAd(view ad: UIView, fixedHeight: CGFloat) {
        if !adsPlaceholder.isHidden { adsPlaceholder.isHidden = true }

        let wrapper = UIView()
        wrapper.backgroundColor = UIColor(white: 0.98, alpha: 1)
        wrapper.clipsToBounds = true

        adsStackView.addArrangedSubview(wrapper)
        wrapper.mas_makeConstraints { make in
            make?.height.equalTo()(fixedHeight)
        }

        // 触发布局，让 wrapper 拿到准确 bounds 再设置广告 view 的 frame
        contentView.layoutIfNeeded()

        ad.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        ad.frame = wrapper.bounds
        wrapper.addSubview(ad)

        // 滚到底部，自动展示刚追加的广告
        scrollAdsToBottom()
    }

    /// 把共用展位 scrollView 滚动到底部（带动画）
    /// 下一个 runloop 再执行，确保 stackView 重排完成、contentSize 已刷新
    private func scrollAdsToBottom() {
        DispatchQueue.main.async { [weak self] in
            guard let self else { return }
            let sv = self.adsScrollView
            let bottomY = sv.contentSize.height - sv.bounds.height
            guard bottomY > 0 else { return }   // 内容还没超过可视高度，无需滚动
            sv.setContentOffset(CGPoint(x: 0, y: bottomY), animated: true)
        }
    }

    private func showSplashAd() {
        guard Thread.isMainThread else {
            DispatchQueue.main.async { [weak self] in self?.showSplashAd() }
            return
        }
        log("→ [Splash] 调用展示接口")
        
        
    }

    // ──────────────────────────────────────────────────────────
    // MARK: MaterialMonitor 采集入口
    // ──────────────────────────────────────────────────────────

    /// 容器广告截图采集（Banner / Native）
    ///
    /// 广告格式由 `extra[kATADDelegateExtraADSourceTypeKey]`（ATAdFormat 整数）自动推导，
    /// 无需调用方手动传入格式字符串。
    private func collectContainer(_ container: UIView,
                                  placementId: String, showTs: Int64,
                                  extra: [AnyHashable: Any]?) {
        let adInfo = buildAdInfo(placementId: placementId, showTs: showTs, extra: extra)
        log("📸 [Debug log] collectFrameFromAdContainer  adType=\(adInfo.adType)")
        MaterialMonitor.shared.collectFrameFromAdContainer(
            container, adInfo: adInfo,
            timing: adInfo.timing,
            // Native（adType=0）素材较复杂，给更多时间等待渲染
            options: FrameOptions(delayMs: adInfo.adType == 0 ? 300 : 200),
            callback: self)
    }

    /// 全屏广告截图采集（Interstitial / RewardedVideo / Splash）
    ///
    /// 广告格式由 `extra[kATADDelegateExtraADSourceTypeKey]`（ATAdFormat 整数）自动推导。
    private func collectPage(placementId: String,
                             showTs: Int64, extra: [AnyHashable: Any]?) {
        let adInfo = buildAdInfo(placementId: placementId, showTs: showTs, extra: extra)
        log("📸 [Debug log] collectFrameFromAdPage  adType=\(adInfo.adType)")
        MaterialMonitor.shared.collectFrameFromAdPage(
            adInfo: adInfo,
            timing: adInfo.timing,
            options: FrameOptions(delayMs: 3000),
            callback: self)
    }

    // ──────────────────────────────────────────────────────────
    // MARK: Helpers
    // ──────────────────────────────────────────────────────────

    private func nowMs() -> Int64 {
        Int64(Date().timeIntervalSince1970 * 1000)
    }

    private func log(_ msg: String) {
        DispatchQueue.main.async { [weak self] in
            guard let self else { return }
            let ts = DateFormatter.localizedString(from: Date(), dateStyle: .none, timeStyle: .medium)
            self.logTextView.text = (self.logTextView.text ?? "") + "[\(ts)] \(msg)\n"
            let offset = max(0, self.logTextView.contentSize.height - self.logTextView.bounds.height)
            self.logTextView.setContentOffset(CGPoint(x: 0, y: offset), animated: false)
        }
    }

    private func makeButton(title: String, action: Selector) -> UIButton {
        let btn = UIButton(type: .system)
        btn.setTitle(title, for: .normal)
        btn.titleLabel?.font = .systemFont(ofSize: 13, weight: .medium)
        btn.contentHorizontalAlignment = .left
        btn.contentEdgeInsets = UIEdgeInsets(top: 0, left: 14, bottom: 0, right: 0)
        btn.backgroundColor = UIColor.systemBlue.withAlphaComponent(0.06)
        btn.setTitleColor(.systemBlue, for: .normal)
        btn.layer.cornerRadius = 8
        btn.layer.borderWidth = 1
        btn.layer.borderColor = UIColor.systemBlue.withAlphaComponent(0.25).cgColor
        btn.mas_makeConstraints { make in make?.height.equalTo()(44) }
        btn.addTarget(self, action: action, for: .touchUpInside)
        return btn
    }

    /// 从 广告回调内容中提取真实广告信息，构建 MMAdInfo
    ///
    /// - `kATADDelegateExtraRequestIDKey`      → requestId
    /// - `kATADDelegateExtraIDKey`             → showId（impression 唯一 ID）
    /// - `kATADDelegateExtraAdSourceIdKey`     → adSourceId
    /// - `kATADDelegateExtraNetworkFirmIdKey`  → adNetworkFirmId
    /// - `kATADDelegateExtraCreativeIDKey`     → creativeId
    /// - `kATADDelegateExtraAdSourcePriceKey`  → eCPM
    /// - `kATADDelegateExtraADSourceTypeKey`   → adType（ATAdFormat：0=Native 1=Rewarded 2=Banner 3=Interstitial 4=Splash）
    private func buildAdInfo(placementId: String,
                             showTs: Int64, extra: [AnyHashable: Any]?) -> MMAdInfo {
        let requestId     = extra?[""] as? String ?? UUID().uuidString
        let showId        = extra?[""] as? String ?? UUID().uuidString
        let adSourceId    = extra?[""] as? String ?? ""
        let networkFirmId = (extra?[""] as? NSNumber)?.intValue ?? 0
        let creativeId    = extra?[""] as? String
        let ecpm          = extra?[""] as? NSNumber
        // ATAdFormat 整数：0=Native 1=RewardedVideo 2=Banner 3=Interstitial 4=Splash
        let adType        = (extra?[""] as? NSNumber)?.intValue ?? -1

        return MMAdInfo(
            requestId:        requestId,
            showId:           showId,
            placementId:      placementId,
            adSourceId:       adSourceId,
            adNetworkFirmId:  networkFirmId,
            adType:           adType,
            requestTimestamp: requestTs,
            fillTimestamp:    fillTs,
            showTimestamp:    showTs,
            creativeId:       creativeId,
            ecpm:             ecpm
        )
    }
}

// ──────────────────────────────────────────────────────────────
// MARK: - ATAdLoadingDelegate + 各广告类型 Delegate
//
// ATBannerDelegate / ATInterstitialDelegate / ATRewardedVideoDelegate /
// ATNativeADDelegate / ATSplashDelegate 全部继承 ATAdLoadingDelegate，
// 因此 didFinishLoadingAD / didFailToLoadAD 只需实现一次，
// 通过 placementID 区分广告类型后分发展示。
// ──────────────────────────────────────────────────────────────

extension ViewController {

    // MARK: ATAdLoadingDelegate（所有类型共用）

    func didFinishLoadingAD(withPlacementID placementID: String) {
        fillTs = nowMs()
        log("✅ 加载成功  placementId=\(placementID)")
        DispatchQueue.main.async { [weak self] in
            guard let self else { return }
            switch placementID {
            case self.kBannerPlacementID:       self.showBannerAd()
            case self.kInterstitialPlacementID: self.showInterstitialAd()
            case self.kRewardedPlacementID:     self.showRewardedAd()
            case self.kNativePlacementID:       self.showNativeAd()
            case self.kSplashPlacementID:       self.showSplashAd()
            default: break
            }
        }
    }

    func didFailToLoadAD(withPlacementID placementID: String, error: Error) {
        log("❌ 加载失败  placementId=\(placementID)  error=\(error.localizedDescription)")
    }

    // MARK: BannerDelegate

    func bannerView(_ bannerView: BannerView,
                    didShowAdWithPlacementID placementID: String,
                    extra: [AnyHashable: Any]) {
        let showTs = nowMs()
        log("👁 [Banner] 展示成功 → 触发采集")
        collectContainer(bannerView, placementId: placementID,
                         showTs: showTs, extra: extra)
    }

    func bannerView(_ bannerView: BannerView,
                    didClickWithPlacementID placementID: String,
                    extra: [AnyHashable: Any]) {
        log("🖱 [Banner] 用户点击")
    }

    // MARK: ATInterstitialDelegate

    func interstitialDidShow(forPlacementID placementID: String,
                             extra: [AnyHashable: Any]) {
        let showTs = nowMs()
        log("👁 [Interstitial] 展示成功 → 触发采集")
        collectPage(placementId: placementID, showTs: showTs, extra: extra)
    }

    func interstitialDidClick(forPlacementID placementID: String,
                              extra: [AnyHashable: Any]) {
        log("🖱 [Interstitial] 用户点击")
    }

    func interstitialDidClose(forPlacementID placementID: String,
                              extra: [AnyHashable: Any]) {
        log("✖ [Interstitial] 已关闭")
    }

    // MARK: ATRewardedVideoDelegate

    func rewardedVideoDidStartPlaying(forPlacementID placementID: String,
                                      extra: [AnyHashable: Any]) {
        let showTs = nowMs()
        log("▶ [Rewarded] 开始播放 → 触发采集")
        collectPage(placementId: placementID, showTs: showTs, extra: extra)
    }

    func rewardedVideoDidEndPlaying(forPlacementID placementID: String,
                                    extra: [AnyHashable: Any]) {
        log("⏹ [Rewarded] 播放结束")
    }

    func rewardedVideoDidClick(forPlacementID placementID: String,
                               extra: [AnyHashable: Any]) {
        log("🖱 [Rewarded] 用户点击")
    }

    func rewardedVideoDidClose(forPlacementID placementID: String,
                               rewarded: Bool,
                               extra: [AnyHashable: Any]) {
        log("✖ [Rewarded] 已关闭  rewarded=\(rewarded)")
    }

    func rewardedVideoDidRewardSuccess(forPlacemenID placementID: String,
                                       extra: [AnyHashable: Any]) {
        log("🎁 [Rewarded] 已发放奖励")
    }

    // MARK: NativeADDelegate

    func didShowNativeAd(in adView: NativeADView,
                         placementID: String,
                         extra: [AnyHashable: Any]) {
        let showTs = nowMs()
        log("👁 [Native] 展示成功 → 触发采集")
        // 直接用回调参数 adView，不再依赖单例变量
        collectContainer(adView, placementId: placementID,
                         showTs: showTs, extra: extra)
    }

    func didClickNativeAd(in adView: NativeADView,
                          placementID: String,
                          extra: [AnyHashable: Any]) {
        log("🖱 [Native] 用户点击")
    }

    // MARK: ATSplashDelegate

    func splashDidShow(forPlacementID placementID: String,
                       extra: [AnyHashable: Any]) {
        let showTs = nowMs()
        log("👁 [Splash] 展示成功 → 触发采集")
        collectPage(placementId: placementID, showTs: showTs, extra: extra)
    }

    func splashDidClick(forPlacementID placementID: String,
                        extra: [AnyHashable: Any]) {
        log("🖱 [Splash] 用户点击")
    }

    func splashDidClose(forPlacementID placementID: String,
                        extra: [AnyHashable: Any]) {
        log("✖ [Splash] 已关闭")
    }
}

// ──────────────────────────────────────────────────────────────
// MARK: - FrameCallback（MaterialMonitor 采集/上报回调）
// ──────────────────────────────────────────────────────────────

extension ViewController: FrameCallback {

    func onCollectSuccess(_ record: FrameRecord) {
        log("✅ [Debug log] 采集成功  hash=\(record.imageHash)  taskId=\(record.uploadTaskId)")
    }

    func onCollectFailure(errorCode: Int, errorName: String, message: String) {
        log("❌ [Debug log] 采集失败  [\(errorCode)/\(errorName)] \(message)")
    }

    func onReportSuccess(_ record: FrameRecord) {
        log("📤 [Debug log] 上报成功  taskId=\(record.uploadTaskId)")
    }

    func onReportSkip(_ record: FrameRecord, reason: String) {
        log("⏭ [Debug log] 上报跳过  reason=\(reason)")
    }

    func onReportFailure(errorCode: Int, errorName: String, message: String, record: FrameRecord?) {
        log("❌ [Debug log] 上报失败  [\(errorCode)/\(errorName)] \(message)")
    }
}

// ──────────────────────────────────────────────────────────────
// MARK: - MMAdInfo timing 便捷扩展
// ──────────────────────────────────────────────────────────────

private extension MMAdInfo {
    var timing: AdTimingInput {
        AdTimingInput(requestTimeMs: requestTimestamp,
                      fillTimeMs:   fillTimestamp,
                      showTimeMs:   showTimestamp)
    }
}
