/// Created by Haoyi on 5/10/21

import 'dart:async';

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart' hide Rx;
import 'package:guru_popup/guru_popup.dart';
import 'package:guru_utils/ads/ads_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/controller/ads_controller.dart';
import 'package:guru_utils/controller/aware/ads/overlay/ads_overlay.dart';
import 'package:guru_utils/controller/lifecycle_controller.dart';
import 'package:guru_utils/datetime/datetime_utils.dart';
import 'package:guru_utils/extensions/extensions.dart';
import 'package:guru_utils/log/log.dart';
import 'package:guru_utils/network/network_utils.dart';
import 'package:guru_utils/remote/remote_config.dart';
import 'package:guru_utils/tuple/tuple.dart';
import 'package:system_clock/system_clock.dart';

class _BannerStatus {
  final LifecycleInfo lifecycleInfo;
  final bool adLoaded;
  final bool isNoAds;

  _BannerStatus(this.lifecycleInfo, this.adLoaded, this.isNoAds);
}

@Deprecated("use SharedBannerAware instead")
mixin BannerAware on AdsController {
  AdsDelegate? bannerAds;

  String get bannerScene => "";
  final BehaviorSubject<bool> adLoadedSubject = BehaviorSubject.seeded(false);

  bool bannerAdIsShow = false;
  bool bannerAdIsLoad = false;

  Stream<bool> get loadBannerWhenReady => Stream.value(true);

  bool shouldHide() => !isTopRoute;

  void onBannerAdLoaded(AdsBundle adsBundle) {
    Log.i("onBannerAdLoaded !", tag: "Ads");
    if (isResumed) {
      adLoadedSubject.addEx(true);
    }
  }

  void onBannerAdLoadFailed(AdsBundle adsBundle) {
    Log.i("onBannerAdLoadFailed !", tag: "Ads");
  }

  Future<AdsDelegate> _getBanner() async {
    bannerAds ??= await AdsManagerDelegate.instance.createBannerAds(
        scene: bannerScene,
        observer: AdsLifecycleObserverDelegate(
          onAdLoadedCallback: onBannerAdLoaded,
          onAdLoadFailedCallback: onBannerAdLoadFailed,
        ));
    return bannerAds!;
  }

  void initBanner() async {
    addSubscription(Rx.combineLatest2<bool, bool, bool>(
        AdsManagerDelegate.instance.observableInitialized,
        loadBannerWhenReady,
        (a, b) => a && b).listen((ready) async {
      if (ready) {
        await _loadBanner();
      }
    }, onError: (error, stacktrace) {
      Log.e("observable Sdk initialized error!", tag: "Ads", error: error, stackTrace: stacktrace);
    }));
    addSubscription(Rx.combineLatest3<LifecycleInfo, bool, bool, _BannerStatus>(
            observableLifecycleInfo,
            adLoadedSubject.stream,
            AdsManagerDelegate.instance.observableNoAds,
            (lifecycleInfo, loaded, noAds) => _BannerStatus(lifecycleInfo, loaded, noAds))
        .listen((status) {
      Log.i(
          "[$runtimeType] observable banner lifecycle state:${status.lifecycleInfo} noAds:${status.isNoAds} adLoaded:${status.adLoaded} bannerAdIsShow:$bannerAdIsShow",
          tag: "Ads");
      if (status.isNoAds) {
        hideBanner();
      } else if (status.lifecycleInfo.isResumed()) {
        if (status.adLoaded) {
          Log.i("showBanner!", tag: "Ads");
          showBanner();
        }
      } else if (shouldHide()) {
        Log.i("hideBanner!", tag: "Ads");
        hideBanner();
      }
    }, onError: (error, stacktrace) {
      Log.e("observable lifecycle state and loaded state error!",
          tag: "Ads", error: error, stackTrace: stacktrace);
    }));
  }

  void disposeBanner() {
    bannerAds?.dispose();
    bannerAds = null;
    adLoadedSubject.close();
    bannerAdIsShow = false;
    bannerAdIsLoad = false;
  }

  void onBannerResume() {}

  void onBannerPaused() {}

  void hideBanner() {
    if (bannerAdIsShow) {
      bannerAds?.hide();
      bannerAdIsShow = false;
    }
  }

  Future _loadBanner() async {
    final banner = await _getBanner();
    if (AdsManagerDelegate.instance.isPurchasedNoAd) {
      return;
    }
    if (!bannerAdIsLoad) {
      Log.v("load banner!!", tag: "Ads");
      bannerAdIsLoad = true;
      final result = await banner.load() == AdCause.success;
      if (result != bannerAdIsLoad) {
        bannerAdIsLoad = result;
      }
    }
  }

  void showBanner() async {
    final banner = await _getBanner();
    if (!bannerAdIsLoad) {
      await _loadBanner();
    } else if (!bannerAdIsShow) {
      bannerAdIsShow = (await banner.show(scene: bannerScene)) == AdCause.success;
      Log.v("banner show $bannerScene", tag: "Ads");
    }
  }
}

enum _BannerState { idle, ready, loaded, shown }

mixin SharedBannerAware on AdsController {
  // static final BehaviorSubject<_BannerState> _bannerStateSubject =
  //     BehaviorSubject.seeded(_BannerState.idle);
  static final BehaviorSubject<bool> adLoadedSubject = BehaviorSubject.seeded(false);
  static StreamSubscription? _bannerVisibilitySubscription;
  static AdsDelegate? bannerAds;
  static int latestLoadedAt = 0;

  static bool bannerAdIsLoad = false;
  static bool bannerAdIsShow = false;

  static Timer? checkTimer;
  static Timer? abnormalClickCheckTimer;
  static int referenceCount = 0;

  static String currentBannerScene = "";

  String get bannerScene => "";

  Stream<bool> get loadBannerWhenReady => Stream.value(true);

  StreamSubscription? _lifecycleSubscription;

  static int? latestClickAt;
  static int abnormalClick = 0;

  static Future<AdsDelegate> _fetchBanner() async {
    bannerAds ??= await AdsManagerDelegate.instance.createBannerAds(
        scene: currentBannerScene,
        observer: AdsLifecycleObserverDelegate(
          onAdLoadedCallback: onBannerAdLoaded,
          onAdLoadFailedCallback: onBannerAdLoadFailed,
          onAdClickedCallback: onBannerAdClicked
        ));
    return bannerAds!;
  }

  bool shouldHide() => !isTopRoute;

  Completer? _internalAdsBannerCompleter;

  Future showInternalBanner() async {
    _internalAdsBannerCompleter?.complete();
    _internalAdsBannerCompleter = AdsOverlay.showBanner();
  }

  Future hideInternalBanner() async {
    _internalAdsBannerCompleter?.complete();
    _internalAdsBannerCompleter = null;
  }

  void initBanner() {
    Log.d("[$runtimeType] initBanner! $referenceCount");
    referenceCount++;
    currentBannerScene = bannerScene;
    addSubscription(Rx.combineLatest3<bool, bool, bool, bool>(
        AdsManagerDelegate.instance.observableInitialized,
        loadBannerWhenReady,
        NetworkUtils.observableConnectivityTrack
            .map((track) => track.newResult != ConnectivityResult.none), (a, b, c) {
      Log.i("[$runtimeType] initialized:$a loadBannerWhenReady:$b connectivity:$c", tag: "Ads");
      return a && b && c;
    }).debounceTime(const Duration(seconds: 1)).listen((ready) async {
      if (ready) {
        _loadBanner();
      }
    }, onError: (error, stacktrace) {
      Log.e("observable Sdk initialized error!", tag: "Ads", error: error, stackTrace: stacktrace);
    }));
  }

  static void onBannerAdLoaded(AdsBundle adsBundle) {
    adLoadedSubject.addEx(true);
    final elapsed = SystemClock.elapsedRealtime().inMilliseconds;
    Log.i("onBannerAdLoaded interval: ${elapsed - latestLoadedAt}!", tag: "Ads");
    latestLoadedAt = elapsed;
  }

  static void onBannerAdLoadFailed(AdsBundle adsBundle) {
    Log.i("onBannerAdLoadFailed !", tag: "Ads");
  }

  static void onBannerAdClicked(AdsBundle adsBundle) {
    Log.i("onBannerAdClicked !", tag: "Ads");
    // final now = SystemClock.elapsedRealtime().inMilliseconds;
    // final latestAt = (latestClickAt ??= now);
    // latestClickAt = now;
    // if (now - latestAt > 2) {
    //   abnormalClick++;
    //   if (abnormalClick > 3) {
    //     abnormalClick = 0;
    //     latestClickAt = null;
    //     abnormalClickCheckTimer?.cancel();
    //     abnormalClickCheckTimer = Timer(duration, () {
    //
    //     });
    //   }
    // }
  }

  static Future _loadBanner() async {
    final banner = await _fetchBanner();
    if (AdsManagerDelegate.instance.isPurchasedNoAd) {
      return;
    }
    if (!bannerAdIsLoad) {
      Log.v("load banner!!", tag: "Ads");
      bannerAdIsLoad = true;
      final result = await banner.load() == AdCause.success;
      if (result != bannerAdIsLoad) {
        bannerAdIsLoad = result;
      }
    }
  }

  static void registerChecker() {
    if (checkTimer?.isActive == true) {
      Log.i("already register checker! ignore", tag: "Ads");
      return;
    }
    final autoDisposeInterval =
        (AdsManagerDelegate.instance.getConfig("bannerAutoDisposeInterval") as int?) ?? 5;
    Log.i("register checker autoDisposeInterval:$autoDisposeInterval", tag: "Ads");
    checkTimer?.cancel();
    checkTimer = Timer.periodic(Duration(minutes: autoDisposeInterval), (timer) async {
      final interval = SystemClock.elapsedRealtime().inMilliseconds - latestLoadedAt;
      Log.i("check banner loaded interval: $interval! autoDisposeInterval:$autoDisposeInterval",
          tag: "Ads");
      if (interval > (DateTimeUtils.minuteInMillis * autoDisposeInterval)) {
        Log.i("banner not response too long, dispose it! $referenceCount", tag: "Ads");
        checkTimer?.cancel();
        checkTimer = null;
        _disposeBanner();
        if (referenceCount > 0 && (await NetworkUtils.isNetworkConnected())) {
          Log.i("exists reference controller!($referenceCount) reload!}", tag: "Ads");
          _loadBanner();
        }
      }
    });
  }

  void showBanner() async {
    final banner = await _fetchBanner();
    if (!bannerAdIsLoad) {
      await _loadBanner();
      Log.i("[$runtimeType] load and show banner!", tag: "Ads");
    } else if (!bannerAdIsShow) {
      bannerAdIsShow = (await banner.show(scene: bannerScene)) == AdCause.success;
      Log.d("[$runtimeType] banner show $bannerScene", tag: "Ads");
      registerChecker();
    } else {
      Log.d("[$runtimeType] banner not show $bannerScene", tag: "Ads");
    }
  }

  void hideBanner() async {
    Log.i("hideBanner! bannerAdIsShow:$bannerAdIsShow", tag: "Ads");
    if (bannerAdIsShow) {
      bannerAds?.hide();
      bannerAdIsShow = false;
    }
    hideInternalBanner();
  }

  @mustCallSuper
  void processResumed() {
    _lifecycleSubscription?.cancel();
    final showInternalAdsWhenBannerUnavailable =
        (AdsManagerDelegate.instance.getConfig("showInternalAdsWhenBannerUnavailable") as bool?) ??
            false;
    _bannerVisibilitySubscription = Rx.combineLatest3<bool, bool, bool, Tuple3<bool, bool, bool>>(
        loadBannerWhenReady,
        adLoadedSubject.stream,
        AdsManagerDelegate.instance.observableNoAds,
        (ready, loaded, noAds) => Tuple3<bool, bool, bool>(ready, loaded, noAds)).listen((tuple) {
      final ready = tuple.item1;
      final loaded = tuple.item2;
      final noAds = tuple.item3;
      final canShow = ready && loaded && !noAds;
      Log.d("[$runtimeType] SharedBannerAware onResumed! canShow:$canShow showInternalAdsWhenBannerUnavailable:$showInternalAdsWhenBannerUnavailable", tag: "Ads");
      if (canShow) {
        showBanner();
        hideInternalBanner();
      } else {
        hideBanner();
        if (showInternalAdsWhenBannerUnavailable && !noAds) {
          // TODO: 暂时不开放
          // showInternalBanner();
        }
      }
    });
  }

  @mustCallSuper
  void processPaused() {
    _bannerVisibilitySubscription?.cancel();
    _bannerVisibilitySubscription = null;
    if (shouldHide()) {
      hideBanner();
    } else {
      Log.d("[$runtimeType] SharedBannerAware onPaused! observableLifecycleInfo", tag: "Ads");
      _lifecycleSubscription = observableLifecycleInfo.listen((event) {
        final canHide = shouldHide();
        Log.d("[$runtimeType] SharedBannerAware onPaused! ${event.isResumed()} canHide:$canHide",
            tag: "Ads");
        if (!event.isResumed() && canHide) {
          hideBanner();
        }
      });
    }
  }

  static void _disposeBanner() {
    bannerAds?.dispose();
    bannerAds = null;
    bannerAdIsLoad = false;
    bannerAdIsShow = false;
    adLoadedSubject.addIfChanged(false);
  }

  void disposeBanner() {
    referenceCount--;
    hideBanner();
  }
}
