import 'dart:convert';

import 'package:guru_app/guru_app.dart';
import 'package:guru_app/property/app_property.dart';
import 'package:guru_app/property/property_keys.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:guru_utils/converts/converts.dart';
import 'package:guru_utils/ads/ads.dart';
import 'package:guru_utils/datetime/datetime_utils.dart';
import 'package:guru_applovin_flutter/guru_applovin_flutter.dart';

part 'ads_config.g.dart';

AdState convertAdStatusToAdState(int status) {
  switch (status) {
    case AdStatus.LOADED:
      return AdState.loaded;
    case AdStatus.CREATED:
      return AdState.created;
    case AdStatus.LOADING:
      return AdState.loading;
    default:
      return AdState.failed;
  }
}

@JsonSerializable()
class TaichiConfig {
  @JsonKey(name: "enable", defaultValue: false)
  final bool enable;

  @JsonKey(name: "threshold", defaultValue: "")
  final String threshold;

  @JsonKey(name: "abnormal_threshold", defaultValue: 1.0)
  final double abnormalThreshold;

  TaichiConfig({this.enable = false, this.threshold = "", this.abnormalThreshold = 1.0});

  factory TaichiConfig.fromJson(Map<String, dynamic> json) => _$TaichiConfigFromJson(json);

  @override
  String toString() {
    return 'TaichiConfig{enable: $enable, threshold: $threshold}';
  }

  Map<String, dynamic> toJson() => _$TaichiConfigToJson(this);
}

@JsonSerializable()
class ImpressionData {
  @JsonKey(name: "ad_platform", defaultValue: "MAX")
  final String platform;

  @JsonKey(name: "id", defaultValue: "")
  final String id;

  @JsonKey(name: "adunit_id", defaultValue: "")
  final String unitId;

  @JsonKey(name: "adunit_name", defaultValue: "")
  final String unitName;

  @JsonKey(name: "adunit_format", defaultValue: "")
  final String unitFormat;

  @JsonKey(name: "adgroup_id", defaultValue: "")
  final String groupId;

  @JsonKey(name: "adgroup_name", defaultValue: "")
  final String groupName;

  @JsonKey(name: "adgroup_type", defaultValue: "")
  final String groupType;

  @JsonKey(name: "currency", defaultValue: "")
  final String currency;

  @JsonKey(name: "country", defaultValue: "")
  final String country;

  @JsonKey(name: "app_version", defaultValue: "")
  final String appVersion;

  @JsonKey(name: "adgroup_priority", defaultValue: 0)
  final int groupPriority;

  @JsonKey(name: "publisher_revenue", defaultValue: -1)
  final double publisherRevenue;

  @JsonKey(name: "network_name", defaultValue: "")
  final String networkName;

  @JsonKey(name: "network_placement_id", defaultValue: "")
  final String networkPlacementId;

  @JsonKey(name: "precision", defaultValue: "")
  final String precision;

  @JsonKey(ignore: true)
  late Map<String, dynamic> payload;

  ImpressionData derive({double? newPublisherRevenue}) {
    final newPayload = Map<String, dynamic>.from(payload);
    newPayload["publisher_revenue"] = newPublisherRevenue ?? publisherRevenue;
    return ImpressionData.fromJson(newPayload);
  }

  @override
  String toString() {
    return 'ImpressionData{platform: $platform, id: $id, unitId: $unitId, unitName: $unitName, unitFormat: $unitFormat, groupId: $groupId, groupName: $groupName, groupType: $groupType, currency: $currency, country: $country, appVersion: $appVersion, groupPriority: $groupPriority, publisherRevenue: $publisherRevenue, networkName: $networkName, networkPlacementId: $networkPlacementId, precision: $precision}';
  }

  ImpressionData(
      {required this.platform,
      required this.id,
      required this.unitId,
      required this.unitName,
      required this.unitFormat,
      required this.groupId,
      required this.groupName,
      required this.groupType,
      required this.currency,
      required this.country,
      required this.appVersion,
      required this.groupPriority,
      required this.publisherRevenue,
      required this.networkName,
      required this.networkPlacementId,
      required this.precision});

  factory ImpressionData.fromJson(Map<String, dynamic> json) =>
      _$ImpressionDataFromJson(json)..payload = json;

  Map<String, dynamic> toJson() => _$ImpressionDataToJson(this);
}

@JsonSerializable()
class CpmCalibrationData {
  @JsonKey(name: "list", defaultValue: <CpmCalibrationItem>[])
  final List<CpmCalibrationItem> items;

  final Map<String, double> _interstitialCpmCalibrationCountryMapping = {};

  final Map<String, double> _rewardedCpmCalibrationCountryMapping = {};

  CpmCalibrationData(this.items);

  double getCpm(String format, String country) {
    _ensureInitializedData();
    final upperFormat = format.toUpperCase();
    if (upperFormat.contains("FULLSCREEN") || upperFormat.contains("INTERSTITIAL")) {
      return _interstitialCpmCalibrationCountryMapping[country] ?? -1;
    }

    if (upperFormat.contains("REWARDED")) {
      return _rewardedCpmCalibrationCountryMapping[country] ?? -1;
    }
    return -1;
  }

  void _ensureInitializedData() {
    if (_interstitialCpmCalibrationCountryMapping.isEmpty ||
        _rewardedCpmCalibrationCountryMapping.isEmpty) {
      for (CpmCalibrationItem item in items) {
        if (item.format == "reward") {
          _rewardedCpmCalibrationCountryMapping[item.country.toUpperCase()] = item.cpm;
        } else if (item.format == "inter") {
          _interstitialCpmCalibrationCountryMapping[item.country.toUpperCase()] = item.cpm;
        }
      }
    }
  }

  factory CpmCalibrationData.fromJson(Map<String, dynamic> json) =>
      _$CpmCalibrationDataFromJson(json);

  Map<String, dynamic> toJson() => _$CpmCalibrationDataToJson(this);
}

@JsonSerializable()
class CpmCalibrationItem {
  @JsonKey(name: "format")
  final String format;

  @JsonKey(name: "cpm")
  final double cpm;

  @JsonKey(name: "country")
  final String country;

  CpmCalibrationItem(this.format, this.cpm, this.country);

  factory CpmCalibrationItem.fromJson(Map<String, dynamic> json) =>
      _$CpmCalibrationItemFromJson(json);

  Map<String, dynamic> toJson() => _$CpmCalibrationItemToJson(this);
}

@JsonSerializable()
class IRLDConfig {
  @JsonKey(name: "fb_ecpm_cache_h", defaultValue: 12)
  final int fbCpmCacheInHour;

  @JsonKey(name: "fb_irld_report", defaultValue: false)
  final bool fbIrldReport;

  @JsonKey(name: "abnormal_threshold", defaultValue: 0.1)
  final double abnormalThreshold;

  IRLDConfig({this.fbCpmCacheInHour = 12, this.fbIrldReport = false, this.abnormalThreshold = 0.1});

  factory IRLDConfig.fromJson(Map<String, dynamic> json) => _$IRLDConfigFromJson(json);

  Map<String, dynamic> toJson() => _$IRLDConfigToJson(this);

  @override
  String toString() {
    return 'IRLDConfig{fbCpmCacheInHour: $fbCpmCacheInHour, fbIrldReport: $fbIrldReport, abnormalThreshold: $abnormalThreshold}';
  }
}

class AdsConfig {
  final CommonAdsConfig commonAdsConfig;
  final AdInterstitialConfig interstitialConfig;
  final AdRewardedConfig rewardedConfig;
  final AdBannerConfig bannerConfig;
  final StrategyAdsConfig strategyAdsConfig;
  final IOSAttConfig iosAttConfig;

  AdsConfig.build(
      {CommonAdsConfig? commonAdsConfig,
      AdInterstitialConfig? interstitialConfig,
      AdRewardedConfig? rewardedConfig,
      AdBannerConfig? bannerConfig,
      StrategyAdsConfig? strategyAdsConfig,
      IOSAttConfig? iosAttConfig})
      : commonAdsConfig = commonAdsConfig ?? CommonAdsConfig.fromJson(<String, dynamic>{}),
        interstitialConfig =
            interstitialConfig ?? AdInterstitialConfig.fromJson(<String, dynamic>{}),
        rewardedConfig = rewardedConfig ?? AdRewardedConfig.fromJson(<String, dynamic>{}),
        bannerConfig = bannerConfig ?? AdBannerConfig.fromJson(<String, dynamic>{}),
        strategyAdsConfig = strategyAdsConfig ?? StrategyAdsConfig.fromJson(<String, dynamic>{}),
        iosAttConfig = iosAttConfig ?? IOSAttConfig.fromJson(<String, dynamic>{});

  static AdsConfig defaultAdsConfig = AdsConfig.build();

  Map<String, String> dump() {
    return {
      "common_config": commonAdsConfig.toJson().toString(),
      "iads_config": interstitialConfig.toJson().toString(),
      "rads_config": rewardedConfig.toJson().toString(),
      "bads_config": bannerConfig.toJson().toString(),
      "sads_config": strategyAdsConfig.toJson().toString(),
      "ios_att_config": iosAttConfig.toJson().toString()
    };
  }
}

@JsonSerializable()
class AdBannerConfig {
  @JsonKey(name: "free_s", defaultValue: 600)
  final int freeInSecond;

  @JsonKey(name: "validation", defaultValue: '')
  final String validation;

  @JsonKey(name: "amazon_enable", defaultValue: false)
  final bool amazonEnable;

  @JsonKey(name: "pubmatic_enable", defaultValue: false)
  final bool pubmaticEnable;

  @JsonKey(name: "auto_dispose_interval_m", defaultValue: 5)
  final int autoDisposeIntervalInMinutes;

  @override
  String toString() {
    return 'AdBannerConfig{freeInSecond: $freeInSecond, validation: $validation, amazonEnable: $amazonEnable, pubmaticEnable: $pubmaticEnable, autoDisposeIntervalInMinutes: $autoDisposeIntervalInMinutes}';
  }

  AdBannerConfig(this.freeInSecond, this.validation, this.amazonEnable, this.pubmaticEnable,
      this.autoDisposeIntervalInMinutes);

  factory AdBannerConfig.fromJson(Map<String, dynamic> json) => _$AdBannerConfigFromJson(json);

  Map<String, dynamic> toJson() => _$AdBannerConfigToJson(this);

  Future<AdCause> check(String? scene, {AdsValidator? validator}) async {
    if (!(await checkFreeTime())) {
      return AdCause.invalidRequest;
    }

    if (await validator?.call() == false) {
      return AdCause.invalidRequest;
    }
    return AdCause.success;
  }

  Future<bool> checkFreeTime() async {
    final firstInstallTime =
        await AppProperty.getInstance().getInt(PropertyKeys.firstInstallTime, defValue: 0);
    return ((DateTimeUtils.currentTimeInMillis() - firstInstallTime) / 1000) >= freeInSecond;
  }
}

@JsonSerializable()
class AdInterstitialConfig {
  @JsonKey(name: "free_s", defaultValue: 600)
  final int freeInSecond;

  @JsonKey(name: "validation", defaultValue: '')
  final String validation;

  @JsonKey(name: "scene", defaultValue: [])
  @joinedStringConvert
  final List<String> scenes;

  @JsonKey(name: "sp_scene", defaultValue: {})
  @configStringIntMapStringConvert
  final Map<String, int> specialScenes;

  @JsonKey(name: "retry_min_s", defaultValue: 4)
  final int retryMinTimeInSecond;

  @JsonKey(name: "retry_max_s", defaultValue: 600)
  final int retryMaxTimeInSecond;

  @JsonKey(name: "amazon_enable", defaultValue: false)
  final bool amazonEnable;

  @JsonKey(name: "imp_gap_s")
  final int? impGapInSeconds;

  AdInterstitialConfig(this.freeInSecond, this.validation, this.scenes, this.retryMinTimeInSecond,
      this.retryMaxTimeInSecond,
      {this.amazonEnable = true, required this.specialScenes, required this.impGapInSeconds});

  factory AdInterstitialConfig.fromJson(Map<String, dynamic> json) =>
      _$AdInterstitialConfigFromJson(json);

  Map<String, dynamic> toJson() => _$AdInterstitialConfigToJson(this);

  bool checkSceneEnabled(String scene) {
    return scenes.contains(scene);
  }

  int getSceneImpGapInSeconds(String scene) {
    return (specialScenes[scene] ??
            impGapInSeconds ??
            GuruApp.instance.appSpec.deployment.fullscreenAdsMinInterval)
        .clamp(5, 600);
  }

  Future<bool> checkFreeTime() async {
    final firstInstallTime =
        await AppProperty.getInstance().getInt(PropertyKeys.firstInstallTime, defValue: 0);
    return ((DateTimeUtils.currentTimeInMillis() - firstInstallTime) / 1000) >= freeInSecond;
  }

  Future<bool> canPreload({AdsValidator? validator}) async {
    if (!(await checkFreeTime())) {
      return false;
    }
    if (await validator?.call() == false) {
      return false;
    }
    return true;
  }

  Future<AdCause> check(String scene, {AdsValidator? validator}) async {
    Log.d("check: $this", tag: "Ads");
    if (!checkSceneEnabled(scene)) {
      return AdCause.disabledScene;
    }
    if (!(await checkFreeTime())) {
      return AdCause.invalidRequest;
    }
    if (await validator?.call() == false) {
      return AdCause.invalidRequest;
    }
    return AdCause.success;
  }

  @override
  String toString() {
    return 'AdInterstitialConfig{freeInSecond: $freeInSecond, validation: $validation, scenes: $scenes, retryMinTimeInSecond: $retryMinTimeInSecond, retryMaxTimeInSecond: $retryMaxTimeInSecond, amazonEnable: $amazonEnable}';
  }

  RetryConfig get retryConfig => RetryConfig(retryMinTimeInSecond, retryMaxTimeInSecond);
}

@JsonSerializable()
class StrategyAdsConfig {
  @JsonKey(name: "iads")
  final List<AdId>? interstitialIds;

  StrategyAdsConfig({this.interstitialIds});

  factory StrategyAdsConfig.fromJson(Map<String, dynamic> json) =>
      _$StrategyAdsConfigFromJson(json);

  Map<String, dynamic> toJson() => _$StrategyAdsConfigToJson(this);
}

class RetryConfig {
  final int minInSecond;
  final int maxInSecond;

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

@JsonSerializable()
class AdRewardedConfig {
  @JsonKey(name: "retry_min_s", defaultValue: 4)
  final int retryMinTimeInSecond;

  @JsonKey(name: "retry_max_s", defaultValue: 600)
  final int retryMaxTimeInSecond;

  @JsonKey(name: "reset_ads", defaultValue: true)
  final bool resetAds;

  @JsonKey(name: "validation", defaultValue: '')
  final String validation;

  AdRewardedConfig(this.retryMinTimeInSecond, this.retryMaxTimeInSecond,
      {this.resetAds = true, this.validation = ''});

  factory AdRewardedConfig.fromJson(Map<String, dynamic> json) => _$AdRewardedConfigFromJson(json);

  Map<String, dynamic> toJson() => _$AdRewardedConfigToJson(this);

  RetryConfig get retryConfig => RetryConfig(retryMinTimeInSecond, retryMaxTimeInSecond);

  Future<AdCause> check(String scene, {AdsValidator? validator}) async {
    if (GuruApp.instance.appSpec.deployment.disableRewardsAds) {
      return AdCause.adsDisabled;
    }
    if (await validator?.call() == false) {
      return AdCause.invalidRequest;
    }
    return AdCause.success;
  }

  Future<bool> canPreload({AdsValidator? validator}) async {
    if (GuruApp.instance.appSpec.deployment.disableRewardsAds) {
      return false;
    }
    if (await validator?.call() == false) {
      return false;
    }
    return true;
  }

  @override
  String toString() {
    return 'AdRewardedConfig{retryMinTimeInSecond: $retryMinTimeInSecond, retryMaxTimeInSecond: $retryMaxTimeInSecond, resetAds: $resetAds}';
  }
}

@JsonSerializable()
class IOSAttConfig {
  @JsonKey(name: "enable", defaultValue: false)
  bool enable;

  IOSAttConfig({this.enable = false});

  factory IOSAttConfig.fromJson(Map<String, dynamic> json) => _$IOSAttConfigFromJson(json);

  Map<String, dynamic> toJson() => _$IOSAttConfigToJson(this);
}

@JsonSerializable()
class CommonAdsConfig {
  @JsonKey(name: "compliant_init", defaultValue: false)
  final bool compliantInitialization;

  CommonAdsConfig({this.compliantInitialization = false});

  factory CommonAdsConfig.fromJson(Map<String, dynamic> json) => _$CommonAdsConfigFromJson(json);

  Map<String, dynamic> toJson() => _$CommonAdsConfigToJson(this);
}
