import 'dart:collection';

import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
import 'package:result_monad/result_monad.dart';
import 'package:shared_preferences/shared_preferences.dart';

import '../di_initialization.dart';
import '../friendica_client/friendica_client.dart';
import '../globals.dart';
import '../models/auth/credentials_intf.dart';
import '../models/auth/profile.dart';
import '../models/exec_error.dart';
import '../update_timer_initialization.dart';
import 'secrets_service.dart';
import 'status_service.dart';

class AccountsService extends ChangeNotifier {
  static final _logger = Logger('$AccountsService');
  Profile? _currentProfile;
  final _loggedInProfiles = <Profile>{};
  final _loggedOutProfiles = <Profile>{};
  var _initializing = false;

  final SecretsService secretsService;

  AccountsService(this.secretsService);

  bool get loggedIn => _currentProfile != null;

  bool get initializing => _initializing;

  List<Profile> get loggedInProfiles => UnmodifiableListView(_loggedInProfiles);

  List<Profile> get loggedOutProfiles =>
      UnmodifiableListView(_loggedOutProfiles);

  Profile get currentProfile => _currentProfile!;

  FutureResult<bool, ExecError> initialize() async {
    final lastActiveProfile = await _getStoredLoginState();

    _initializing = true;
    final result = await runCatchingAsync(() async {
      final initialProfiles = secretsService.profiles;
      for (final p in initialProfiles) {
        if (!p.loggedIn) {
          _loggedOutProfiles.add(p);
          continue;
        }

        final pr = await signIn(p.credentials, withNotification: false);
        if (pr.isSuccess) {
          final profile = pr.value;
          if (profile.id.isNotEmpty && profile.id == lastActiveProfile) {
            _currentProfile = profile;
            Future.delayed(
              const Duration(seconds: 10),
              () async => await executeUpdatesForProfile(profile),
            );
          }
        } else {
          await signOut(p, withNotification: false);
        }
      }

      if (_currentProfile == null && _loggedInProfiles.isNotEmpty) {
        await setActiveProfile(_loggedInProfiles.first);
      }

      notifyListeners();
      return Result.ok(loggedIn);
    });

    _initializing = false;
    return result.execErrorCast();
  }

  FutureResult<Profile, ExecError> signIn(ICredentials credentials,
      {bool withNotification = true}) async {
    ICredentials? credentialsCache;
    final result =
        await credentials.signIn().andThenAsync((signedInCredentials) async {
      final client =
          ProfileClient(Profile.credentialsOnly(signedInCredentials));
      credentialsCache = signedInCredentials;
      getIt<StatusService>().setStatus(
          'Getting user profile from ${signedInCredentials.serverName}');
      return await client.getMyProfile();
    }).andThenAsync((profileData) async {
      final loginProfile = Profile(
        credentials: credentialsCache!,
        username: profileData.name,
        serverName: credentialsCache!.serverName,
        avatar: profileData.avatarUrl,
        userId: profileData.id,
        loggedIn: true,
      );

      getIt<StatusService>()
          .setStatus('Loaded user profile ${profileData.handle}');
      if (_loggedInProfiles.isEmpty) {
        await setActiveProfile(loginProfile,
            withNotification: withNotification);
      }
      _loggedInProfiles.add(loginProfile);
      _loggedOutProfiles.remove(loginProfile);
      await secretsService.addOrUpdateProfile(loginProfile);
      await updateProfileDependencyInjectors(loginProfile);
      if (withNotification) {
        notifyListeners();
      }
      return Result.ok(loginProfile);
    });

    if (result.isFailure) {
      getIt<StatusService>().setStatus('Error signing in: ${result.error}');
      _logger.severe('Error signing in: ${result.error}');
    }

    return result.execErrorCast();
  }

  Future signOut(Profile profile, {bool withNotification = true}) async {
    if (_currentProfile == profile) {
      await clearActiveProfile(withNotification: withNotification);
    }
    _loggedInProfiles.remove(profile);
    _loggedOutProfiles.add(profile.copyWithLoginUpdate(false));
    await secretsService.addOrUpdateProfile(profile.copyWithLoginUpdate(false));

    if (_loggedInProfiles.isNotEmpty) {
      setActiveProfile(
        _loggedInProfiles.first,
        withNotification: withNotification,
      );
    }

    if (withNotification) {
      notifyListeners();
    }
  }

  Future removeProfile(Profile profile, {bool withNotification = true}) async {
    if (_currentProfile == profile) {
      await clearActiveProfile(withNotification: withNotification);
    }
    _loggedInProfiles.remove(profile);
    _loggedOutProfiles.remove(profile);
    await secretsService.removeProfile(profile);

    if (_loggedInProfiles.isNotEmpty) {
      setActiveProfile(
        _loggedInProfiles.first,
        withNotification: withNotification,
      );
    }

    if (withNotification) {
      notifyListeners();
    }
  }

  Future clearActiveProfile({bool withNotification = true}) async {
    _currentProfile = null;
    if (withNotification) {
      notifyListeners();
    }
    await _saveStoredLoginState();
  }

  Future<void> setActiveProfile(Profile profile,
      {bool withNotification = true}) async {
    _currentProfile = profile;
    if (withNotification) {
      notifyListeners();
    }
    await _saveStoredLoginState();
  }

  Future<void> _saveStoredLoginState() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('active_profile_id', _currentProfile?.id ?? '');
  }

  Future<String> _getStoredLoginState() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.getString('active_profile_id') ?? '';
  }
}
