import 'dart:convert';
import 'dart:io';

import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:guru_app/ads/core/ads_config.dart';
import 'package:guru_app/analytics/data/analytics_model.dart';
import 'package:guru_app/firebase/firebase.dart';
import 'package:guru_app/firebase/remoteconfig/reserved_remote_config_models.dart';
import 'package:guru_app/guru_app.dart';
import 'package:guru_utils/http/http_model.dart';
import 'package:guru_utils/log/log.dart';
import 'package:rxdart/rxdart.dart';
import 'package:guru_utils/remote/remote_config.dart';

part "remote_config_interface.dart";

/// Created by Haoyi on 2020/4/22
///
part "remote_config_reserved_constants.dart";

class RemoteConfigManager extends IRemoteConfig {
  final BehaviorSubject<FirebaseRemoteConfigWrapper?> _subject = BehaviorSubject.seeded(null);
  static RemoteConfigManager? _instance;

  static RemoteConfigManager _getInstance() {
    _instance ??= RemoteConfigManager._internal();
    return _instance!;
  }

  factory RemoteConfigManager() => _getInstance();

  static RemoteConfigManager get instance => _getInstance();

  static final RegExp invalidABKeyRegExp = RegExp('[^a-zA-Z0-9_-]');

  RemoteConfigManager._internal();

  Future<void> init(Map<String, dynamic> defaultConfigs) async {
    final remoteConfig = FirebaseRemoteConfig.instance;
    await remoteConfig.setConfigSettings(RemoteConfigSettings(
      fetchTimeout: const Duration(seconds: 15),
      minimumFetchInterval: const Duration(hours: 2),
    ));

    _subject.add(FirebaseRemoteConfigWrapper._(remoteConfig));

    try {
      await remoteConfig.setDefaults(defaultConfigs);
      await remoteConfig.activate();
    } catch (exception) {
      Log.d("Unable to fetch remote config. Cached or default values will be used!",
          error: exception);
    } finally {
      _subject.add(FirebaseRemoteConfigWrapper._(remoteConfig));
    }
  }

  Future fetchAndActivate() async {
    final remoteConfig = FirebaseRemoteConfig.instance;
    try {
      await remoteConfig.fetchAndActivate();
    } catch (exception) {
      Log.d("Unable to fetch remote config. Cached or default values will be used!",
          error: exception);
    } finally {
      _subject.add(FirebaseRemoteConfigWrapper._(remoteConfig));
    }
  }

  Future<String> dumpString() async {
    final config = FirebaseRemoteConfig.instance;
    final data = config.getAll();
    String result = "";
    for (var entry in data.entries) {
      result += "[${entry.key}] ==> (${entry.value.asString()})\n";
    }
    return result;
  }

  static String valueSourceToString(ValueSource source) {
    switch (source) {
      case ValueSource.valueRemote:
        return "Remote";
      case ValueSource.valueStatic:
        return "Static";
      default:
        return "Default";
    }
  }

  Map<String, String> allData() {
    final config = FirebaseRemoteConfig.instance;
    final data = config.getAll();
    final result = {
      for (var entry in data.entries)
        "${entry.key} [${valueSourceToString(entry.value.source)}]": entry.value.asString()
    };
    result["last_fetch_remote_config_time"] = config.lastFetchTime.toString();
    result["last_fetch_remote_config_status"] = config.lastFetchStatus.toString();
    return result;
  }

  Future forceFetch({bool debug = false}) async {
    final remoteConfig = FirebaseRemoteConfig.instance;
    try {
      await remoteConfig.setConfigSettings(RemoteConfigSettings(
        fetchTimeout: const Duration(seconds: 15),
        minimumFetchInterval: const Duration(seconds: 0),
      ));
      await remoteConfig.fetchAndActivate();
    } catch (exception) {
      Log.d("Unable to fetch remote config. Cached or default values will be used $exception",
          error: exception);
      if (debug) {
        rethrow;
      }
    } finally {
      _subject.add(FirebaseRemoteConfigWrapper._(remoteConfig));
      await remoteConfig.setConfigSettings(RemoteConfigSettings(
        fetchTimeout: const Duration(seconds: 15),
        minimumFetchInterval: const Duration(hours: 2),
      ));
    }
  }

  Map<String, String> getABProperties() {
    final config = FirebaseRemoteConfig.instance;
    final data = config.getAll();
    final result = <String, String>{};
    final invalidABKeys = <String>{};
    dynamic cause;
    for (var entry in data.entries) {
      final valueStr = entry.value.asString();
      if (valueStr.contains("guru_ab_")) {
        try {
          final jsonValue = json.decode(valueStr);
          if (jsonValue is Map<String, dynamic>) {
            for (var jsonEntry in jsonValue.entries) {
              if (jsonEntry.key.contains("guru_ab_")) {
                String abName = jsonEntry.key.replaceFirst("guru_ab_", "");
                if (abName.contains(invalidABKeyRegExp)) {
                  Log.w("abName($abName) length is invalid! $abName");
                  invalidABKeys.add(abName);
                } else {
                  if (abName.length > 20) {
                    invalidABKeys.add(abName);
                    abName = abName.substring(0, 20);
                  }
                  result[GuruAnalytics.buildVariantKey(abName)] = jsonEntry.value.toString();
                  Log.i("abName:ab_$abName value:${jsonEntry.value}");
                }
              }
            }
          }
        } catch (error, stacktrace) {
          Log.w("decode json error! $error");
          cause = error;
        }
      }
    }
    if (invalidABKeys.isNotEmpty) {
      GuruAnalytics.instance
          .logException(InvalidABPropertyKeysException(invalidABKeys, cause: cause));
    }
    return result;
  }

  @override
  bool? getBool(String name, {bool? defaultValue}) => _subject.value?.getBool(name) ?? defaultValue;

  @override
  String? getString(String name, {String? defaultValue}) =>
      _subject.value?.getString(name) ??
      defaultValue ??
      RemoteConfigReservedConstants.getDefaultConfigString(name);

  @override
  double? getDouble(String name, {double? defaultValue}) =>
      _subject.value?.getDouble(name) ?? defaultValue;

  @override
  int? getInt(String name, {int? defaultValue}) => _subject.value?.getInt(name) ?? defaultValue;

  Stream<FirebaseRemoteConfigWrapper> observeConfig() => _subject.stream
      .map((config) => config ?? FirebaseRemoteConfigWrapper._(FirebaseRemoteConfig.instance));

  @override
  Stream<bool?> observeBool(String name, {bool? defaultValue}) =>
      observeConfig().map((config) => config.getBool(name));

  @override
  Stream<String?> observeString(String name, {String? defaultValue}) =>
      observeConfig().map((config) => config.getString(name));

  @override
  Stream<double?> observeDouble(String name, {double? defaultValue}) =>
      observeConfig().map((config) => config.getDouble(name));

  @override
  Stream<int?> observeInt(String name, {int? defaultValue}) =>
      observeConfig().map((config) => config.getInt(name));
}

class FirebaseRemoteConfigWrapper {
  final FirebaseRemoteConfig config;

  const FirebaseRemoteConfigWrapper._(this.config);

  DateTime get lastFetchTime => config.lastFetchTime;

  RemoteConfigFetchStatus get lastFetchStatus => config.lastFetchStatus;

  static String _key(String key) => GuruApp.instance.getRemoteConfigKey(key);

  String getString(String key) => config.getString(_key(key));

  bool getBool(String key) => config.getBool(_key(key));

  double getDouble(String key) => config.getDouble(_key(key));

  int getInt(String key) => config.getInt(_key(key));
}
