import 'push_registration_type.dart';
import 'push_secure_storage.dart';

/// Persists the bookkeeping produced by successful push registrations —
/// per [PushRegistrationType]: the Nextcloud device identifier, the FCM token
/// the registration was made with (so a token refresh can be detected), the
/// endpoints it was bound to (so an endpoint switch in the dev tools can be
/// detected) and the last attempt outcome. The server public key and the
/// device keypair are shared between both registrations.
///
/// Key layout: the `general` type uses the pre-dual key names unchanged, so
/// existing installs are implicitly migrated — their stored registration IS
/// the general one; the missing talk registration is added by the next
/// register-on-start self-heal.
class PushRegistrationStore {
  static const _deviceIdentifierKey = 'push_device_identifier';
  static const _serverPublicKeyKey = 'push_server_public_key_pem';
  static const _registeredTokenKey = 'push_registered_fcm_token';
  static const _proxyServerKey = 'push_registered_proxy_server';
  static const _ncBaseUrlKey = 'push_registered_nc_base_url';
  static const _lastAttemptAtKey = 'push_last_registration_at';
  static const _lastAttemptErrorKey = 'push_last_registration_error';
  // Native-only context: the iOS AppDelegate answers Talk notification actions
  // (reply / mark-as-read) directly via URLSession while the Flutter engine is
  // not guaranteed to run. It needs the Nextcloud username and base URL from the
  // shared (group-scoped) keychain; the app password already lives there
  // (AccountData writes `nextcloud_app_password` group-scoped).
  static const _usernameKey = 'nextcloud_username';
  static const _baseUrlKey = 'nextcloud_base_url';
  // Mirror of the in-app notification toggle (`notificationSettings.enabled`),
  // written group-scoped so the FCM background isolate (no bloc access) and the
  // iOS NSE can gate rendering. The device stays *registered* when off so silent
  // sync pushes keep flowing — only the visible alert is suppressed.
  static const _notificationsEnabledKey = 'push_notifications_enabled';

  static const _perTypeKeys = [
    _deviceIdentifierKey,
    _registeredTokenKey,
    _proxyServerKey,
    _ncBaseUrlKey,
    _lastAttemptAtKey,
    _lastAttemptErrorKey,
  ];

  final FlutterSecureStorageLike _storage;

  const PushRegistrationStore([this._storage = const PushSecureStorage()]);

  /// Type-specific key: general keeps the legacy names (implicit migration of
  /// pre-dual installs), talk appends a suffix.
  static String keyFor(String baseKey, PushRegistrationType type) =>
      type == PushRegistrationType.general ? baseKey : '${baseKey}_talk';

  Future<void> save({
    required PushRegistrationType type,
    required String deviceIdentifier,
    required String serverPublicKeyPem,
    required String fcmToken,
    required String proxyServer,
    required String ncBaseUrl,
  }) async {
    await _storage.write(
      key: keyFor(_deviceIdentifierKey, type),
      value: deviceIdentifier,
    );
    await _storage.write(key: _serverPublicKeyKey, value: serverPublicKeyPem);
    await _storage.write(
      key: keyFor(_registeredTokenKey, type),
      value: fcmToken,
    );
    await _storage.write(
      key: keyFor(_proxyServerKey, type),
      value: proxyServer,
    );
    await _storage.write(key: keyFor(_ncBaseUrlKey, type), value: ncBaseUrl);
  }

  /// Persists the username and Nextcloud base URL group-scoped so the native
  /// iOS Talk action handler (AppDelegate) can authenticate OCS calls. The base
  /// URL is a full origin like `https://cloud.marianum-fulda.de` (domain +
  /// optional path, no trailing slash).
  Future<void> saveNativeAuthContext({
    required String username,
    required String baseUrl,
  }) async {
    await _storage.write(key: _usernameKey, value: username);
    await _storage.write(key: _baseUrlKey, value: baseUrl);
  }

  /// Mirrors the in-app notification toggle so the background isolate / iOS NSE
  /// can read it without bloc access.
  Future<void> setNotificationsEnabled(bool enabled) =>
      _storage.write(
        key: _notificationsEnabledKey,
        value: enabled ? '1' : '0',
      );

  /// The mirrored notification toggle. Defaults to `true` when unset (fresh
  /// install / pre-mirror build) so a missing mirror never silences pushes.
  Future<bool> notificationsEnabled() async =>
      await _storage.read(key: _notificationsEnabledKey) != '0';

  Future<String?> deviceIdentifier(PushRegistrationType type) =>
      _storage.read(key: keyFor(_deviceIdentifierKey, type));

  /// Per-user server public key — identical for both registrations.
  Future<String?> serverPublicKeyPem() =>
      _storage.read(key: _serverPublicKeyKey);

  Future<String?> registeredFcmToken(PushRegistrationType type) =>
      _storage.read(key: keyFor(_registeredTokenKey, type));

  /// Proxy-server URL the registration of [type] was made with.
  Future<String?> registeredProxyServer(PushRegistrationType type) =>
      _storage.read(key: keyFor(_proxyServerKey, type));

  /// Nextcloud base URL the registration of [type] was made against.
  Future<String?> registeredNcBaseUrl(PushRegistrationType type) =>
      _storage.read(key: keyFor(_ncBaseUrlKey, type));

  /// True when a registration of [type] has been persisted.
  Future<bool> isRegistered(PushRegistrationType type) async =>
      (await registeredFcmToken(type))?.isNotEmpty ?? false;

  /// Records the outcome of the most recent registration attempt of [type] so
  /// the push status view can show when it ran and why it failed. [error]
  /// null = success (stored as empty string).
  Future<void> saveLastRegistrationAttempt({
    required PushRegistrationType type,
    required DateTime at,
    String? error,
  }) async {
    await _storage.write(
      key: keyFor(_lastAttemptAtKey, type),
      value: at.toIso8601String(),
    );
    await _storage.write(
      key: keyFor(_lastAttemptErrorKey, type),
      value: error ?? '',
    );
  }

  /// Timestamp of the last registration attempt of [type], or null when none
  /// ran yet.
  Future<DateTime?> lastRegistrationAt(PushRegistrationType type) async {
    final raw = await _storage.read(key: keyFor(_lastAttemptAtKey, type));
    if (raw == null || raw.isEmpty) return null;
    return DateTime.tryParse(raw);
  }

  /// Error text of the last registration attempt of [type], or null when it
  /// succeeded (or never ran).
  Future<String?> lastRegistrationError(PushRegistrationType type) async {
    final raw = await _storage.read(key: keyFor(_lastAttemptErrorKey, type));
    return (raw == null || raw.isEmpty) ? null : raw;
  }

  Future<void> clear() async {
    for (final type in PushRegistrationType.values) {
      for (final key in _perTypeKeys) {
        await _storage.delete(key: keyFor(key, type));
      }
    }
    await _storage.delete(key: _serverPublicKeyKey);
    await _storage.delete(key: _usernameKey);
    await _storage.delete(key: _baseUrlKey);
  }
}
