import 'dart:async';

import 'package:logging/logging.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';

import 'data/interfaces/circles_repo_intf.dart';
import 'data/interfaces/connections_repo_intf.dart';
import 'data/interfaces/hashtag_repo_intf.dart';
import 'data/memory/memory_circles_repo.dart';
import 'data/objectbox/objectbox_cache.dart';
import 'data/objectbox/objectbox_connections_repo.dart';
import 'data/objectbox/objectbox_hashtag_repo.dart';
import 'friendica_client/friendica_client.dart';
import 'globals.dart';
import 'models/auth/profile.dart';
import 'models/instance_info.dart';
import 'services/auth_service.dart';
import 'services/blocks_manager.dart';
import 'services/connections_manager.dart';
import 'services/direct_message_service.dart';
import 'services/entry_manager_service.dart';
import 'services/feature_version_checker.dart';
import 'services/fediverse_server_validator.dart';
import 'services/follow_requests_manager.dart';
import 'services/gallery_service.dart';
import 'services/hashtag_service.dart';
import 'services/interactions_manager.dart';
import 'services/network_status_service.dart';
import 'services/notifications_manager.dart';
import 'services/persistent_info_service.dart';
import 'services/reshared_via_service.dart';
import 'services/secrets_service.dart';
import 'services/setting_service.dart';
import 'services/status_service.dart';
import 'services/timeline_entry_filter_service.dart';
import 'services/timeline_manager.dart';
import 'update_timer_initialization.dart';
import 'utils/active_profile_selector.dart';

final _logger = Logger('DI_Init');

Future<void> dependencyInjectionInitialization() async {
  getIt.registerSingleton<StatusService>(StatusService());

  final appSupportdir = await getApplicationSupportDirectory();
  getIt.registerSingleton<ActiveProfileSelector<PersistentInfoService>>(
    ActiveProfileSelector(
      (profile) {
        final profilePersistencePath =
            p.join(appSupportdir.path, '${profile.id}.json');
        return PersistentInfoService(profilePersistencePath)..load();
      },
    ),
  );

  getIt.registerSingleton<ActiveProfileSelector<TimelineEntryFilterService>>(
    ActiveProfileSelector(
      (profile) {
        final profilePersistencePath =
            p.join(appSupportdir.path, '${profile.id}_filters.json');
        return TimelineEntryFilterService(profilePersistencePath)..load();
      },
    ),
  );
  final objectBoxCache = await ObjectBoxCache.create();
  getIt.registerSingleton<ObjectBoxCache>(objectBoxCache);
  getIt.registerSingleton<IHashtagRepo>(ObjectBoxHashtagRepo());
  getIt.registerSingleton<HashtagService>(HashtagService());
  getIt.registerSingleton<NetworkStatusService>(NetworkStatusService());
  getIt.registerSingleton<FediverseServiceValidator>(
      FediverseServiceValidator());
  getIt.registerSingleton<FriendicaVersionChecker>(
      const FriendicaVersionChecker());

  final settingsService = SettingsService();
  await settingsService.initialize();
  getIt.registerSingleton<SettingsService>(settingsService);

  getIt.registerSingleton<ActiveProfileSelector<IConnectionsRepo>>(
      ActiveProfileSelector(null));

  getIt.registerSingleton<ActiveProfileSelector<InstanceInfo>>(
      ActiveProfileSelector(null));

  final secretsService = SecretsService();
  final serviceInit = await secretsService.initialize();

  final accountsService = AccountsService(secretsService);
  if (serviceInit.isFailure) {
    _logger.severe('Error initializing credentials');
  } else {
    accountsService.initialize();
  }
  getIt.registerSingleton<AccountsService>(accountsService);
  getIt<ActiveProfileSelector<IConnectionsRepo>>().subscribeToProfileSwaps();
  getIt<ActiveProfileSelector<InstanceInfo>>().subscribeToProfileSwaps();
  getIt.registerSingleton<ActiveProfileSelector<ReshareViaService>>(
      ActiveProfileSelector((p) => ReshareViaService())
        ..subscribeToProfileSwaps());

  getIt.registerSingleton<ActiveProfileSelector<ICirclesRepo>>(
      ActiveProfileSelector((p) => MemoryCirclesRepo())
        ..subscribeToProfileSwaps());

  getIt.registerSingleton<ActiveProfileSelector<ConnectionsManager>>(
      ActiveProfileSelector(
    (p) => ConnectionsManager(
      p,
      getIt<ActiveProfileSelector<IConnectionsRepo>>().getForProfile(p).value,
      getIt<ActiveProfileSelector<ICirclesRepo>>().getForProfile(p).value,
    ),
  ));

  getIt.registerSingleton<ActiveProfileSelector<GalleryService>>(
      ActiveProfileSelector((p) => GalleryService(p))
        ..subscribeToProfileSwaps());
  getIt.registerSingleton<ActiveProfileSelector<EntryManagerService>>(
      ActiveProfileSelector((p) => EntryManagerService(p))
        ..subscribeToProfileSwaps());
  getIt.registerSingleton<ActiveProfileSelector<TimelineManager>>(
      ActiveProfileSelector((p) => TimelineManager(
            p,
            getIt<ActiveProfileSelector<ICirclesRepo>>().getForProfile(p).value,
            getIt<ActiveProfileSelector<EntryManagerService>>()
                .getForProfile(p)
                .value,
          ))
        ..subscribeToProfileSwaps());
  getIt.registerSingleton<ActiveProfileSelector<NotificationsManager>>(
      ActiveProfileSelector((p) => NotificationsManager(p))
        ..subscribeToProfileSwaps());
  getIt.registerSingleton<ActiveProfileSelector<FollowRequestsManager>>(
      ActiveProfileSelector((p) => FollowRequestsManager(p))
        ..subscribeToProfileSwaps());
  getIt.registerSingleton<ActiveProfileSelector<DirectMessageService>>(
      ActiveProfileSelector((p) => DirectMessageService(p))
        ..subscribeToProfileSwaps());
  getIt.registerSingleton<ActiveProfileSelector<InteractionsManager>>(
      ActiveProfileSelector((p) => InteractionsManager(p))
        ..subscribeToProfileSwaps());
  getIt.registerSingleton<ActiveProfileSelector<BlocksManager>>(
      ActiveProfileSelector((p) => BlocksManager(p))
        ..subscribeToProfileSwaps());
  setupUpdateTimers();
}

Future<void> updateProfileDependencyInjectors(Profile profile) async {
  final objectBox = await ObjectBoxCache.create(
    baseDir: 'profileboxcaches',
    subDir: profile.id,
  );
  final connectionReposSelector =
      getIt<ActiveProfileSelector<IConnectionsRepo>>();
  connectionReposSelector.injectEntry(
      profile, ObjectBoxConnectionsRepo(objectBox));

  final instanceSelector = getIt<ActiveProfileSelector<InstanceInfo>>();
  final instanceInfo = await InstanceDataClient(profile).getInstanceData();
  instanceInfo.andThenSuccess(
    (instanceInfo) => instanceSelector.injectEntry(
      profile,
      instanceInfo,
    ),
  );
}

void clearCaches() {
  getIt<ActiveProfileSelector<IConnectionsRepo>>().activeEntry.match(
        onSuccess: (repo) => repo.clear(),
        onError: (error) =>
            _logger.severe('Error clearing IConnections Repo: $error'),
      );

  getIt<ActiveProfileSelector<DirectMessageService>>().activeEntry.match(
        onSuccess: (service) => service.clear(),
        onError: (error) =>
            _logger.severe('Error clearing DirectMessageService Repo: $error'),
      );

  getIt<ActiveProfileSelector<EntryManagerService>>().activeEntry.match(
        onSuccess: (service) => service.clear(),
        onError: (error) =>
            _logger.severe('Error clearing EntryManagerService Repo: $error'),
      );

  getIt<ActiveProfileSelector<FollowRequestsManager>>().activeEntry.match(
        onSuccess: (manager) => manager.clear(),
        onError: (error) =>
            _logger.severe('Error clearing FollowRequestsManager Repo: $error'),
      );

  getIt<ActiveProfileSelector<GalleryService>>().activeEntry.match(
        onSuccess: (service) => service.clear(),
        onError: (error) =>
            _logger.severe('Error clearing GalleryService Repo: $error'),
      );

  getIt<HashtagService>().clear();

  getIt<ActiveProfileSelector<NotificationsManager>>().activeEntry.match(
        onSuccess: (manager) => manager.clear(),
        onError: (error) =>
            _logger.severe('Error clearing NotificationsManager Repo: $error'),
      );

  getIt<ActiveProfileSelector<TimelineManager>>().activeEntry.match(
        onSuccess: (manager) => manager.clear(),
        onError: (error) =>
            _logger.severe('Error clearing TimelineManager Repo: $error'),
      );
}
