import 'dart:async';

import 'package:guru_utils/ads/ads_delegate.dart';
import 'package:guru_utils/ads/ads_manager_delegate.dart';
import 'package:guru_utils/ads/ads_manager_delegate.dart';
import 'package:guru_utils/ads/data/ads_model.dart';
import 'package:guru_utils/ads/handler/ads_handler.dart';
import 'package:guru_utils/analytics/analytics.dart';
import 'package:guru_utils/controller/ads_controller.dart';
import 'package:guru_utils/controller/aware/ads/interstitial_aware.dart';
import 'package:guru_utils/extensions/extensions.dart';
import 'package:guru_utils/guru_utils.dart';
import 'package:guru_utils/log/log.dart';
import 'package:guru_utils/property/runtime_property.dart';

/// Created by Haoyi on 2022/3/24

class LoadInterrupter {
  bool _interrupted = false;

  bool interrupted() {
    return _interrupted;
  }

  void interrupt() {
    _interrupted = true;
  }
}

mixin RewardedAware on AdsController {
  AdsDelegate? rewardedAds;

  AdsLifecycleObserver? _rewardedObserver;

  bool isShowing = false;

  bool _latestUserRewarded = false;

  final BehaviorSubject<bool> _rewardedAdsLoadedSubject = BehaviorSubject.seeded(false);

  bool get _allowInterstitialAsAlternativeReward =>
      (AdsManagerDelegate.instance.getConfig("allowInterstitialAsAlternativeReward") as bool?) ??
      false;

  void bindRewardedAd() async {
    addSubscription(AdsManagerDelegate.instance.observableInitialized.listen((ready) async {
      if (ready) {
        await _getRewarded();
      }
    }, onError: (error, stacktrace) {
      Log.e("observable Sdk initialized error!", tag: "Ads", error: error, stackTrace: stacktrace);
    }));
  }

  bool get isLoadedRewardedAds => _rewardedAdsLoadedSubject.value;

  Stream<bool> get observableRewardedAdsLoaded => _rewardedAdsLoadedSubject.stream;

  Future<bool> checkRewardedAdsIsLoaded() async {
    final ads = await _getRewarded();
    return ads.loaded;
  }

  void unbindRewardedAd() async {
    final _observer = _rewardedObserver;
    if (_observer != null) {
      rewardedAds?.removeObserver(_observer);
    }
    // 这里并不需要释放，该插屏广告会缓存在内存中快速响应下一次请求
    rewardedAds = null;
  }

  Future<AdsDelegate> _createRewarded() async {
    final ads = await AdsManagerDelegate.instance.getRewardsAds();
    _rewardedObserver = AdsLifecycleObserverDelegate(
        onAdDisplayFailedCallback: _onRewardedAdDisplayFailed,
        onAdDisplayedCallback: _onRewardedAdDisplayed,
        onAdHiddenCallback: _onRewardedAdHidden,
        onAdRewardedCallback: _onUserRewarded);
    ads.addObserver(_rewardedObserver!);
    Log.i("bindRewardedAd success! ${ads.hashCode}");
    addSubscription(ads.observableLoaded.listen((loaded) {
      Log.d("ads loaded:$loaded");
      _rewardedAdsLoadedSubject.addEx(loaded);
    }));
    return ads;
  }

  Future<AdsDelegate> _getRewarded() async {
    return rewardedAds ??= await _createRewarded();
  }

  void _onRewardedAdDisplayFailed(AdsBundle adsBundle) {
    final completer = adsCompleter;
    if (completer != null && !completer.isCompleted) {
      Log.d("[$runtimeType] _onRewardedAdDisplayFailed $_latestUserRewarded", tag: "Ads");
      completer.error(AdCause.displayFailed);
    }
    adsCompleter = null;
  }

  void _onRewardedAdDisplayed(AdsBundle adsBundle) {
    Log.d("[$runtimeType] _onRewardedAdDisplayed: invoke clearAutoCompleteFrozenAdsTimer",
        tag: "Ads");
    clearAutoCompleteFrozenAdsTimer();
  }

  void _onRewardedAdHidden(AdsBundle adsBundle) {
    final completer = adsCompleter;
    Log.d("[$runtimeType] _onRewardedAdHidden $_latestUserRewarded ${completer?.isCompleted}",
        tag: "Ads");
    if (completer != null && !completer.isCompleted) {
      _latestUserRewarded ? completer.success() : completer.error(AdCause.rewardedFailed);
    }
    adsCompleter = null;
  }

  void _onUserRewarded(AdsBundle adsBundle) {
    _latestUserRewarded = true;
    Log.d("[$runtimeType] _onUserRewarded", tag: "Ads");
  }

  // Future _loadRewarded() async {
  //   final ads = await _getRewarded();
  //   _rewardedObserver = AdsLifecycleObserverDelegate(
  //       onAdDisplayFailedCallback: _onRewardedAdDisplayFailed, onAdHiddenCallback: _onRewardedAdHidden, onAdRewardedCallback: _onUserRewarded);
  //   ads.addObserver(_rewardedObserver!);
  //   Log.d("load _loadRewarded!!", tag: "Ads");
  //   final status = await ads.getStatus();
  //   if (status != AdStatus.LOADING && status != AdStatus.LOADED) {
  //     await ads.load();
  //   }
  // }

  void pollAdsLoaded(AdsDelegate ads, Completer<AdCause> completer, LoadInterrupter interrupter,
      {Duration maxDuration = const Duration(seconds: 90), int maxRetry = -1}) async {
    const Duration interval = Duration(milliseconds: 5000);
    int retry = 0;

    while (!interrupter.interrupted() &&
        maxDuration.inSeconds > 0 &&
        (maxRetry < 0 || retry < maxRetry)) {
      final state = await ads.getState();
      Log.d("load ads currentStatus:${toAdStatusName(state)}", tag: "Ads");
      if (state != AdState.loaded && state != AdState.loading) {
        final adCause = await ads.load();
        if (adCause == AdCause.success) {
          Log.d("pollAdsLoaded! $adCause", tag: "Ads");
        } else {
          Log.w("load error! $adCause", tag: "Ads");
          completer.complete(AdCause.internalError);
          return;
        }
      } else {
        if (state == AdState.loading) {
          if (ads.needReset()) {
            await ads.reset();
            Log.d("reset complete", tag: "Ads");
          } else {
            await Future.delayed(const Duration(seconds: 1));
          }
          continue;
        } else if (state == AdState.loaded) {
          completer.safeComplete(AdCause.success);
          return;
        }
      }
      await Future.delayed(interval);
      retry++;
      maxDuration -= interval;
      Log.d("pollAdsLoaded! retry! $retry $maxDuration $interval", tag: "Ads");
    }
    Log.d("pollAdsLoaded complete! $retry", tag: "Ads");
  }

  Completer<AdCause> createWaitAdsLoadedCompleter(AdsDelegate ads,
      {required LoadInterrupter interrupter}) {
    final completer = Completer<AdCause>();
    Log.d("createWaitAdsLoadedCompleter!", tag: "Ads");
    pollAdsLoaded(ads, completer, interrupter);
    return completer;
  }

  Future<bool> isRewardedAdsLoaded() async {
    final ads = await _getRewarded();
    return await ads.getState() == AdState.loaded;
  }

  Future<AdsResult> showRewardedAd(
      {required String scene,
      bool ignoreNoAds = false,
      Future<AdCause> Function(Completer<AdCause>)? loadingDialog}) async {
    const adType = AdType.rewarded;
    try {
      if (adsCompleter?.isCompleted == false) {
        Log.e("$runtimeType Ads conflict!", tag: "Ads", syncFirebase: true);
        return AdsResult.build(adType, AdCause.conflict);
      }
      Log.i(
          "showRewardedAd[$scene] ignoreNoAds:$ignoreNoAds showLoading:${loadingDialog != null} !",
          tag: "Ads");
      AdCause adCause = await AdsManagerDelegate.instance.validateRewards(scene);
      if (RuntimeProperty.instance.fakeRewardedAds) {
        GuruUtils.showToast("FAKE[$scene]\n REWARDED Ads has been shown!\n$adCause",
            duration: const Duration(seconds: 3));
        return AdsResult.success(AdType.rewarded);
      }
      if (adCause != AdCause.success) {
        return AdsResult.build(adType, adCause);
      }

      final ads = await _getRewarded();
      if (loadingDialog != null) {
        final interrupter = LoadInterrupter();
        final loadCompleter = createWaitAdsLoadedCompleter(ads, interrupter: interrupter);
        try {
          adCause = await loadingDialog(loadCompleter);
          if (adCause == AdCause.canceled) {
            AnalyticsUtils.instance.logEventEx("rads_load_cancel", itemCategory: scene);
          }
        } catch (error, stacktrace) {
          Log.w("showAdsLoading error!", error: error, stackTrace: stacktrace, tag: "Ads");
        }
        interrupter.interrupt();
      }

      if (adCause == AdCause.success) {
        adCause = await ads.show(scene: scene);
        if (adCause == AdCause.success) {
          Log.i("showRewardedAd ads success!", tag: "Ads");
          adsCompleter = AdsCompleter(adType);

          return await adsCompleter!.waiting();
        }
      }

      if ((this is InterstitialAware) && _allowInterstitialAsAlternativeReward) {
        final fallbackScene = "${scene}_fallback";
        Log.i("showRewardedAd error! $adCause use alternative ads! [$fallbackScene]", tag: "Ads");
        return (this as InterstitialAware).directShowInterstitialAd(scene: fallbackScene);
      }
      Log.i("showRewardedAd error! $adCause ", tag: "Ads");
      return AdsResult.build(adType, adCause);
    } catch (error, stacktrace) {
      Log.i("showRewardedAd exception! ", tag: "Ads", error: error, stackTrace: stacktrace);
      return AdsResult.build(AdType.rewarded, AdCause.internalError);
    }
  }
}
