import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:io';

import 'package:flutter/services.dart';
import 'package:guru_analytics_flutter/events_constants.dart';
import 'package:guru_analytics_flutter/events_constants.dart';
import 'package:guru_app/account/account_data_store.dart';
import 'package:guru_app/analytics/guru_analytics.dart';
import 'package:guru_app/firebase/remoteconfig/remote_config_manager.dart';
import 'package:guru_app/guru_app.dart';
import 'package:guru_app/property/app_property.dart';
import 'package:guru_app/property/settings/guru_settings.dart';
import 'package:guru_app/utils/guru_file_utils_extension.dart';
import 'package:guru_utils/core/ext.dart';
import 'package:guru_utils/file/file_utils.dart';
import 'package:guru_utils/log/log.dart';
import 'package:guru_utils/property/runtime_property.dart';
import 'package:guru_utils/quiver/cache.dart';
import 'package:guru_utils/quiver/collection.dart';
import 'package:guru_utils/settings/settings.dart';
import 'package:guru_utils/tuple/tuple.dart';

abstract class EventMatcher {
  bool match(String eventName);
}

class UniversalMatcher extends EventMatcher {
  @override
  bool match(String eventName) => true;

  @override
  String toString() {
    return 'UniversalMatcher';
  }
}

class RegexMatcher extends EventMatcher {
  final RegExp re;

  RegexMatcher(String pattern) : re = RegExp(pattern);

  @override
  bool match(String eventName) => re.hasMatch(eventName);

  @override
  String toString() {
    return 'RegexMatcher:${re.pattern}';
  }
}

class WildcardMatcher extends RegexMatcher {
  final String wildcard;

  WildcardMatcher(this.wildcard) : super("^${wildcard.replaceAll("*", ".*")}\$");

  @override
  bool match(String eventName) => super.match(eventName);

  @override
  String toString() {
    return 'WildcardMatcher:$wildcard => ${re.pattern}';
  }
}

abstract class StrategyValidator {
  bool get alwaysVerify => false;

  const StrategyValidator();

  bool validate();
}

class UnlimitedValidator extends StrategyValidator {
  const UnlimitedValidator();

  @override
  bool validate() => true;

  @override
  String toString() {
    return 'UnlimitedValidator';
  }
}

class DisabledValidator extends StrategyValidator {
  @override
  bool validate() => false;

  @override
  String toString() {
    return 'DisabledValidator';
  }
}

class PlatformValidator extends StrategyValidator {
  final String platform;

  PlatformValidator(this.platform);

  @override
  bool validate() => Platform.isAndroid ? platform == "android" : platform == "ios";

  @override
  String toString() {
    return 'PlatformValidator($platform)';
  }
}

class CountryCodeValidator extends StrategyValidator {
  final Set<String> included;
  final Set<String> excluded;

  CountryCodeValidator(this.included, this.excluded);

  @override
  bool validate() {
    final countryCode = AccountDataStore.instance.countryCode;

    // 如果excluded不为空，证明存在排除选项，该validate将只判断所有excluded中的逻辑，
    // 将不会在判断included中的逻辑
    if (excluded.isNotEmpty) {
      return !excluded.contains(countryCode);
    }

    if (included.contains(countryCode)) {
      return true;
    }
    return false;
  }

  @override
  String toString() {
    return 'CountryCodeValidator{included: $included, excluded: $excluded}';
  }
}

class UserPropertyValidator extends StrategyValidator {
  @override
  bool get alwaysVerify => true;

  final List<Tuple2<String, String>> validProperties;

  UserPropertyValidator(this.validProperties);

  @override
  bool validate() {
    for (var tuple in validProperties) {
      if (GuruAnalytics.instance.getProperty(tuple.item1) != tuple.item2) {
        return false;
      }
    }
    return true;
  }

  @override
  String toString() {
    return 'UserPropertyValidator{validProperties: $validProperties}';
  }
}

class RandomValidator extends StrategyValidator {
  final int percent;

  RandomValidator(int percent) : percent = percent.clamp(10, 90);

  @override
  bool validate() {
    final firstInstallTime =
        RuntimeProperty.instance.getInt(UtilsSettingsKeys.firstInstallTime, defValue: -1);
    return (firstInstallTime % 9) >= (percent ~/ 10 - 1);
  }

  @override
  String toString() {
    return 'RandomValidator{percent: $percent}';
  }
}

class VersionValidator extends StrategyValidator {
  final String opt;
  final String buildId;

  VersionValidator(this.opt, this.buildId);

  @override
  bool validate() {
    final buildNumber = GuruSettings.instance.buildNumber.get();
    switch (opt) {
      case "ve":
        return buildNumber == buildId;
      case "vg":
        return buildNumber.compareTo(buildId) > 0;
      case "vge":
        return buildNumber.compareTo(buildId) >= 0;
      case "vl":
        return buildNumber.compareTo(buildId) < 0;
      case "vle":
        return buildNumber.compareTo(buildId) <= 0;
      default:
        return false;
    }
  }

  @override
  String toString() {
    return 'VersionValidator{opt: $opt, buildId: $buildId}';
  }
}

class StrategyRuleTypeException implements Exception {
  final String message;

  StrategyRuleTypeException([this.message = "Type mismatch: Expected a StrategyRuleItem."]);

  @override
  String toString() => "StrategyRuleTypeException: $message";
}

class StrategyRule {
  final EventMatcher? matcher;

  final StrategyValidator validator;

  final AppEventCapabilities appEventCapabilities;

  final String? adjustToken;

  AppEventOptions? _options;

  StrategyRule(this.validator, this.appEventCapabilities, {this.matcher, this.adjustToken});

  AppEventOptions? getAppEventOptions() {
    if ((_options != null && !validator.alwaysVerify) || validator.validate()) {
      return _options ??= AppEventOptions(capabilities: appEventCapabilities);
    }
    return null;
  }

  @override
  String toString() {
    return 'StrategyRule{matcher: $matcher, validator: $validator, appEventCapabilities: $appEventCapabilities, adjustToken: $adjustToken}';
  }
}

class StrategyRuleParser {
  static final invalidWildcardReg = RegExp(r'[^a-zA-Z=0-9_*]');
  static final adjustTokenReg = RegExp(r'^[a-z0-9]{6}$');
  static final randomStrategyReg = RegExp(r'^r([1-9]0)$');
  static final userPropertyStrategyReg = RegExp(r'^up:(.+)=(.+)$');
  static final versionStrategyReg = RegExp(r'^(ve|vg|vl|vge|vle)(\d{8})$');
  static final countryStrategyReg = RegExp(r'^cc:(.+)$');
  static final countryCodeValidReg = RegExp(r'^[a-z]{2}|\![a-z]{2}$');

  final List<String> fields;

  StrategyRuleParser(this.fields);

  EventMatcher? createEventMatcher(String event) {
    if (event == "_all_") {
      return UniversalMatcher();
    } else if (!invalidWildcardReg.hasMatch(event)) {
      if (event.contains("*")) {
        return WildcardMatcher(event);
      } else {
        // 返回空的话，表示精确匹配，无需提供matcher
        return null;
      }
    } else {
      return RegexMatcher(event);
    }
  }

  StrategyValidator? createStrategyValidator(String strategy) {
    if (strategy == "unlimited") {
      return const UnlimitedValidator();
    } else if (strategy == "disabled") {
      return DisabledValidator();
    } else if (strategy == "android" || strategy == "ios") {
      return PlatformValidator(strategy);
    } else {
      final randomMatch = randomStrategyReg.firstMatch(strategy);
      final randomPercent = randomMatch?.group(1);
      if (!DartExt.isBlank(randomPercent)) {
        return RandomValidator(int.parse(randomPercent!));
      }

      final userPropertyMatch = userPropertyStrategyReg.firstMatch(strategy);
      final userPropertyKey = userPropertyMatch?.group(1);
      final userPropertyValue = userPropertyMatch?.group(2);
      if (!DartExt.isBlank(userPropertyKey) && !DartExt.isBlank(userPropertyValue)) {
        return UserPropertyValidator([Tuple2(userPropertyKey!, userPropertyValue!)]);
      }

      final versionMatch = versionStrategyReg.firstMatch(strategy);
      final versionOpt = versionMatch?.group(1);
      final versionBuildId = versionMatch?.group(2);
      if (!DartExt.isBlank(versionOpt) && !DartExt.isBlank(versionBuildId)) {
        return VersionValidator(versionOpt!, versionBuildId!);
      }

      final countryCodeMatch = countryStrategyReg.firstMatch(strategy);
      final countryCodeExpression = countryCodeMatch?.group(1);
      if (!DartExt.isBlank(countryCodeExpression)) {
        final included = <String>{};
        final excluded = <String>{};
        final countryCodes = countryCodeExpression!
            .split("|")
            .where((cc) => countryCodeValidReg.hasMatch(cc))
            .toSet();
        for (var cc in countryCodes) {
          if (cc.startsWith("!")) {
            excluded.add(cc.substring(1));
          } else {
            included.add(cc);
          }
        }

        return CountryCodeValidator(included, excluded);
      }
    }
    return null;
  }

  StrategyRuleItem? fromData(List<String> data) {
    if (data.length != fields.length) {
      return null;
    }
    String? event;
    EventMatcher? eventMatcher;
    StrategyValidator? validator;
    int appEventCapabilitiesFlag = 0;
    String? adjustToken;
    for (int i = 0; i < fields.length; ++i) {
      final field = fields[i];
      final value = data[i];

      if (field == "event") {
        event = value;
        if (event.isEmpty) {
          return null;
        }
        try {
          eventMatcher = createEventMatcher(value);
          Log.d("eventMatcher:$eventMatcher");
        } catch (error, stacktrace) {
          Log.w("createEventMatcher error! $error", stackTrace: stacktrace);
          return null;
        }
      } else if (field == "guru") {
        if (value == '1') {
          appEventCapabilitiesFlag |= AppEventCapabilities.guru;
        }
      } else if (field == "firebase") {
        if (value == '1') {
          appEventCapabilitiesFlag |= AppEventCapabilities.firebase;
        }
      } else if (field == "facebook") {
        if (value == '1') {
          appEventCapabilitiesFlag |= AppEventCapabilities.facebook;
        }
      } else if (field == "adjust") {
        if (value == '1') {}
      } else if (field == "strategy") {
        validator = createStrategyValidator(value);
      } else if ((Platform.isAndroid && field == "ata") || (Platform.isIOS && field == "ati")) {
        if (value.isNotEmpty && adjustTokenReg.hasMatch(value)) {
          adjustToken = value;
        }
      }
    }
    if (event != null && validator != null) {
      return StrategyRuleItem(
          event,
          StrategyRule(validator, AppEventCapabilities(appEventCapabilitiesFlag),
              matcher: eventMatcher, adjustToken: adjustToken));
    }
    return null;
  }
}

class StrategyRuleItem extends Comparable {
  final String eventName;
  final StrategyRule rule;

  StrategyRuleItem(this.eventName, this.rule);

  @override
  int compareTo(other) {
    if (other is StrategyRuleItem) {
      return eventName.compareTo(other.eventName);
    }
    throw StrategyRuleTypeException();
  }
}

class GuruAnalyticsStrategy {
  static const String tag = "GuruAnalyticsStrategy";
  final List<StrategyRule> priorityRules = [];
  final SplayTreeMap<String, StrategyRule> explicitRules = SplayTreeMap();
  final Map<String, AdjustEventConverter> iosAdjustEventConverters = {};
  final Map<String, AdjustEventConverter> androidAdjustEventConverts = {};

  bool loaded = false;

  final LinkedLruHashMap<String, StrategyRule> eventRules = LinkedLruHashMap(maximumSize: 128);

  GuruAnalyticsStrategy._();

  static final GuruAnalyticsStrategy instance = GuruAnalyticsStrategy._();

  void reset() {
    priorityRules.clear();
    explicitRules.clear();
  }

  static const guruAnalyticsStrategyExtension = ".gas"; // Guru Analytics Strategy

  Future<File?> checkAndCreateLocalStrategyFile() async {
    final currentLocalStrategy =
        "${GuruSettings.instance.buildNumber.get()}$guruAnalyticsStrategyExtension";
    final file = await FileUtils.instance.getGuruConfigFile("analytics", currentLocalStrategy);
    if (!file.existsSync()) {
      try {
        final data = await rootBundle.loadString("assets/guru/analytics_strategy.csv");
        file.writeAsStringSync(data);
        Log.i("load local strategy success! [$currentLocalStrategy]", tag: tag);
        return file;
      } catch (error, stacktrace) {
        Log.w("not config local strategy!", tag: tag);
      }
    }
    return null;
  }

  Future load() async {
    try {
      final analyticsConfig = RemoteConfigManager.instance.getAnalyticsConfig();

      if (!GuruApp.instance.appSpec.deployment.enabledGuruAnalyticsStrategy ||
          !analyticsConfig.enabledStrategy) {
        Log.w("analytics strategy disabled!", tag: tag);
        return;
      }

      final String remoteAnalyticsStrategy = analyticsConfig.strategy;
      final latestAnalyticsStrategy = await AppProperty.getInstance().getLatestAnalyticsStrategy();
      if (remoteAnalyticsStrategy != latestAnalyticsStrategy) {
        loaded = false;
      }
      if (loaded) {
        Log.w("already loaded! ignore!", tag: tag);
        return;
      }
      File? file;
      // 如果remoteAnalyticsStrategy非空表示云控配置了strategy
      if (!DartExt.isBlank(remoteAnalyticsStrategy)) {
        file = await FileUtils.instance.getGuruConfigFile("analytics", remoteAnalyticsStrategy);
        if (!file.existsSync()) {
          try {
            await FileUtils.instance.downloadFile(
                "${GuruApp.instance.details.storagePrefix}/guru%2Fanalytics%2F$remoteAnalyticsStrategy?alt=media",
                file);
            Log.i("download analytics strategy[$remoteAnalyticsStrategy] success", tag: tag);
          } catch (error, stacktrace) {
            Log.w("downloadFile error! $error try to fallback", tag: tag);
            // 这里没有使用上一次的strategy做回滚的原因，
            // 主要是考虑到上一次的云端strategy可能没有本地的strategy可靠，
            // SDK假设本地的strategy比Firebase Storage中配置的strategy更可靠
            // 因此这里在出现下载异常的情况下，会回滚到本地strategy上
            // 如果不想使用这个机制，可以在自己的项目中不配置任何strategy
          }
        }
        if (remoteAnalyticsStrategy != latestAnalyticsStrategy) {
          AppProperty.getInstance().setLatestAnalyticsStrategy(remoteAnalyticsStrategy);
          final latestStrategyFile =
              await FileUtils.instance.getGuruConfigFile("analytics", latestAnalyticsStrategy);
          if (latestStrategyFile.existsSync()) {
            FileUtils.instance.deleteFile(latestStrategyFile);
          }
        }
      }

      // 如果当前文件为空或是不存在，证明有可能相应的strategy下载失败，或是没有设置
      // 因此这种情况下尝试使用本地的strategy进行加载
      if (file?.existsSync() != true) {
        file = await checkAndCreateLocalStrategyFile();
        if (file?.existsSync() != true) {
          return;
        }
      }

      final Stream<String> strategyTextStream = file!.openRead().transform(utf8.decoder);

      StrategyRule? newDefaultRule;
      final List<StrategyRule> newPriorityRules = [];
      final Map<String, StrategyRule> newExplicitRules = {};
      StrategyRuleParser? parser;
      int lineNum = 0;
      await for (var line in strategyTextStream.transform(const LineSplitter())) {
        final list = line.split(",");
        Log.d("[${lineNum++}] $list", tag: tag);
        if (parser == null) {
          parser = StrategyRuleParser(list);
        } else {
          final ruleItem = parser.fromData(list);
          if (ruleItem == null) {
            continue;
          }
          if (ruleItem.eventName == "_all_") {
            newDefaultRule = ruleItem.rule;
          } else if (ruleItem.rule.matcher != null) {
            newPriorityRules.add(ruleItem.rule);
          } else {
            newExplicitRules[ruleItem.eventName] = ruleItem.rule;
          }

          if (ruleItem.rule.adjustToken != null) {
            if (Platform.isAndroid) {
              androidAdjustEventConverts[ruleItem.eventName] =
                  (_) => AdjustEvent(ruleItem.rule.adjustToken!);
            } else if (Platform.isIOS) {
              iosAdjustEventConverters[ruleItem.eventName] =
                  (_) => AdjustEvent(ruleItem.rule.adjustToken!);
            }
          }
        }
      }

      reset();
      priorityRules.addAll(newPriorityRules.reversed);
      if (newDefaultRule != null) {
        priorityRules.add(newDefaultRule);
      }
      explicitRules.addAll(newExplicitRules);

      loaded = true;
      Log.d(
          "analytics strategy loaded! ${eventRules.length} ${explicitRules.length} ${priorityRules.length}",
          tag: tag);
    } catch (error, stacktrace) {}
  }

  StrategyRule? getStrategyRule(String eventName) {
    Log.d(
        "[$loaded]getStrategyRule:$eventName ${eventRules.length} ${explicitRules.length} ${priorityRules.length}");
    if (!loaded) {
      return null;
    }

    final rule = eventRules[eventName];
    if (rule != null) {
      return rule;
    }

    final explicitRule = explicitRules[eventName];
    if (explicitRule != null) {
      return explicitRule;
    }

    for (var rule in priorityRules) {
      Log.d("matcher: ${rule.matcher} eventName: $eventName ${rule.matcher?.match(eventName)}",
          tag: tag);
      if (rule.matcher?.match(eventName) == true) {
        return rule;
      }
    }
    // 如果没有启用strategy，默认按之前逻辑处理
    return null;
  }

  AdjustEventConverter? getAdjustEventConverter(String eventName) {
    if (Platform.isAndroid) {
      return androidAdjustEventConverts[eventName];
    } else if (Platform.isIOS) {
      return iosAdjustEventConverters[eventName];
    }
    return null;
  }

  void testRule(String eventName) {
    final rule = getStrategyRule(eventName);
    if (rule?.matcher?.match(eventName) != false) {
      Log.d("testMatch: $eventName => $rule success!", tag: tag);
    } else {
      Log.d("testMatch: $eventName => $rule error!", tag: tag);
    }
  }
}
