// Copyright 2022, the Flutter project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'dart:math';

import 'package:applovin_max/applovin_max.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:logging/logging.dart';
import 'package:puzzleweave/ads/ad_helper.dart';
import 'package:puzzleweave/firebase/adjust_helper.dart';
import 'package:puzzleweave/firebase/firebase_helper.dart';

import '../persistence/persistence.dart';
import '../remote_config/remote_config.dart';
import '../statistics/statistics.dart';

final Logger _log = Logger('ApplovinAdsController');

const int bannerReportDuration = 3 * 60 * 1000; // banner paid 3分钟报一次

/// Allows showing ads. A facade for `package:google_mobile_ads`.
class ApplovinAdsController {
  final BuildContext context;

  late double bannerPaidValueMicros; // banner广告累计收益
  late int lastBannerPaidReportTimestamp; // 上次banner广告收益上报时间戳，用于控制banner广告收益上报频率
  late double allPaidValueMicros; // 所有广告累计收益
  late double revenueThreshold; // 广告累计上报阈值

  bool _hasInit = false;
  bool get hasInit => _hasInit;

  final Completer<bool> completer = Completer();

  /// Creates an [ApplovinAdsController] that wraps around a [MobileAds] [instance].
  ///
  /// Example usage:
  ///
  ///     var controller = ApplovinAdsController(MobileAds.instance);
  ApplovinAdsController(this.context);

  void dispose() {}

  /// Initializes the injected [MobileAds.instance].
  Future<void> initialize() async {
    if (_hasInit) return;

    _log.info('AppLovinMAX.initialize...');

    // 1. 开启合规流开关
    AppLovinMAX.setTermsAndPrivacyPolicyFlowEnabled(true);

    // 2. 设置你的隐私政策和用户协议链接（必须是有效的 URL）
    AppLovinMAX.setPrivacyPolicyUrl("https://longreachai.net/game/privacy_policy.html");
    AppLovinMAX.setTermsOfServiceUrl("https://longreachai.net/game/terms_of_service.html");

    // 3. 然后再初始化 SDK
    MaxConfiguration? sdkConfiguration = await AppLovinMAX.initialize(AdHelper.applovinSdkKey);

    _log.info('AppLovinMAX.initialize success!');

    lastBannerPaidReportTimestamp = Persistence().lastBannerPaidReportTimestamp;
    bannerPaidValueMicros = Persistence().bannerPaidValueMicros;
    allPaidValueMicros = Persistence().allPaidValueMicros;
    revenueThreshold = RemoteConfig().adRevenueThreshold;
    if (revenueThreshold <= 0.0) revenueThreshold = 0.01;

    initializeBannerAds();

    initializeInterstitialAds();

    initializeRewardedAd();

    completer.complete(true);

    _hasInit = true;
  }

  void initializeBannerAds() {
    AppLovinMAX.setBannerExtraParameter(AdHelper.applovinBannerAdUnitId, "adaptive_banner", "true");
    // MAX automatically sizes banners to 320×50 on phones and 728×90 on tablets
    // 移除这一行，避免与 MaxAdView Widget 冲突！
    // AppLovin MAX 在 Flutter 中的最佳实践是只使用 MaxAdView Widget，让 Flutter 来管理 Banner 视图的布局和生命周期
    // 同时调用 AppLovinMAX.createBanner (原生创建) 和渲染 MaxAdView (Flutter Widget)，特别是在 iOS 上，原生层可能会意外地创建了一个透明的全屏视图来管理广告
    // AppLovinMAX.createBanner(AdHelper.applovinBannerAdUnitId, AdViewPosition.bottomCenter);
  }

  Widget get bannerAdWidget => MaxAdView(
    adUnitId: AdHelper.applovinBannerAdUnitId,
    adFormat: AdFormat.banner,
    placement: 'banner',
    extraParameters: const {'adaptive_banner': 'true'},
    listener: AdViewAdListener(
      onAdLoadedCallback: (ad) {
        // // 广告加载成功后， ad.size 包含实际的宽度和高度 (dp)
        // double? widthDp = ad.size?.width;
        // double? heightDp = ad.size?.height;
        // if (heightDp != null) {
        //   context.read<Device>().bannerHeight = heightDp;
        // }
        // _log.info('banner广告 width = $widthDp, height = $heightDp');
        _log.info(() => 'applovin banner Ad loaded: ${ad.hashCode}');
      },
      onAdLoadFailedCallback: (adUnitId, error) {
        _log.warning('applovin banner Ad failedToLoad: $error');
      },
      onAdClickedCallback: (ad) {
        _log.info('applovin banner Ad click registered');
      },
      onAdExpandedCallback: (ad) {
        _log.info('applovin banner Ad expanded');
      },
      onAdCollapsedCallback: (ad) {
        _log.info('applovin banner Ad collaspsed');
      },
      onAdRevenuePaidCallback: (ad) {
        _log.info('woooooooooo, applovin banner paid event: revenue: ${ad.revenue} precision: ${ad.revenuePrecision}');
        if (ad.revenue > 0) {
          onAdRevenuePaid(ad); // revenue 单位转化为 valueMicro，以便和admod一致
        }
      },
    ),
  );

  // revenue 回调处理，所有广告类型统一在此处理
  onAdRevenuePaid(MaxAd ad) async {
    _log.info('report ad revenue paid: ${ad.revenue}');

    Map<String, Object> params = {
      "ad_platform": 'appLovin',
      "ad_source": ad.networkName,
      "ad_format": ad.placement,
      "ad_unit_name": ad.adUnitId,
      "value": ad.revenue,
      "currency": "USD",
    };

    // for ARO : ad_impression 上报给firebase
    FirebaseHelper.logEvent("ad_impression", params);
    // for Taichi
    FirebaseHelper.logEvent("Ad_Impression_Revenue", params);

    // 累计超过0.01 USD 上报 Total_Ads_Revenue_001
    double previousTroasCache = Persistence().tRoasCache;
    double currentTroasCache = previousTroasCache + ad.revenue;
    if (currentTroasCache >= revenueThreshold) {
      FirebaseHelper.logEvent("Total_Ads_Revenue_001", {"value": currentTroasCache, "currency": "USD"});
      Persistence().tRoasCache = 0; // 缓存清零
    } else {
      Persistence().tRoasCache = currentTroasCache;
    }

    // adRevenue 事件上报给adjust
    AdjustHelper.trackAdRevenue(ad.placement, 1, ad.revenue, 'USD');

    // revenue 埋点时间上报给自主BI平台
    Statistics.postEvent({
      "project_id": Persistence().projectId,
      "user_id": Persistence().uuid,
      "library_name": Persistence().libraryName,
      "library_version": Persistence().packageVersion,
      "name": 'revenue',
      "tab_source": adSrc,
      "sku_id": skuId,
      "ad_src": adSrc,
      "ad_type": ad.placement,
      "ad_rev": ad.revenue,
    });
  }

  ////////////////////////////  interstitialAd /////////////////////////////
  final int _maxExponentialRetryCount = 6;
  var _interstitialRetryAttempt = 0;
  ValueNotifier<AdState> interstitialAdState = ValueNotifier(AdState.initial);

  void initializeInterstitialAds() {
    AppLovinMAX.setInterstitialListener(
      InterstitialListener(
        onAdLoadedCallback: (ad) {
          // Interstitial ad is ready to show. AppLovinMAX.isInterstitialReady(_interstitial_ad_unit_ID) now returns 'true'.
          _log.info('Interstitial ad loaded from ${ad.networkName}');
          // Reset retry attempt
          _interstitialRetryAttempt = 0;
          interstitialAdState.value = AdState.ready;
        },
        onAdLoadFailedCallback: (adUnitId, error) {
          // Interstitial ad failed to load.
          // AppLovin recommends that you retry with exponentially higher delays up to a maximum delay (in this case 64 seconds).
          _interstitialRetryAttempt = _interstitialRetryAttempt + 1;
          if (_interstitialRetryAttempt > _maxExponentialRetryCount) return;
          int retryDelay = pow(2, min(_maxExponentialRetryCount, _interstitialRetryAttempt)).toInt();

          if (kDebugMode) {
            _log.info('Interstitial ad failed to load with code ${error.code} - retrying in ${retryDelay}s');
          }

          Future.delayed(Duration(milliseconds: retryDelay * 1000), () {
            AppLovinMAX.loadInterstitial(AdHelper.applovinInterstitialAdUnitId);
          });
        },
        onAdDisplayedCallback: (ad) {
          _log.info('interstitialAd displayed');
          interstitialAdState.value = AdState.showing; // 广告成功展示
        },
        onAdDisplayFailedCallback: (ad, error) {
          _log.info('interstitialAd display failed');
          interstitialAdState.value = AdState.error; // 广告显示异常
        },
        onAdClickedCallback: (ad) {},
        onAdHiddenCallback: (ad) {
          _log.info('interstitialAd hidden');
          interstitialAdState.value = AdState.dismissed; // 广告关闭
          AppLovinMAX.loadInterstitial(AdHelper.applovinInterstitialAdUnitId);
        },
        onAdRevenuePaidCallback: (ad) {
          _log.info('woooooooooo, applovin interstitial paid event: revenue: ${ad.revenue}, precision: ${ad.revenuePrecision}');
          if (ad.revenue > 0) {
            onAdRevenuePaid(ad); // revenue 单位转化为 valueMicro，以便和admod一致
          }
        },
      ),
    );

    // Load the first interstitial.
    Future.delayed(const Duration(seconds: 3), () {
      loadInterstitialAd();
    });
  }

  void loadInterstitialAd() async {
    if (!_hasInit) return;

    bool? isInit = await AppLovinMAX.isInitialized();
    if (isInit == null || !isInit) {
      return;
    }
    bool isReady = (await AppLovinMAX.isInterstitialReady(AdHelper.applovinInterstitialAdUnitId))!;
    if (isReady) {
      _log.info("applovin interstitial ad already ready, no need to load!");
      return;
    }

    AppLovinMAX.loadInterstitial(AdHelper.applovinInterstitialAdUnitId);
  }

  Future<bool> isInterstitialAdReady() async {
    if (!_hasInit) return false;

    try {
      bool? isInit = await AppLovinMAX.isInitialized();
      if (isInit == null || !isInit) {
        return false;
      }
      bool isReady = (await AppLovinMAX.isInterstitialReady(AdHelper.applovinInterstitialAdUnitId))!;
      return isReady;
    } catch (e) {
      _log.warning('isInterstitialReady error: $e');
    }
    return false;
  }

  String adSrc = "";
  String skuId = "";
  showInterstitialAd({String adSrc = "", String skuId = ""}) async {
    this.adSrc = adSrc;
    this.skuId = skuId;
    try {
      bool? isInit = await AppLovinMAX.isInitialized();
      if (isInit == null || !isInit) {
        return;
      }
      bool isReady = (await AppLovinMAX.isInterstitialReady(AdHelper.applovinInterstitialAdUnitId))!;
      if (isReady) {
        AppLovinMAX.showInterstitial(AdHelper.applovinInterstitialAdUnitId, placement: 'inters');
      } else {
        AppLovinMAX.loadInterstitial(AdHelper.applovinInterstitialAdUnitId);
      }
    } on PlatformException catch (e) {
      _log.warning('PlatformException: $e');
    } catch (e) {
      _log.warning('showInterstitialAd: $e');
    }
  }

  ////////////////////////////  rewardedAd /////////////////////////////
  ValueNotifier<AdState> rewardedAdState = ValueNotifier(AdState.initial);
  var _rewardedAdRetryAttempt = 0;

  void initializeRewardedAd() {
    AppLovinMAX.setRewardedAdListener(
      RewardedAdListener(
        onAdLoadedCallback: (ad) {
          // Rewarded ad is ready to show. AppLovinMAX.isRewardedAdReady(_rewarded_ad_unit_ID) now returns 'true'.
          _log.info('Rewarded ad loaded from ${ad.networkName}');
          // Reset retry attempt
          _rewardedAdRetryAttempt = 0;
          rewardedAdState.value = AdState.ready;
        },
        onAdLoadFailedCallback: (adUnitId, error) {
          // Rewarded ad failed to load.
          // AppLovin recommends that you retry with exponentially higher delays up to a maximum delay (in this case 64 seconds).
          _rewardedAdRetryAttempt = _rewardedAdRetryAttempt + 1;
          if (_rewardedAdRetryAttempt > _maxExponentialRetryCount) return;
          int retryDelay = pow(2, min(_maxExponentialRetryCount, _rewardedAdRetryAttempt)).toInt();
          _log.info('Rewarded ad failed to load with code ${error.code} - retrying in ${retryDelay}s');

          Future.delayed(Duration(milliseconds: retryDelay * 1000), () {
            AppLovinMAX.loadRewardedAd(AdHelper.applovinRewardedAdUnitId);
          });
        },
        onAdDisplayedCallback: (ad) {
          _log.info('rewardedAd displayed');
          rewardedAdState.value = AdState.showing; // 广告成功展示
        },
        onAdDisplayFailedCallback: (ad, error) {
          _log.info('rewardedAd display failed');
          rewardedAdState.value = AdState.error; // 广告显示异常
          rewardCallback = null;
        },
        onAdClickedCallback: (ad) {},
        onAdHiddenCallback: (ad) {
          _log.info('rewardedAd hide');
          rewardedAdState.value = AdState.dismissed; // 广告关闭
          rewardCallback = null;
          AppLovinMAX.loadRewardedAd(AdHelper.applovinRewardedAdUnitId);
        },
        onAdReceivedRewardCallback: (ad, reward) {
          _log.info('rewardedAd receive reward: $reward');
          rewardCallback?.call(ad, reward);
        },
        onAdRevenuePaidCallback: (ad) {
          _log.info('woooooooooo, applovin rewarded paid event: revenue: ${ad.revenue} precision: ${ad.revenuePrecision}');
          if (ad.revenue > 0) {
            onAdRevenuePaid(ad); // revenue 单位转化为 valueMicro，以便和admod一致
          }
        },
      ),
    );

    // Load the first reward ad.
    // loadRewardedAd();
    Future.delayed(const Duration(seconds: 3), () {
      loadRewardedAd();
    });
  }

  void loadRewardedAd() async {
    if (!_hasInit) return;

    bool? isInit = await AppLovinMAX.isInitialized();
    if (isInit == null || !isInit) {
      _log.warning("applovin not initialized yet!");
      return;
    }

    bool isReady = (await AppLovinMAX.isRewardedAdReady(AdHelper.applovinRewardedAdUnitId))!;
    if (isReady) {
      _log.info("applovin reward ad already ready, no need to load!");
      return;
    }

    AppLovinMAX.loadRewardedAd(AdHelper.applovinRewardedAdUnitId);
  }

  Future<bool> isRewardedAdReady() async {
    if (!_hasInit) return false;

    try {
      bool? isInit = await AppLovinMAX.isInitialized();
      if (isInit == null || !isInit) {
        return false;
      }
      bool isReady = (await AppLovinMAX.isRewardedAdReady(AdHelper.applovinRewardedAdUnitId))!;
      return isReady;
    } catch (e) {
      _log.warning('isInterstitialReady error: $e');
    }
    return false;
  }

  Function(MaxAd, MaxReward)? rewardCallback;
  Future<bool> showRewardedlAd({required onUserEarnedReward, String adSrc = "", String skuId = ""}) async {
    this.adSrc = adSrc;
    this.skuId = skuId;
    rewardCallback = null;
    try {
      bool? isInit = await AppLovinMAX.isInitialized();
      if (isInit == null || !isInit) {
        return false;
      }
      bool isReady = (await AppLovinMAX.isRewardedAdReady(AdHelper.applovinRewardedAdUnitId))!;
      if (isReady) {
        AppLovinMAX.showRewardedAd(AdHelper.applovinRewardedAdUnitId, placement: 'reward');
        rewardCallback = onUserEarnedReward;
        return true;
      } else {
        AppLovinMAX.loadRewardedAd(AdHelper.applovinRewardedAdUnitId);
        return false;
      }
    } on PlatformException catch (e) {
      _log.warning('PlatformException: $e');
      return false;
    } catch (e) {
      _log.warning('showRewardedlAd error: $e');
      return false;
    }
  }
}
