import 'dart:async';

import 'package:flutter_test/flutter_test.dart';
import 'package:alt_client/src/integrations/mattermost/mattermost_auth_service.dart';
import 'package:alt_client/src/integrations/mattermost/mattermost_push_client.dart';
import 'package:alt_client/src/integrations/mattermost/mattermost_push_host_integration.dart';

class _FakePushClient implements MattermostPushClient {
  int initializeCalls = 0;
  int navigateChannelAssignments = 0;
  int navigateThreadAssignments = 0;

  final StreamController<Map<String, dynamic>> _controller =
      StreamController<Map<String, dynamic>>.broadcast();

  void Function(String serverUrl, String channelId)? _navigateChannel;
  void Function(String serverUrl, String rootId)? _navigateThread;

  @override
  Stream<Map<String, dynamic>> get onNotification => _controller.stream;

  void emit(Map<String, dynamic> data) => _controller.add(data);

  Future<void> dispose() => _controller.close();

  @override
  Future<void> initialize() async {
    initializeCalls += 1;
  }

  @override
  Future<String?> getDeviceToken() async => null;

  @override
  Future<void> setAuthToken(
    String serverUrl,
    String token, {
    String? identifier,
  }) async {}

  @override
  Future<void> setSigningKey(String serverUrl, String signingKey) async {}

  @override
  set onDeviceTokenReady(Future<void> Function(String token)? callback) {}

  @override
  set onNavigateToChannel(
    void Function(String serverUrl, String channelId)? callback,
  ) {
    navigateChannelAssignments += 1;
    _navigateChannel = callback;
  }

  @override
  set onNavigateToThread(
    void Function(String serverUrl, String rootId)? callback,
  ) {
    navigateThreadAssignments += 1;
    _navigateThread = callback;
  }

  void triggerNavigateChannel(String serverUrl, String channelId) {
    _navigateChannel?.call(serverUrl, channelId);
  }

  void triggerNavigateThread(String serverUrl, String rootId) {
    _navigateThread?.call(serverUrl, rootId);
  }
}

class _NoopAuthService implements MattermostAuthService {
  int autoLoginCalls = 0;
  final bool throwOnLogin;

  _NoopAuthService({this.throwOnLogin = false});

  @override
  Future<void> autoLoginAndRegister() async {
    autoLoginCalls += 1;
    if (throwOnLogin) {
      throw StateError('credentials missing');
    }
  }

  @override
  noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}

void main() {
  test('initialize calls push client init exactly once', () async {
    final push = _FakePushClient();
    final auth = _NoopAuthService();
    final host = MattermostPushHostIntegration(
      pushClient: push,
      authServiceFactory: (_) => auth,
    );

    await host.initialize();
    await host.initialize();

    expect(push.initializeCalls, equals(1));
    expect(auth.autoLoginCalls, equals(1));
    expect(host.isInitialized, isTrue);

    await push.dispose();
  });

  test('auto-login failure does not block initialize', () async {
    final push = _FakePushClient();
    final auth = _NoopAuthService(throwOnLogin: true);
    final host = MattermostPushHostIntegration(
      pushClient: push,
      authServiceFactory: (_) => auth,
    );

    await host.initialize();

    expect(host.isInitialized, isTrue);
    expect(push.initializeCalls, equals(1));

    await push.dispose();
  });

  test(
    'navigation callbacks are assigned exactly once during initialize',
    () async {
      final push = _FakePushClient();
      final host = MattermostPushHostIntegration(
        pushClient: push,
        authServiceFactory: (_) => _NoopAuthService(),
      );

      String? capturedChannel;
      String? capturedThread;
      await host.initialize(
        onNavigateToChannel: (_, channelId) => capturedChannel = channelId,
        onNavigateToThread: (_, rootId) => capturedThread = rootId,
      );

      expect(push.navigateChannelAssignments, equals(1));
      expect(push.navigateThreadAssignments, equals(1));

      push.triggerNavigateChannel('https://srv', 'channel-1');
      push.triggerNavigateThread('https://srv', 'root-1');

      expect(capturedChannel, equals('channel-1'));
      expect(capturedThread, equals('root-1'));

      await push.dispose();
    },
  );

  test('notification stream remains consumable through integration', () async {
    final push = _FakePushClient();
    final host = MattermostPushHostIntegration(
      pushClient: push,
      authServiceFactory: (_) => _NoopAuthService(),
    );
    await host.initialize();

    final received = <Map<String, dynamic>>[];
    final sub = host.onNotification.listen(received.add);

    push.emit(const {'type': 'message', 'message': 'hi'});
    await Future<void>.delayed(Duration.zero);

    expect(received, hasLength(1));
    expect(received.first['message'], equals('hi'));

    await sub.cancel();
    await push.dispose();
  });
}
