/// Created by Haoyi on 5/7/21
part of '../ads.dart';

class RetryConfig {
  final int minInSecond;
  final int maxInSecond;

  RetryConfig(this.minInSecond, this.maxInSecond);
}

mixin AdsCache<T> on Ads {
  int retryAttempt = 0;

  DateTime latestPreloadAt = DateTime.now();
  Timer? _retryTimer;

  RetryConfig get retryConfig => RetryConfig(4, 30);

  bool get isLoadingRewardAdsDelayed => _retryTimer != null;

  void _resetRetryTimer() {
    try {
      _retryTimer?.cancel();
      _retryTimer = null;
    } catch (error, stacktrace) {
      _retryTimer = null;
    }
  }

  void _resetPreload() {
    retryAttempt = 0;
    _resetRetryTimer();
    setProperty("Retry Attempt", "0");
  }

  @override
  void preload() {
    latestPreloadAt = DateTime.now();
    setProperty("Latest Preload Time", latestPreloadAt.toString());
    _resetRetryTimer();
    if (AdsManager.instance.connectivityStatus != ConnectivityResult.none) {
      load();
    }
  }

  @override
  void retry() {
    if (_retryTimer?.isActive == true) {
      return;
    }
    retryAttempt++;
    final delaySecond = pow(2, retryAttempt).toInt();
    final config = retryConfig;
    final duration = Duration(seconds: delaySecond.clamp(config.minInSecond, config.maxInSecond));
    setProperty("Retry Attempt", retryAttempt.toString());
    setProperty("Retry Interval", duration.toString());
    _retryTimer = Timer(duration, () {
      preload();
    });
  }
}

class AdsCacheObserver extends AdsLifecycleObserver {
  final String adsName;

  AdsCacheObserver(this.adsName);

  @override
  String get name => "$adsName-AdsCache";

  void _apply(AdsBundle adsBundle, void Function(AdsCache) callback) {
    if (adsBundle.ads is AdsCache) {
      callback(adsBundle.ads as AdsCache);
    }
  }

  @override
  void onRequestShow(AdsBundle adsBundle) {
    Log.i("[$name] onRequestShow! ${adsBundle.arguments}", tag: "Ads");
    _apply(adsBundle, (adsCache) async {
      final adCause = adsBundle.getValue<AdCause>("cause", defValue: AdCause.internalError);
      if (adCause != AdCause.success && adCause != AdCause.tooFrequent) {
        final state = await adsCache.getState();
        Log.d("[$name] onRequestShow state:$state", tag: "Ads");
        if (state != AdState.loaded) {
          adsCache.preload();
        } else {
          Log.d("[$name] onRequestShow ignore preload! AdsState: $state", tag: "Ads");
        }
      }
    });
  }

  @override
  void onRequestLoad(AdsBundle adsBundle) {
    // Log.i("[$name] onRequestLoad! ${adsBundle.arguments}", tag: "Ads");
    _apply(adsBundle, (adsCache) async {
      final state = await adsCache.getState();
      final adCause = adsBundle.getValue<AdCause>("cause", defValue: AdCause.internalError);
      if (adCause != AdCause.success && state != AdState.loaded && state != AdState.loading) {
        Log.d("[$name] onRequestLoad adCause:$adCause state:$state", tag: "Ads");
        adsCache.retry();
      } else {
        if (state == AdState.loaded) {
          adsCache._resetPreload();
          adsCache.loaded = true;
        }
        Log.d("[$name] onRequestLoad ignore preload! AdsState: $state", tag: "Ads");
      }
    });
  }

  @override
  void onRequestReset(AdsBundle adsBundle) {
    Log.i("[$name] onRequestReset! ", tag: "Ads");
    _apply(adsBundle, (adsCache) async {
      adsCache._resetPreload();
    });
  }

  @override
  void onAdDisplayed(AdsBundle adsBundle) {
    Log.i("[$name] onAdDisplayed! ", tag: "Ads");
    _apply(adsBundle, (adsCache) {
      adsCache.loaded = false;
    });
  }

  @override
  void onAdLoaded(AdsBundle adsBundle) {
    Log.i("[$name] onAdLoaded! ", tag: "Ads");
    _apply(adsBundle, (adsCache) {
      adsCache._resetPreload();
      adsCache.loaded = true;
    });
  }

  @override
  void onAdLoadFailed(AdsBundle adsBundle) {
    Log.i("[$name] onAdLoadFailed! ", tag: "Ads");
    _apply(adsBundle, (adsCache) async {
      adsCache.retry();
      adsCache.loaded = false;

    });
  }

  @override
  void onAdDisplayFailed(AdsBundle adsBundle) {
    Log.i("[$name] onAdDisplayFailed! ", tag: "Ads");
    _apply(adsBundle, (adsCache) {
      adsCache.preload();
      adsCache.loaded = false;
    });
  }

  @override
  void onAdHidden(AdsBundle adsBundle) {
    Log.i("[$name] onHidden! ", tag: "Ads");
    _apply(adsBundle, (adsCache) async {
      final state = await adsCache.getState();
      if (state != AdState.loaded && state != AdState.loading) {
        adsCache.loaded = false;
        adsCache.preload();
      } else {
        Log.d("[$name] onHidden ignore preload! AdsState: $state", tag: "Ads");
      }
    });
  }
}
