import 'dart:io';

import 'package:guru_app/account/account_data_store.dart';
import 'package:guru_app/firebase/remoteconfig/remote_config_manager.dart';
import 'package:guru_app/property/settings/guru_settings.dart';
import 'package:guru_utils/device/device_utils.dart';
import 'package:guru_utils/extensions/extensions.dart';
import 'package:guru_utils/log/log.dart';
import 'package:guru_utils/random/random_utils.dart';
import 'package:json_annotation/json_annotation.dart';

part 'abtest_model.g.dart';

class ConditionOpt {
  static const equals = "eq";
  static const greaterThan = "gt";
  static const greaterThanOrEquals = "gte";
  static const lessThan = "lt";
  static const lessThanOrEquals = "lte";
  static const notEquals = "ne";

  static bool evaluate<T extends Comparable>(T value, T target, String opt) {
    final result = value.compareTo(target);
    switch (opt) {
      case ConditionOpt.equals:
        return result == 0;
      case ConditionOpt.greaterThan:
        return result > 0;
      case ConditionOpt.greaterThanOrEquals:
        return result >= 0;
      case ConditionOpt.lessThan:
        return result < 0;
      case ConditionOpt.lessThanOrEquals:
        return result <= 0;
      case ConditionOpt.notEquals:
        return result != 0;
      default:
        return false;
    }
  }
}

abstract class ABTestFilter {
  static const platform = 1;
  static const version = 2;
  static const country = 3;
  static const newUser = 4;

  final int type;

  const ABTestFilter(this.type);

  bool filter();

  factory ABTestFilter.fromJson(Map<String, dynamic> json) {
    final type = json["type"] = json["type"] ?? 0;
    switch (type) {
      case ABTestFilter.platform:
        return PlatformFilter.fromJson(json);
      case ABTestFilter.country:
        return CountryFilter.fromJson(json);
      case ABTestFilter.version:
        return VersionFilter.fromJson(json);
      case ABTestFilter.newUser:
        return NewUserFilter.fromJson(json);
      default:
        throw UnimplementedError("Unknown ABTestFilter type: $type");
    }
  }

  Map<String, dynamic> toJson() => toJson()..addAll({"type": type});
}

abstract class ABTestCondition {
  bool validate();
}

/// 为了后面可以做一些定制，因此这里按平台进行区分
@JsonSerializable()
class AndroidCondition extends ABTestCondition {
  @JsonKey(name: "opt")
  final String? opt;

  @JsonKey(name: "sdk")
  final int? sdkInt;

  AndroidCondition({this.opt, this.sdkInt});

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

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

  @override
  bool validate() {
    final versionOpt = opt;
    final targetVersion = sdkInt;
    if (versionOpt != null && targetVersion != null) {
      final versionCode = DeviceUtils.peekOSVersion();
      // 操作系统版本号获取失败，直接返回false
      if (versionCode == -1) {
        return false;
      }
      if (!ConditionOpt.evaluate<int>(versionCode, targetVersion, versionOpt)) {
        return false;
      }
    }
    // 方便后面扩展其它字段
    return true;
  }
}

@JsonSerializable()
class IosCondition extends ABTestCondition {
  @JsonKey(name: "opt")
  final String? opt;

  @JsonKey(name: "ver")
  final int? version; // 这里只记录大版本号

  IosCondition({this.opt, this.version});

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

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

  @override
  bool validate() {
    final versionOpt = opt;
    final targetVersion = version;
    if (versionOpt != null && targetVersion != null) {
      final versionCode = DeviceUtils.peekOSVersion();
      // 操作系统版本号获取失败，直接返回false
      if (versionCode == -1) {
        return false;
      }
      if (!ConditionOpt.evaluate<int>(versionCode, targetVersion, versionOpt)) {
        return false;
      }
    }
    // 方便后面扩展其它字段
    return true;
  }
}

@JsonSerializable()
class PlatformFilter extends ABTestFilter {
  @JsonKey(name: "ac")
  final AndroidCondition? androidCondition;

  @JsonKey(name: "ic")
  final IosCondition? iosCondition;

  PlatformFilter({this.androidCondition, this.iosCondition}) : super(ABTestFilter.platform);

  @override
  bool filter() {
    // 如果配了 Platform Filter, 如果指定平台没有 condition, 则默认为true
    if (Platform.isAndroid) {
      return androidCondition?.validate() != false;
    } else if (Platform.isIOS) {
      return iosCondition?.validate() != false;
    }
    return false;
  }

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

  @override
  Map<String, dynamic> toJson() => _$PlatformFilterToJson(this);
}

@JsonSerializable(constructor: "_")
class VersionFilter extends ABTestFilter {
  @JsonKey(name: "opt")
  final String opt;

  @JsonKey(name: "mmp")
  final String mmp; // major.minor.patch

  VersionFilter._(this.opt, this.mmp) : super(ABTestFilter.version);

  VersionFilter.equals(this.mmp)
      : opt = ConditionOpt.equals,
        super(ABTestFilter.version);

  VersionFilter.greaterThan(this.mmp)
      : opt = ConditionOpt.greaterThan,
        super(ABTestFilter.version);

  VersionFilter.greaterThanOrEquals(this.mmp)
      : opt = ConditionOpt.greaterThanOrEquals,
        super(ABTestFilter.version);

  VersionFilter.lessThan(this.mmp)
      : opt = ConditionOpt.lessThan,
        super(ABTestFilter.version);

  VersionFilter.lessThanOrEquals(this.mmp)
      : opt = ConditionOpt.lessThanOrEquals,
        super(ABTestFilter.version);

  VersionFilter.notEquals(this.mmp)
      : opt = ConditionOpt.notEquals,
        super(ABTestFilter.version);

  @override
  bool filter() {
    final version = GuruSettings.instance.version.get();
    Log.d("[$runtimeType] $version $opt $mmp");
    return ConditionOpt.evaluate<String>(version, mmp, opt);
  }

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

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

  @override
  Map<String, dynamic> toJson() => _$VersionFilterToJson(this);
}

@JsonSerializable(constructor: "_")
class CountryFilter extends ABTestFilter {
  @JsonKey(name: "included", defaultValue: {})
  final Set<String> included;

  @JsonKey(name: "excluded", defaultValue: {})
  final Set<String> excluded;

  CountryFilter._(this.included, this.excluded) : super(ABTestFilter.country);

  CountryFilter.included(this.included)
      : excluded = {},
        super(ABTestFilter.country);

  CountryFilter.excluded(this.excluded)
      : included = {},
        super(ABTestFilter.country);

  @override
  bool filter() {
    final String countryCode = Platform.localeName.split('_').safeLast?.toLowerCase() ?? "";
    Log.d("[$runtimeType] $countryCode included: $included excluded: $excluded");
    if (countryCode.isEmpty) {
      return false;
    }

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

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

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

  @override
  Map<String, dynamic> toJson() => _$CountryFilterToJson(this);
}

@JsonSerializable()
class NewUserFilter extends ABTestFilter {
  NewUserFilter() : super(ABTestFilter.newUser);

  @override
  bool filter() {
    final version = GuruSettings.instance.version.get();
    final fiv = GuruSettings.instance.firstInstallVersion.get();
    return fiv.startsWith(version);
  }

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

  @override
  Map<String, dynamic> toJson() => _$NewUserFilterToJson(this);
}

@JsonSerializable()
class ABTestAudience {
  @JsonKey(name: "filters")
  final List<ABTestFilter> filters;

  @JsonKey(name: "variant", defaultValue: 2)
  final int variant;

  ABTestAudience({required this.filters, this.variant = 2});

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

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

  @override
  String toString() {
    return 'ABTestAudience{filters: $filters, variant: $variant}';
  }

  bool validate() {
    for (var filter in filters) {
      if (!filter.filter()) {
        return false;
      }
    }
    return true;
  }
}

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

  @JsonKey(name: "start_ts", defaultValue: 0)
  final int startTs;

  @JsonKey(name: "end_ts", defaultValue: 0)
  final int endTs;

  @JsonKey(name: "audience")
  final ABTestAudience audience;

  ABTestExperiment(
      {required String name, required this.startTs, required this.endTs, required this.audience})
      : name = _validExperimentName(name);

  @override
  String toString() {
    return 'ABTestExperiment{name: $name, startTs: $startTs, endTs: $endTs, audience: $audience}';
  }

  static String _validExperimentName(String experimentName) {
    if (experimentName.contains(RemoteConfigManager.invalidABKeyRegExp)) {
      Log.w("abName($experimentName) use invalid key! $experimentName! replace invalid char to _");
      experimentName = experimentName.replaceAll(RemoteConfigManager.invalidABKeyRegExp, "_");
    } else {
      if (experimentName.length > 20) {
        experimentName = experimentName.substring(0, 20);
      }
    }
    return experimentName;
  }

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

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

  bool isExpired() {
    final now = DateTime.now().millisecondsSinceEpoch;
    return now < startTs || now > endTs;
  }

  bool isMatchAudience() {
    return audience.validate();
  }

  @JsonKey(includeToJson: false)
  String? _variantName;

  String get variantName =>
      (_variantName ??= _toVariantName(RandomUtils.nextInt(audience.variant)));

  static const _originalVariant = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

  String _toVariantName(int value) {
    String codes = "";
    int nv = value;
    while (true) {
      final nextNum = nv ~/ _originalVariant.length;
      if (nextNum <= 0) {
        break;
      }
      codes = "${_originalVariant[nv % _originalVariant.length]}$codes";
      nv = nextNum;
    }

    final tailIndex = nv % _originalVariant.length;
    if (tailIndex >= 0) {
      codes = "${_originalVariant[tailIndex]}$codes";
    }
    return codes.toString();
  }
}
