import 'package:flutter_test/flutter_test.dart';
import 'package:marianum_mobile/push/push_registration.dart';
import 'package:marianum_mobile/push/push_registration_store.dart';
import 'package:marianum_mobile/push/push_registration_type.dart';
import 'package:marianum_mobile/push/push_secure_storage.dart';

class _MemoryStorage implements FlutterSecureStorageLike {
  final Map<String, String> values = {};

  @override
  Future<String?> read({required String key}) async => values[key];

  @override
  Future<void> write({required String key, required String? value}) async {
    if (value == null) {
      values.remove(key);
    } else {
      values[key] = value;
    }
  }

  @override
  Future<void> delete({required String key}) async => values.remove(key);
}

void main() {
  group('talk user agents', () {
    // Quoted from nextcloud/server lib/public/IRequest.php.
    final uaTalkAndroid = RegExp(
      r'^Mozilla/5\.0 \(Android\) Nextcloud-Talk v([^ ]*).*$',
    );
    final uaTalkIos = RegExp(
      r'^Mozilla/5\.0 \(iOS\) Nextcloud-Talk v([^ ]*).*$',
    );

    test('android UA matches USER_AGENT_TALK_ANDROID', () {
      expect(
        uaTalkAndroid.hasMatch(PushRegistration.talkUserAgentAndroid),
        isTrue,
      );
      expect(
        uaTalkIos.hasMatch(PushRegistration.talkUserAgentAndroid),
        isFalse,
      );
    });

    test('ios UA matches USER_AGENT_TALK_IOS', () {
      expect(uaTalkIos.hasMatch(PushRegistration.talkUserAgentIos), isTrue);
      expect(
        uaTalkAndroid.hasMatch(PushRegistration.talkUserAgentIos),
        isFalse,
      );
    });
  });

  group('registrationTypesFor', () {
    test('password accounts maintain both registrations', () {
      expect(
        PushRegistration.registrationTypesFor(
          usesLoginFlow: false,
          hasTalkAppPassword: false,
        ),
        PushRegistrationType.values,
      );
      expect(
        PushRegistration.registrationTypesFor(
          usesLoginFlow: false,
          hasTalkAppPassword: true,
        ),
        PushRegistrationType.values,
      );
    });

    test('flow account without talk app password is talk-only', () {
      expect(
        PushRegistration.registrationTypesFor(
          usesLoginFlow: true,
          hasTalkAppPassword: false,
        ),
        [PushRegistrationType.talk],
      );
    });

    test('flow account with second (talk) app password maintains both', () {
      expect(
        PushRegistration.registrationTypesFor(
          usesLoginFlow: true,
          hasTalkAppPassword: true,
        ),
        PushRegistrationType.values,
      );
    });
  });

  group('pushTokenVariant', () {
    test('general uses the raw token, talk appends the suffix', () {
      expect(pushTokenVariant('tok', PushRegistrationType.general), 'tok');
      expect(pushTokenVariant('tok', PushRegistrationType.talk), 'tok#talk');
    });

    test('variants always differ (NC would delete same-hash siblings)', () {
      const token = 'any-token';
      expect(
        pushTokenVariant(token, PushRegistrationType.general),
        isNot(pushTokenVariant(token, PushRegistrationType.talk)),
      );
    });
  });

  group('PushRegistrationStore', () {
    test('pre-dual entries are read as the general registration', () async {
      final storage = _MemoryStorage();
      // State written by a pre-dual app version (no type suffixes).
      storage.values.addAll({
        'push_device_identifier': 'legacy-device',
        'push_server_public_key_pem': 'legacy-key',
        'push_registered_fcm_token': 'legacy-token',
        'push_registered_proxy_server': 'https://old/push-proxy/',
        'push_registered_nc_base_url': 'https://cloud',
        'push_last_registration_at': '2026-07-01T10:00:00.000',
        'push_last_registration_error': '',
      });
      final store = PushRegistrationStore(storage);

      expect(
        await store.deviceIdentifier(PushRegistrationType.general),
        'legacy-device',
      );
      expect(await store.isRegistered(PushRegistrationType.general), isTrue);
      expect(
        await store.registeredProxyServer(PushRegistrationType.general),
        'https://old/push-proxy/',
      );
      expect(
        await store.lastRegistrationAt(PushRegistrationType.general),
        DateTime(2026, 7, 1, 10),
      );

      // The talk registration is genuinely absent — the self-heal adds it.
      expect(await store.deviceIdentifier(PushRegistrationType.talk), isNull);
      expect(await store.isRegistered(PushRegistrationType.talk), isFalse);
    });

    test('per-type values stay independent', () async {
      final storage = _MemoryStorage();
      final store = PushRegistrationStore(storage);

      await store.save(
        type: PushRegistrationType.general,
        deviceIdentifier: 'dev-general',
        serverPublicKeyPem: 'server-key',
        fcmToken: 'token',
        proxyServer: 'https://a/push-proxy/',
        ncBaseUrl: 'https://cloud',
      );
      await store.save(
        type: PushRegistrationType.talk,
        deviceIdentifier: 'dev-talk',
        serverPublicKeyPem: 'server-key',
        fcmToken: 'token',
        proxyServer: 'https://a/push-proxy/',
        ncBaseUrl: 'https://cloud',
      );
      await store.saveLastRegistrationAttempt(
        type: PushRegistrationType.talk,
        at: DateTime(2026, 7, 5, 12),
        error: 'HTTP 404',
      );

      expect(
        await store.deviceIdentifier(PushRegistrationType.general),
        'dev-general',
      );
      expect(
        await store.deviceIdentifier(PushRegistrationType.talk),
        'dev-talk',
      );
      // Shared server key: last write wins, both read the same value.
      expect(await store.serverPublicKeyPem(), 'server-key');
      expect(
        await store.lastRegistrationError(PushRegistrationType.general),
        isNull,
      );
      expect(
        await store.lastRegistrationError(PushRegistrationType.talk),
        'HTTP 404',
      );
    });

    test('clear removes both registrations and shared keys', () async {
      final storage = _MemoryStorage();
      final store = PushRegistrationStore(storage);
      for (final type in PushRegistrationType.values) {
        await store.save(
          type: type,
          deviceIdentifier: 'dev',
          serverPublicKeyPem: 'key',
          fcmToken: 'token',
          proxyServer: 'https://a/',
          ncBaseUrl: 'https://cloud',
        );
        await store.saveLastRegistrationAttempt(
          type: type,
          at: DateTime(2026),
          error: 'x',
        );
      }
      await store.saveNativeAuthContext(
        username: 'user',
        baseUrl: 'https://cloud',
      );

      await store.clear();
      expect(storage.values, isEmpty);
    });
  });
}
