import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart' show AsyncData;
import 'package:flutter_test/flutter_test.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'package:url_launcher_platform_interface/link.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
import 'package:whitenoise/providers/auth_provider.dart';
import 'package:whitenoise/providers/offline_provider.dart';
import 'package:whitenoise/screens/chat_invite_screen.dart';
import 'package:whitenoise/screens/chat_list_screen.dart';
import 'package:whitenoise/screens/chat_screen.dart';
import 'package:whitenoise/screens/settings_screen.dart';
import 'package:whitenoise/screens/share_profile_screen.dart';
import 'package:whitenoise/screens/user_search_screen.dart';
import 'package:whitenoise/src/rust/api/chat_list.dart';
import 'package:whitenoise/src/rust/api/chat_summary.dart';
import 'package:whitenoise/src/rust/api/groups.dart';
import 'package:whitenoise/src/rust/api/messages.dart' show ChatMessage;
import 'package:whitenoise/src/rust/frb_generated.dart';
import 'package:whitenoise/widgets/chat_list_header.dart';
import 'package:whitenoise/widgets/chat_list_search_and_filters.dart';
import 'package:whitenoise/widgets/chat_list_tile.dart';
import 'package:whitenoise/widgets/wn_chat_list.dart';
import 'package:whitenoise/widgets/wn_icon_button.dart';
import 'package:whitenoise/widgets/wn_slate.dart';
import 'package:whitenoise/widgets/wn_system_notice.dart';

import '../mocks/mock_wn_api.dart';
import '../test_helpers.dart';

class _MockUrlLauncher extends UrlLauncherPlatform with MockPlatformInterfaceMixin {
  final List<({String url, LaunchOptions options})> calls = [];
  bool returnValue = true;

  @override
  LinkDelegate? get linkDelegate => null;

  @override
  Future<bool> launchUrl(String url, LaunchOptions options) async {
    calls.add((url: url, options: options));
    return returnValue;
  }
}

void _setInstalledVersion(String version) {
  PackageInfo.setMockInitialValues(
    appName: 'Whitenoise',
    packageName: 'org.parres.whitenoise',
    version: version,
    buildNumber: '1',
    buildSignature: '',
  );
}

ChatSummary _chatSummary({
  required String id,
  required bool pendingConfirmation,
  String? name,
}) => ChatSummary(
  mlsGroupId: id,
  name: name ?? 'Chat $id',
  groupType: GroupType.group,
  createdAt: DateTime(2024),
  pendingConfirmation: pendingConfirmation,
  selfRemoved: false,
  unreadCount: BigInt.zero,
);

class _MockApi extends MockWnApi {
  StreamController<ChatListStreamItem>? controller;
  StreamController<ChatListStreamItem>? archivedController;
  List<ChatSummary> initialChats = [];
  List<ChatSummary> initialArchivedChats = [];

  @override
  void reset() {
    super.reset();
    controller?.close();
    controller = null;
    archivedController?.close();
    archivedController = null;
    initialChats = [];
    initialArchivedChats = [];
  }

  @override
  Stream<ChatListStreamItem> crateApiChatListSubscribeToChatList({
    required String accountPubkey,
  }) {
    controller?.close();
    controller = StreamController<ChatListStreamItem>.broadcast();
    Future.microtask(() {
      controller?.add(ChatListStreamItem.initialSnapshot(items: initialChats));
    });
    return controller!.stream;
  }

  @override
  Stream<ChatListStreamItem> crateApiChatListSubscribeToArchivedChatList({
    required String accountPubkey,
  }) {
    archivedController?.close();
    archivedController = StreamController<ChatListStreamItem>.broadcast();
    Future.microtask(() {
      archivedController?.add(
        ChatListStreamItem.initialSnapshot(items: initialArchivedChats),
      );
    });
    return archivedController!.stream;
  }

  @override
  Future<Group> crateApiGroupsGetGroup({
    required String accountPubkey,
    required String groupId,
  }) async => Group(
    mlsGroupId: groupId,
    nostrGroupId: '',
    name: 'Test',
    description: '',
    adminPubkeys: const [],
    epoch: BigInt.zero,
    state: GroupState.active,
  );

  @override
  Future<List<ChatMessage>> crateApiMessagesFetchAggregatedMessagesForGroup({
    required String pubkey,
    required String groupId,
    DateTime? before,
    String? beforeMessageId,
    int? limit,
  }) async {
    return [];
  }
}

class _MockAuthNotifier extends AuthNotifier {
  @override
  Future<String?> build() async {
    state = const AsyncData(testPubkeyA);
    return testPubkeyA;
  }
}

class _SwitchableAuthNotifier extends AuthNotifier {
  @override
  Future<String?> build() async {
    state = const AsyncData(testPubkeyA);
    return testPubkeyA;
  }

  void switchTo(String pubkey) {
    state = AsyncData(pubkey);
  }
}

final _api = _MockApi();

void main() {
  setUpAll(() => RustLib.initMock(api: _api));
  setUp(() {
    _api.reset();
    _setInstalledVersion('2026.3.5');
  });

  Future<void> pumpChatListScreen(WidgetTester tester) async {
    await mountTestApp(
      tester,
      overrides: [authProvider.overrideWith(() => _MockAuthNotifier())],
    );
    await tester.pumpAndSettle();
  }

  group('ChatListScreen', () {
    testWidgets('displays header', (tester) async {
      await pumpChatListScreen(tester);

      expect(find.byType(ChatListHeader), findsOneWidget);
    });

    testWidgets('displays slate container', (tester) async {
      await pumpChatListScreen(tester);

      expect(find.byType(WnSlate), findsOneWidget);
    });

    testWidgets('displays chat list', (tester) async {
      await pumpChatListScreen(tester);

      expect(find.byType(WnChatList), findsOneWidget);
    });

    testWidgets('search and filters hidden initially', (tester) async {
      _api.initialChats = [
        _chatSummary(id: testPubkeyA, pendingConfirmation: false),
      ];
      await pumpChatListScreen(tester);

      expect(find.byType(ChatListSearchAndFilters), findsNothing);
    });

    testWidgets('search and filters appear on pull down', (tester) async {
      _api.initialChats = [
        _chatSummary(id: testPubkeyA, pendingConfirmation: false),
      ];
      await pumpChatListScreen(tester);

      final gesture = await tester.startGesture(const Offset(200, 400));
      await gesture.moveBy(const Offset(0, 200));
      await tester.pump();

      expect(find.byType(ChatListSearchAndFilters), findsOneWidget);
      await gesture.up();
      await tester.pumpAndSettle();
    });

    testWidgets('tapping avatar navigates to settings', (tester) async {
      await pumpChatListScreen(tester);
      await tester.tap(find.byKey(const Key('avatar_button')));
      await tester.pumpAndSettle();

      expect(find.byType(SettingsScreen), findsOneWidget);
    });

    testWidgets('tapping chat icon navigates to user search', (tester) async {
      await pumpChatListScreen(tester);
      await tester.tap(find.byKey(const Key('chat_add_button')));
      await tester.pumpAndSettle();
      expect(find.byType(UserSearchScreen), findsOneWidget);
    });

    testWidgets('hides no internet notice', (tester) async {
      await pumpChatListScreen(tester);
      await tester.pump();
      expect(find.text('Waiting for internet connection'), findsNothing);
    });

    group('without chats', () {
      testWidgets('shows welcome notice', (tester) async {
        await pumpChatListScreen(tester);

        expect(find.byType(WnSystemNotice), findsOneWidget);
        expect(find.text('Your profile is ready'), findsOneWidget);
      });

      testWidgets('shows welcome notice description', (tester) async {
        await pumpChatListScreen(tester);

        expect(
          find.textContaining('Find people'),
          findsWidgets,
        );
      });

      testWidgets('shows find people button', (tester) async {
        await pumpChatListScreen(tester);

        expect(find.byKey(const Key('find_people_button')), findsOneWidget);
      });

      testWidgets('shows share profile button', (tester) async {
        await pumpChatListScreen(tester);

        expect(find.byKey(const Key('share_profile_button')), findsOneWidget);
      });

      testWidgets('shows slogan in body', (tester) async {
        await pumpChatListScreen(tester);

        expect(find.byKey(const Key('welcome_slogan')), findsOneWidget);
        expect(find.textContaining('Decentralized'), findsOneWidget);
        expect(find.textContaining('uncensorable'), findsOneWidget);
        expect(find.textContaining('secure messaging'), findsOneWidget);
      });

      testWidgets('search is available on pull when chat list is empty', (tester) async {
        await pumpChatListScreen(tester);

        expect(find.byType(ChatListSearchAndFilters), findsNothing);

        final gesture = await tester.startGesture(const Offset(200, 400));
        await gesture.moveBy(const Offset(0, 200));
        await tester.pump();

        expect(find.byType(ChatListSearchAndFilters), findsOneWidget);
        await gesture.up();
        await tester.pumpAndSettle();
      });

      testWidgets('tapping find people navigates to user search', (tester) async {
        await pumpChatListScreen(tester);
        await tester.tap(find.byKey(const Key('find_people_button')));
        await tester.pumpAndSettle();

        expect(find.byType(UserSearchScreen), findsOneWidget);
      });

      testWidgets('tapping share profile navigates to share profile', (tester) async {
        await pumpChatListScreen(tester);
        await tester.tap(find.byKey(const Key('share_profile_button')));
        await tester.pumpAndSettle();

        expect(find.byType(ShareProfileScreen), findsOneWidget);
      });

      testWidgets('dismissing welcome notice hides it', (tester) async {
        await pumpChatListScreen(tester);

        expect(find.byType(WnSystemNotice), findsOneWidget);

        await tester.tap(find.byKey(const Key('systemNotice_actionIcon')));
        await tester.pumpAndSettle();

        expect(find.byType(WnSystemNotice), findsNothing);
      });

      testWidgets('keeps showing slogan after dismissing notice', (tester) async {
        await pumpChatListScreen(tester);
        await tester.tap(find.byKey(const Key('systemNotice_actionIcon')));
        await tester.pumpAndSettle();

        expect(find.byKey(const Key('welcome_slogan')), findsOneWidget);
        expect(find.textContaining('Decentralized'), findsOneWidget);
        expect(find.text('No chats yet'), findsNothing);
        expect(find.text('Start a conversation'), findsNothing);
      });

      testWidgets('welcome notice reappears after switching accounts', (tester) async {
        final mockAuth = _SwitchableAuthNotifier();
        await mountTestApp(
          tester,
          overrides: [authProvider.overrideWith(() => mockAuth)],
        );
        await tester.pumpAndSettle();

        expect(find.byType(WnSystemNotice), findsOneWidget);

        await tester.tap(find.byKey(const Key('systemNotice_actionIcon')));
        await tester.pumpAndSettle();

        expect(find.byType(WnSystemNotice), findsNothing);

        mockAuth.switchTo(testPubkeyB);
        await tester.pumpAndSettle();

        expect(find.byType(WnSystemNotice), findsOneWidget);
      });
    });

    group('with chats', () {
      setUp(
        () => _api.initialChats = [
          _chatSummary(id: testPubkeyA, pendingConfirmation: true),
          _chatSummary(id: testPubkeyB, pendingConfirmation: false),
        ],
      );

      testWidgets('shows chat tiles', (tester) async {
        await pumpChatListScreen(tester);

        expect(find.byType(ChatListTile), findsNWidgets(2));
      });

      testWidgets('shows chat tiles in the correct order', (tester) async {
        await pumpChatListScreen(tester);
        final tiles = tester.widgetList<ChatListTile>(find.byType(ChatListTile)).toList();

        expect(tiles.first.key, const Key(testPubkeyA));
        expect(tiles.last.key, const Key(testPubkeyB));
      });

      testWidgets('hides welcome notice when chats exist', (tester) async {
        await pumpChatListScreen(tester);

        expect(find.byType(WnSystemNotice), findsNothing);
      });

      testWidgets('hides empty state when chats exist', (tester) async {
        await pumpChatListScreen(tester);

        expect(find.text('No chats yet'), findsNothing);
      });

      testWidgets('tapping pending chat navigates to invite screen', (tester) async {
        await pumpChatListScreen(tester);
        await tester.tap(find.byType(ChatListTile).first);
        await tester.pumpAndSettle();

        expect(find.byType(ChatInviteScreen), findsOneWidget);
      });

      testWidgets('tapping accepted chat navigates to chat screen', (tester) async {
        await pumpChatListScreen(tester);
        await tester.tap(find.byType(ChatListTile).last);
        await tester.pumpAndSettle();

        expect(find.byType(ChatScreen), findsOneWidget);
      });
    });

    group('system notice', () {
      testWidgets('shows error notice when pin action fails', (tester) async {
        _api.initialChats = [
          _chatSummary(id: testPubkeyA, pendingConfirmation: false),
        ];
        await pumpChatListScreen(tester);

        await tester.longPress(find.byType(ChatListTile));
        await tester.pumpAndSettle();

        final pinAction = find.byKey(const Key('context_menu_action_pin'));
        if (pinAction.evaluate().isNotEmpty) {
          await tester.tap(pinAction);
          await tester.pumpAndSettle();

          expect(find.byType(WnSystemNotice), findsOneWidget);
        }
      });
    });

    group('update notice', () {
      final originalUrlLauncher = UrlLauncherPlatform.instance;
      tearDown(() => UrlLauncherPlatform.instance = originalUrlLauncher);

      testWidgets('shows update notice when a newer version is available', (tester) async {
        _api.zapstoreVersion = '2026.4.0';

        await pumpChatListScreen(tester);
        await tester.pump();

        expect(find.text('Update available'), findsOneWidget);
        expect(find.byKey(const Key('update_now_button')), findsOneWidget);
      });

      testWidgets('shows version in update notice description', (tester) async {
        _api.zapstoreVersion = '2026.4.0';

        await pumpChatListScreen(tester);
        await tester.pump();

        expect(find.textContaining('2026.4.0'), findsOneWidget);
      });

      testWidgets('does not show update notice when version matches', (tester) async {
        _api.zapstoreVersion = '2026.3.5';

        await pumpChatListScreen(tester);
        await tester.pump();

        expect(find.text('Update available'), findsNothing);
        expect(find.byKey(const Key('update_now_button')), findsNothing);
      });

      testWidgets('does not show update notice when Zapstore has no release', (tester) async {
        _api.zapstoreVersion = null;

        await pumpChatListScreen(tester);
        await tester.pump();

        expect(find.text('Update available'), findsNothing);
      });

      testWidgets('dismissing update notice hides it', (tester) async {
        _api.zapstoreVersion = '2026.4.0';

        await pumpChatListScreen(tester);
        await tester.pump();

        expect(find.text('Update available'), findsOneWidget);

        await tester.tap(find.byKey(const Key('systemNotice_actionIcon')));
        await tester.pumpAndSettle();

        expect(find.text('Update available'), findsNothing);
      });

      testWidgets('first chat tile is not hidden behind the update banner', (tester) async {
        _api.zapstoreVersion = '2026.4.0';
        _api.initialChats = [
          _chatSummary(id: testPubkeyA, pendingConfirmation: false, name: 'Alice'),
        ];

        await pumpChatListScreen(tester);
        await tester.pump();
        await tester.pumpAndSettle();

        final slateBottom = tester.getBottomLeft(find.byType(WnSlate)).dy;
        final tileTop = tester.getTopLeft(find.byType(ChatListTile)).dy;

        expect(
          tileTop,
          greaterThanOrEqualTo(slateBottom),
          reason: 'First chat tile should not be hidden under the update banner',
        );
      });

      testWidgets('update notice takes priority over welcome notice', (tester) async {
        // No chats → welcome notice would normally show.
        // Newer version available → update notice should show instead.
        _api.zapstoreVersion = '2026.4.0';

        await pumpChatListScreen(tester);
        await tester.pump();

        expect(find.text('Update available'), findsOneWidget);
        expect(find.text('Your profile is ready'), findsNothing);
      });

      testWidgets('welcome notice shown after update notice is dismissed', (tester) async {
        // No chats, so welcome notice is also pending.
        _api.zapstoreVersion = '2026.4.0';

        await pumpChatListScreen(tester);
        await tester.pump();

        await tester.tap(find.byKey(const Key('systemNotice_actionIcon')));
        await tester.pumpAndSettle();

        expect(find.text('Your profile is ready'), findsOneWidget);
      });

      testWidgets('tapping Update now launches Zapstore URL with externalApplication mode', (
        tester,
      ) async {
        final mockLauncher = _MockUrlLauncher();
        UrlLauncherPlatform.instance = mockLauncher;

        _api.zapstoreVersion = '2026.4.0';

        await pumpChatListScreen(tester);
        await tester.pump();

        expect(find.byKey(const Key('update_now_button')), findsOneWidget);

        await tester.tap(find.byKey(const Key('update_now_button')));
        await tester.pump();

        expect(mockLauncher.calls, hasLength(1));
        expect(
          mockLauncher.calls.first.url,
          equals('https://zapstore.dev/apps/org.parres.whitenoise'),
        );
        expect(
          mockLauncher.calls.first.options.mode,
          equals(PreferredLaunchMode.externalApplication),
        );
      });

      testWidgets('tapping Update now falls back to default mode when externalApplication fails', (
        tester,
      ) async {
        final mockLauncher = _MockUrlLauncher()..returnValue = false;
        UrlLauncherPlatform.instance = mockLauncher;

        _api.zapstoreVersion = '2026.4.0';

        await pumpChatListScreen(tester);
        await tester.pump();

        expect(find.byKey(const Key('update_now_button')), findsOneWidget);

        await tester.tap(find.byKey(const Key('update_now_button')));
        await tester.pump();

        // Two calls: first externalApplication (fails), then fallback with default mode.
        expect(mockLauncher.calls, hasLength(2));
        expect(
          mockLauncher.calls[0].url,
          equals('https://zapstore.dev/apps/org.parres.whitenoise'),
        );
        expect(mockLauncher.calls[0].options.mode, equals(PreferredLaunchMode.externalApplication));
        expect(
          mockLauncher.calls[1].url,
          equals('https://zapstore.dev/apps/org.parres.whitenoise'),
        );
        expect(mockLauncher.calls[1].options.mode, equals(PreferredLaunchMode.platformDefault));
      });
    });

    group('search', () {
      Future<void> revealSearchBar(WidgetTester tester) async {
        final gesture = await tester.startGesture(const Offset(200, 400));
        await gesture.moveBy(const Offset(0, 200));
        await tester.pump();
        await gesture.up();
        await tester.pumpAndSettle();
      }

      setUp(
        () => _api.initialChats = [
          _chatSummary(id: testPubkeyA, pendingConfirmation: false, name: 'Alice'),
          _chatSummary(id: testPubkeyB, pendingConfirmation: false, name: 'Bob'),
          _chatSummary(id: testPubkeyC, pendingConfirmation: false, name: 'Engineering Team'),
        ],
      );

      testWidgets('filters chats by search query', (tester) async {
        await pumpChatListScreen(tester);
        await revealSearchBar(tester);

        await tester.enterText(find.byType(TextField), 'Alice');
        await tester.pump();

        expect(find.byType(ChatListTile), findsOneWidget);
      });

      testWidgets('shows all chats when search is cleared', (tester) async {
        await pumpChatListScreen(tester);
        await revealSearchBar(tester);

        await tester.enterText(find.byType(TextField), 'Alice');
        await tester.pump();
        expect(find.byType(ChatListTile), findsOneWidget);

        await tester.enterText(find.byType(TextField), '');
        await tester.pump();
        expect(find.byType(ChatListTile), findsNWidgets(3));
      });

      testWidgets('search is case-insensitive', (tester) async {
        await pumpChatListScreen(tester);
        await revealSearchBar(tester);

        await tester.enterText(find.byType(TextField), 'alice');
        await tester.pump();

        expect(find.byType(ChatListTile), findsOneWidget);
      });

      testWidgets('shows no results message for non-matching query', (tester) async {
        await pumpChatListScreen(tester);
        await revealSearchBar(tester);

        await tester.enterText(find.byType(TextField), 'Zorro');
        await tester.pump();

        expect(find.byType(ChatListTile), findsNothing);
        expect(find.text('No results'), findsOneWidget);
        expect(find.text('No chats yet'), findsNothing);
      });

      testWidgets('search bar stays visible when no results match', (tester) async {
        await pumpChatListScreen(tester);
        await revealSearchBar(tester);

        await tester.enterText(find.byType(TextField), 'Zorro');
        await tester.pump();

        expect(find.byType(ChatListSearchAndFilters), findsOneWidget);
        expect(find.byType(TextField), findsOneWidget);
      });

      testWidgets('passes onSearchChanged callback to ChatListSearchAndFilters', (tester) async {
        await pumpChatListScreen(tester);
        await revealSearchBar(tester);

        final widget = tester.widget<ChatListSearchAndFilters>(
          find.byType(ChatListSearchAndFilters),
        );
        expect(widget.onSearchChanged, isNotNull);
      });

      testWidgets('OS back closes search header without popping screen', (tester) async {
        await pumpChatListScreen(tester);
        await revealSearchBar(tester);
        expect(find.byType(ChatListSearchAndFilters), findsOneWidget);

        await tester.binding.handlePopRoute();
        await tester.pumpAndSettle();

        expect(find.byType(ChatListScreen), findsOneWidget);
        expect(find.byType(ChatListSearchAndFilters), findsNothing);
      });

      testWidgets('OS back clears search query without popping screen', (tester) async {
        await pumpChatListScreen(tester);
        await revealSearchBar(tester);
        await tester.enterText(find.byType(TextField), 'Alice');
        await tester.pump();
        expect(find.byType(ChatListTile), findsOneWidget);

        await tester.binding.handlePopRoute();
        await tester.pumpAndSettle();

        expect(find.byType(ChatListScreen), findsOneWidget);
        expect(find.byType(ChatListTile), findsNWidgets(3));
      });

      testWidgets('OS back with no search active does not consume pop', (tester) async {
        await pumpChatListScreen(tester);

        final handled = await tester.binding.handlePopRoute();
        await tester.pumpAndSettle();

        expect(handled, isFalse);
      });
    });

    group('archive filter', () {
      setUp(
        () => _api.initialChats = [
          _chatSummary(id: testPubkeyA, pendingConfirmation: false, name: 'Alice'),
        ],
      );

      Future<void> revealSearchHeader(WidgetTester tester) async {
        final gesture = await tester.startGesture(const Offset(200, 400));
        await gesture.moveBy(const Offset(0, 200));
        await tester.pump();
        await gesture.up();
        await tester.pumpAndSettle();
      }

      testWidgets('neither chip visible without pull-down', (tester) async {
        await pumpChatListScreen(tester);

        expect(find.byKey(const Key('filter_chip_chats')), findsNothing);
        expect(find.byKey(const Key('filter_chip_archive')), findsNothing);
      });

      testWidgets('both chips appear side-by-side after pull-down', (tester) async {
        await pumpChatListScreen(tester);
        await revealSearchHeader(tester);

        expect(find.byKey(const Key('filter_chip_chats')), findsOneWidget);
        expect(find.byKey(const Key('filter_chip_archive')), findsOneWidget);
        expect(find.byKey(const Key('filter_chips_row')), findsOneWidget);
      });

      testWidgets('tapping Archive chip in search header shows archived empty state', (
        tester,
      ) async {
        await pumpChatListScreen(tester);
        await revealSearchHeader(tester);

        await tester.tap(find.byKey(const Key('filter_chip_archive')));
        await tester.pumpAndSettle();

        expect(find.byKey(const Key('archived_chats_empty')), findsOneWidget);
        expect(find.text('No archived chats'), findsOneWidget);
      });

      testWidgets(
        'tapping Archive chip in search header shows archived chat tiles when available',
        (
          tester,
        ) async {
          _api.initialArchivedChats = [
            _chatSummary(id: testPubkeyA, pendingConfirmation: false, name: 'Alice'),
          ];
          await pumpChatListScreen(tester);
          await revealSearchHeader(tester);

          await tester.tap(find.byKey(const Key('filter_chip_archive')));
          await tester.pumpAndSettle();

          expect(find.byKey(const Key('archived_chats_empty')), findsNothing);
          expect(find.byKey(const Key(testPubkeyA)), findsOneWidget);
        },
      );

      testWidgets('both chips pinned and always visible in archive mode', (tester) async {
        await pumpChatListScreen(tester);
        await revealSearchHeader(tester);

        await tester.tap(find.byKey(const Key('filter_chip_archive')));
        await tester.pumpAndSettle();

        expect(find.byKey(const Key('filter_chip_chats')), findsOneWidget);
        expect(find.byKey(const Key('filter_chip_archive')), findsOneWidget);
        expect(find.byKey(const Key('filter_chips_row')), findsOneWidget);
      });

      testWidgets('chips remain visible in archive mode after search is closed', (tester) async {
        await pumpChatListScreen(tester);
        await revealSearchHeader(tester);
        await tester.tap(find.byKey(const Key('filter_chip_archive')));
        await tester.pumpAndSettle();

        await tester.drag(find.byKey(const Key('chat_list')), const Offset(0, -200));
        await tester.pumpAndSettle();

        expect(find.byKey(const Key('filter_chip_chats')), findsOneWidget);
        expect(find.byKey(const Key('filter_chip_archive')), findsOneWidget);
      });

      testWidgets('tapping Chats chip hides archived empty state', (tester) async {
        await pumpChatListScreen(tester);
        await revealSearchHeader(tester);

        await tester.tap(find.byKey(const Key('filter_chip_archive')));
        await tester.pumpAndSettle();

        await tester.tap(find.byKey(const Key('filter_chip_chats')));
        await tester.pumpAndSettle();

        expect(find.byKey(const Key('archived_chats_empty')), findsNothing);
      });

      testWidgets('tapping Chats chip shows chats group', (tester) async {
        await pumpChatListScreen(tester);
        await revealSearchHeader(tester);

        await tester.tap(find.byKey(const Key('filter_chip_archive')));
        await tester.pumpAndSettle();
        expect(find.byKey(const Key(testPubkeyA)), findsNothing);

        await tester.tap(find.byKey(const Key('filter_chip_chats')));
        await tester.pumpAndSettle();

        expect(find.byKey(const Key(testPubkeyA)), findsOneWidget);
      });

      testWidgets('welcome notice is not shown in archived view', (tester) async {
        _api.initialChats = [];
        await pumpChatListScreen(tester);
        await revealSearchHeader(tester);

        await tester.tap(find.byKey(const Key('filter_chip_archive')));
        await tester.pumpAndSettle();

        expect(find.byKey(const Key('welcome_notice')), findsNothing);
      });

      testWidgets('neither chip visible with no chats before pull-down', (tester) async {
        _api.initialChats = [];
        await pumpChatListScreen(tester);

        expect(find.byKey(const Key('filter_chip_chats')), findsNothing);
        expect(find.byKey(const Key('filter_chip_archive')), findsNothing);
      });

      testWidgets('chips visible after pull-down with no chats', (tester) async {
        _api.initialChats = [];
        await pumpChatListScreen(tester);
        await revealSearchHeader(tester);

        expect(find.byKey(const Key('filter_chip_chats')), findsOneWidget);
        expect(find.byKey(const Key('filter_chip_archive')), findsOneWidget);
        expect(find.byKey(const Key('filter_chips_row')), findsOneWidget);
      });

      testWidgets('chips pinned and always visible in archived empty state', (tester) async {
        _api.initialChats = [];
        await pumpChatListScreen(tester);
        await revealSearchHeader(tester);

        await tester.tap(find.byKey(const Key('filter_chip_archive')));
        await tester.pumpAndSettle();

        expect(find.byKey(const Key('archived_chats_empty')), findsOneWidget);
        expect(find.byKey(const Key('filter_chip_chats')), findsOneWidget);
        expect(find.byKey(const Key('filter_chip_archive')), findsOneWidget);
        expect(find.byKey(const Key('filter_chips_row')), findsOneWidget);
      });
    });
    group('when offline', () {
      mountOfflineTestApp(WidgetTester tester) async {
        await mountTestApp(
          tester,
          overrides: [
            authProvider.overrideWith(() => _MockAuthNotifier()),
            offlineProvider.overrideWith((ref) => Stream.value(true)),
          ],
        );
        await tester.pumpAndSettle();
      }

      testWidgets('shows no internet notice', (tester) async {
        await mountOfflineTestApp(tester);
        expect(find.text('Waiting for internet connection'), findsOneWidget);
      });

      testWidgets('disables start chat button', (tester) async {
        await mountOfflineTestApp(tester);
        final startChatButton = tester.widget<WnIconButton>(
          find.byKey(const Key('chat_add_button')),
        );
        expect(startChatButton.disabled, isTrue);
        expect(startChatButton.onPressed, isNull);
      });
    });
  });
}
