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

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:guru_app/ads/ads_manager.dart';
import 'package:guru_app/ads/core/ads_config.dart';
import 'package:guru_app/aigc/bi/ai_bi.dart';
import 'package:guru_app/analytics/guru_analytics.dart';
import 'package:guru_app/hook/hook_manager.dart';
import 'package:guru_app/property/app_property.dart';
import 'package:guru_app/property/property_keys.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_applovin_flutter/guru_applovin_flutter.dart';

import 'package:guru_analytics_flutter/events_constants.dart';
import 'package:rxdart/rxdart.dart';
import 'package:guru_utils/ads/ads.dart';

part 'banner/banner_ads.dart';

part 'banner/banner_ads_handler.dart';

part 'handler/ads_audit.dart';

part 'interstitial/interstitial_ads.dart';

part 'interstitial/interstitial_ads_handler.dart';

part 'rewards/rewarded_ads.dart';

part 'rewards/rewarded_ads_handler.dart';

part 'handler/ads_cache.dart';

/// Created by Haoyi on 5/6/21

abstract class Ads extends AdsLifecycleOwner with AdsDelegate {
  set loaded(bool loaded);

  final Map<String, String> properties = <String, String>{};

  void setProperty(String name, String data) {
    properties[name] = data;
  }

  void retry() {}

  void preload() {}
}

abstract class SingleAds<T> extends Ads {
  Map<T, AdsEvent> get eventsMapping;

  AdUnitId get adUnitId;

  @override
  set loaded(bool loaded) {
    loadedSubject.addEx(loaded);
    setProperty("isLoaded", loaded ? "true" : "false");
  }

  @override
  bool get loaded => loadedSubject.value == true;

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

  @override
  Stream<bool> get observableLoaded => loadedSubject.stream;

  void dispatchEvent(T event, {Map<dynamic, dynamic> arguments = const <dynamic, dynamic>{}}) {
    final adsEvent = eventsMapping[event];
    if (adsEvent != null) {
      final adsBundle = AdsBundle.create(this, arguments: arguments);
      switch (adsEvent) {
        case AdsEvent.adLoaded:
          onAdLoaded(adsBundle);
          break;
        case AdsEvent.adLoadFailed:
          onAdLoadFailed(adsBundle);
          break;
        case AdsEvent.adDisplayed:
          onAdDisplayed(adsBundle);
          break;
        case AdsEvent.adDisplayFailed:
          onAdDisplayFailed(adsBundle);
          break;
        case AdsEvent.adClick:
          onAdClicked(adsBundle);
          break;
        case AdsEvent.adHidden:
          onAdHidden(adsBundle);
          break;
        case AdsEvent.adRewarded:
          onAdRewarded(adsBundle);
          break;
      }
    }
  }

  @override
  @mustCallSuper
  Future dispose() async {
    bool result = false;
    try {
      result = await requestDispose();
    } catch (error, stacktrace) {
      Log.d("requestDispose error:$error $stacktrace");
    }
    onRequestDispose(AdsBundle.create(this));
    super.dispose();
    return result;
  }

  @override
  @mustCallSuper
  Future<bool> reset() async {
    AdCause result = AdCause.internalError;
    try {
      result = await requestReset();
    } catch (error, stacktrace) {
      Log.d("requestReset error:$error $stacktrace");
    }
    onRequestReset(AdsBundle.create(this));
    return result == AdCause.success;
  }

  @mustCallSuper
  void init() {
    if (this is AdsAudit) {
      Log.d("[$runtimeType]AdsAudit === add AdsAuditObserver", tag: "Ads");
      addObserver(AdsAuditObserver(runtimeType.toString()));
    }
    if (this is AdsCache) {
      Log.d("[$runtimeType]AdsReload === add AdsReloadObserver", tag: "Ads");
      addObserver(AdsCacheObserver(runtimeType.toString()));
    }
  }

  @override
  Future<AdCause> load() async {
    final adCause = await requestLoad().catchError((error, stacktrace) {
      Log.e("load error! ", tag: "Ads", error: error, stackTrace: stacktrace, syncFirebase: true);
      return AdCause.internalError;
    });
    Log.d("[$runtimeType]load complete!! $adCause", syncFirebase: true);
    onRequestLoad(AdsBundle.create(this, arguments: {"cause": adCause}));
    return adCause;
  }

  @override
  Future hide() async {
    onRequestHide(AdsBundle.create(this));
    return await requestHide();
  }

  @override
  Future<AdCause> show({required String scene, bool ignoreCheck = false}) async {
    final adCause =
        await requestShow(scene: scene, ignoreCheck: ignoreCheck).catchError((error, stacktrace) {
      Log.e("show error! $error", stackTrace: stacktrace, syncFirebase: true);
      return AdCause.internalError;
    });
    Log.d("[$runtimeType]show $scene complete!! $adCause", syncFirebase: true);
    onRequestShow(AdsBundle.create(this, arguments: {"scene": scene, "cause": adCause}));
    return adCause;
  }

  Future<AdCause> requestLoad();

  Future<AdCause> requestShow({required String scene, bool ignoreCheck = false});

  Future<bool> requestHide();

  Future<bool> requestDispose();

  Future<AdCause> requestReset() async {
    return AdCause.internalError;
  }

  Future<int> getStatus();

  @override
  Future<AdState> getState() async {
    final status = await getStatus();
    return convertAdStatusToAdState(status);
  }
}

enum AdsEvent {
  adLoaded,
  adLoadFailed,
  adDisplayed,
  adDisplayFailed,
  adClick,
  adHidden,
  adRewarded
}
