import 'dart:collection';
import 'dart:convert';

import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:result_monad/result_monad.dart';

import '../globals.dart';
import '../models/auth/basic_credentials.dart';
import '../models/auth/credentials_intf.dart';
import '../models/auth/oauth_credentials.dart';
import '../models/auth/profile.dart';
import '../models/exec_error.dart';

const _storageAccountName = 'social.myportal.relatica.secure_storage';

class SecretsService {
  static const _basicProfilesKey = 'basic_profiles';
  static const _oauthProfilesKey = 'oauth_profiles';

  final _cachedProfiles = <Profile>{};

  List<Profile> get profiles =>
      UnmodifiableListView(List.from(_cachedProfiles));

  final _secureStorage = const FlutterSecureStorage(
    iOptions: IOSOptions(
      accountName: _storageAccountName,
      accessibility: KeychainAccessibility.first_unlock,
    ),
    mOptions: MacOsOptions(
      accountName: _storageAccountName,
      groupId: macOsGroupId,
    ),
  );

  FutureResult<List<Profile>, ExecError> initialize() async {
    return await loadProfiles();
  }

  FutureResult<List<Profile>, ExecError> clearCredentials() async {
    try {
      await _secureStorage.delete(key: _basicProfilesKey);
      await _secureStorage.delete(key: _oauthProfilesKey);
      return Result.ok(profiles);
    } catch (e) {
      return Result.error(ExecError(
        type: ErrorType.localError,
        message: e.toString(),
      ));
    }
  }

  FutureResult<List<Profile>, ExecError> addOrUpdateProfile(
      Profile profile) async {
    try {
      _cachedProfiles.remove(profile);
      _cachedProfiles.add(profile);
      return await saveCredentials();
    } catch (e) {
      return Result.error(ExecError(
        type: ErrorType.localError,
        message: e.toString(),
      ));
    }
  }

  FutureResult<List<Profile>, ExecError> removeProfile(Profile profile) async {
    try {
      _cachedProfiles.remove(profile);
      return await saveCredentials();
    } catch (e) {
      return Result.error(ExecError(
        type: ErrorType.localError,
        message: e.toString(),
      ));
    }
  }

  FutureResult<List<Profile>, ExecError> loadProfiles() async {
    try {
      await _loadJson(_basicProfilesKey, BasicCredentials.fromJson);
      await _loadJson(_oauthProfilesKey, OAuthCredentials.fromJson);
      return Result.ok(profiles);
    } catch (e) {
      return Result.error(ExecError(
        type: ErrorType.localError,
        message: e.toString(),
      ));
    }
  }

  FutureResult<List<Profile>, ExecError> saveCredentials() async {
    try {
      await _saveJson<BasicCredentials>(_basicProfilesKey);
      await _saveJson<OAuthCredentials>(_oauthProfilesKey);
      return Result.ok(profiles);
    } catch (e) {
      return Result.error(ExecError(
        type: ErrorType.localError,
        message: e.toString(),
      ));
    }
  }

  Future<void> _loadJson(
    String key,
    ICredentials Function(Map<String, dynamic>) fromJson,
  ) async {
    final jsonString = await _secureStorage.read(key: key);
    if (jsonString == null || jsonString.isEmpty) {
      return;
    }
    final profiles = (jsonDecode(jsonString) as List<dynamic>)
        .map((json) => Profile.fromJson(json, fromJson))
        .toList();
    _cachedProfiles.addAll(profiles);
  }

  FutureResult<bool, ExecError> _saveJson<T>(
    String key,
  ) async {
    final json = _cachedProfiles
        .where((p) => p.credentials is T)
        .map((p) => p.toJson())
        .toList();
    final jsonString = jsonEncode(json);
    await _secureStorage.write(key: key, value: jsonString);
    final pulledResult = await _secureStorage.read(key: key);
    if (pulledResult == jsonString) {
      return Result.ok(true);
    }

    return buildErrorResult(
      type: ErrorType.localError,
      message:
          'For key $key value read from secure storage did not match value to secure storage',
    );
  }
}
