import 'dart:async';
import 'dart:developer';

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:flutter/foundation.dart';
import 'package:lightmeter/data/analytics/analytics.dart';
import 'package:lightmeter/data/models/feature.dart';

abstract class IRemoteConfigService {
  const IRemoteConfigService();

  Future<void> activeAndFetchFeatures();

  Future<void> fetchConfig();

  dynamic getValue(Feature feature);

  Map<Feature, dynamic> getAll();

  Stream<Set<Feature>> onConfigUpdated();

  bool isEnabled(Feature feature);
}

class RemoteConfigService implements IRemoteConfigService {
  final LightmeterAnalytics analytics;

  const RemoteConfigService(this.analytics);

  @override
  Future<void> activeAndFetchFeatures() async {
    final FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.instance;
    const cacheStaleDuration = kDebugMode ? Duration(minutes: 1) : Duration(hours: 12);

    try {
      await remoteConfig.setConfigSettings(
        RemoteConfigSettings(
          fetchTimeout: const Duration(seconds: 15),
          minimumFetchInterval: cacheStaleDuration,
        ),
      );
      await remoteConfig.setDefaults(featuresDefaultValues.map((key, value) => MapEntry(key.name, value)));
      await remoteConfig.activate();
      await remoteConfig.ensureInitialized();

      log('Firebase remote config initialized successfully');
    } on FirebaseException catch (e) {
      _logError('Firebase exception during Firebase Remote Config initialization: $e');
    } catch (e) {
      _logError('Error during Firebase Remote Config initialization: $e');
    }
  }

  @override
  Future<void> fetchConfig() async {
    try {
      // https://github.com/firebase/flutterfire/issues/6196#issuecomment-927751667
      await Future.delayed(const Duration(seconds: 1));
      await FirebaseRemoteConfig.instance.fetch();
    } on FirebaseException catch (e) {
      _logError('Firebase exception during Firebase Remote Config fetch: $e');
    } catch (e) {
      _logError('Error during Firebase Remote Config fetch: $e');
    }
  }

  @override
  dynamic getValue(Feature feature) => FirebaseRemoteConfig.instance.getValue(feature.name).toValue(feature);

  @override
  Map<Feature, dynamic> getAll() {
    final Map<Feature, dynamic> result = {};
    for (final value in FirebaseRemoteConfig.instance.getAll().entries) {
      try {
        final feature = Feature.values.firstWhere((f) => f.name == value.key);
        result[feature] = value.value.toValue(feature);
      } catch (e, stackTrace) {
        _logError(e, stackTrace: stackTrace);
      }
    }
    return result;
  }

  @override
  Stream<Set<Feature>> onConfigUpdated() => FirebaseRemoteConfig.instance.onConfigUpdated.asyncMap(
        (event) async {
          await FirebaseRemoteConfig.instance.activate();
          final Set<Feature> updatedFeatures = {};
          for (final key in event.updatedKeys) {
            try {
              updatedFeatures.add(Feature.values.firstWhere((element) => element.name == key));
            } catch (e, stackTrace) {
              _logError(e, stackTrace: stackTrace);
            }
          }
          return updatedFeatures;
        },
      );

  @override
  bool isEnabled(Feature feature) => FirebaseRemoteConfig.instance.getBool(feature.name);

  void _logError(dynamic throwable, {StackTrace? stackTrace}) => analytics.logCrash(throwable, stackTrace);
}

class MockRemoteConfigService implements IRemoteConfigService {
  const MockRemoteConfigService();

  @override
  Future<void> activeAndFetchFeatures() async {}

  @override
  Future<void> fetchConfig() async {}

  @override
  Map<Feature, dynamic> getAll() => featuresDefaultValues;

  @override
  dynamic getValue(Feature feature) => featuresDefaultValues[feature];

  @override
  // ignore: cast_nullable_to_non_nullable
  bool isEnabled(Feature feature) => featuresDefaultValues[feature] as bool;

  @override
  Stream<Set<Feature>> onConfigUpdated() => const Stream.empty();
}

extension on RemoteConfigValue {
  dynamic toValue(Feature feature) {
    switch (feature) {
      case Feature.showUnlockProOnMainScreen:
        return asBool();
    }
  }
}
