@TestOn('windows')
library;

import 'dart:ffi';
import 'dart:io';

import 'package:flutter_test/flutter_test.dart';
import 'package:matrix/matrix.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:sqlite3/open.dart' as sqlite_open;

/// Setup-Helfer für die Call-Tests mit den Autopilot-Test-Accounts (gegated
/// wie `live_encryption_e2e_test.dart`, läuft NIE im normalen `flutter test`):
/// stellt sicher, dass ein Voice-Channel-Raum („Pyramid Autopilot Voice",
/// `io.pyramid.voice_channel` + Presence-PowerLevel 0 wie im
/// create_join_dialog) existiert und BEIDE Test-Nutzer Mitglied sind.
/// Danach können zwei GUI-Instanzen (PYRAMID_PROFILE_DIR) den Kanal joinen.
///
/// Kein Logout am Ende nötig/gewollt für die GUI-Sessions – die Skript-Geräte
/// melden sich selbst ab, die GUI-Tokens bleiben unberührt.

const _kVoiceRoomName = 'Pyramid Autopilot Voice';

Map<String, String>? _loadAccounts() {
  final profile = Platform.environment['USERPROFILE'] ?? '';
  final file = File('$profile\\.pyramid-autopilot\\test-accounts.txt');
  if (!file.existsSync()) return null;
  final map = <String, String>{};
  for (final line in file.readAsLinesSync()) {
    final t = line.trim();
    if (t.isEmpty || t.startsWith('#')) continue;
    final i = t.indexOf('=');
    if (i > 0) map[t.substring(0, i)] = t.substring(i + 1);
  }
  return map;
}

String? _findSqliteDll() {
  for (final config in ['Debug', 'Profile', 'Release']) {
    final file = File('build/windows/x64/runner/$config/sqlite3.dll');
    if (file.existsSync()) return file.absolute.path;
  }
  return null;
}

Future<Client> _login(String name, String user, String pass, Uri hs) async {
  // Wegwerf-Client: In-Memory-DB reicht (die DLL aus dem App-Build laden,
  // der flutter_tester bringt selbst keine sqlite3.dll mit).
  final dll = _findSqliteDll()!;
  final factory = createDatabaseFactoryFfi(
    ffiInit: () {
      sqlite_open.open.overrideFor(
        sqlite_open.OperatingSystem.windows,
        () => DynamicLibrary.open(dll),
      );
    },
    noIsolate: true,
  );
  final db = await factory.openDatabase(inMemoryDatabasePath);
  final sdkDb = await MatrixSdkDatabase.init(name, database: db);
  final client = Client(name, database: sdkDb);
  await client.init(waitForFirstSync: false, waitUntilLoadCompletedLoaded: false);
  await client.checkHomeserver(hs);
  await client.login(
    LoginType.mLoginPassword,
    identifier: AuthenticationUserIdentifier(user: user),
    password: pass,
    initialDeviceDisplayName: name,
  );
  return client;
}

void main() {
  final acc = _loadAccounts();
  final skip = Platform.environment['PYRAMID_LIVE_TEST'] != '1'
      ? 'Live-Setup nur mit PYRAMID_LIVE_TEST=1'
      : acc == null
          ? 'Test-Account-Datei fehlt'
          : null;

  test('Voice-Channel-Testraum existiert und beide Test-Nutzer sind drin',
      () async {
    final hs = Uri.parse(acc!['homeserver']!);
    final c1 = await _login('AutopilotCallSetup1', acc['user1']!, acc['pass1']!, hs);
    final c2 = await _login('AutopilotCallSetup2', acc['user2']!, acc['pass2']!, hs);
    try {
      // ignore: avoid_print
      print('[setup] beide Logins ok, erster Sync…');
      await c1.oneShotSync().timeout(const Duration(seconds: 60));
      // ignore: avoid_print
      print('[setup] c1 sync ok, suche Raum…');

      Room? voiceRoom;
      for (final r in c1.rooms) {
        if (r.membership == Membership.join &&
            r.name == _kVoiceRoomName &&
            r.getState(EventTypes.RoomCreate)?.content['type'] ==
                'io.pyramid.voice_channel') {
          voiceRoom = r;
          break;
        }
      }

      if (voiceRoom == null) {
        // ignore: avoid_print
        print('[setup] Raum fehlt, lege an…');
        final roomId = await c1.createRoom(
          name: _kVoiceRoomName,
          visibility: Visibility.private,
          preset: CreateRoomPreset.privateChat,
          creationContent: {'type': 'io.pyramid.voice_channel'},
        );
        // Presence muss für normale Mitglieder schreibbar sein – exakt wie
        // in create_join_dialog.dart (Continuwuity ignoriert
        // power_level_content_override beim createRoom).
        final plRaw =
            await c1.getRoomStateWithKey(roomId, EventTypes.RoomPowerLevels, '');
        final pl = Map<String, dynamic>.from(plRaw as Map);
        final events = Map<String, dynamic>.from(pl['events'] as Map? ?? {});
        events['io.pyramid.voice.presence'] = 0;
        pl['events'] = events;
        await c1.setRoomStateWithKey(roomId, EventTypes.RoomPowerLevels, '', pl);
        // ignore: avoid_print
        print('[setup] Raum $roomId angelegt, warte auf Sync…');
        await c1.waitForRoomInSync(roomId, join: true)
            .timeout(const Duration(seconds: 60));
        voiceRoom = c1.getRoomById(roomId);
      }
      expect(voiceRoom, isNotNull);
      // ignore: avoid_print
      print('[setup] Raum da: ${voiceRoom!.id}, hole user2 rein…');

      // user2 einladen (falls noch nicht Mitglied) und beitreten lassen.
      final u2 = c2.userID!;
      await c2.oneShotSync().timeout(const Duration(seconds: 60));
      // ignore: avoid_print
      print('[setup] c2 sync ok…');
      final r2 = c2.getRoomById(voiceRoom.id);
      if (r2 == null || r2.membership != Membership.join) {
        if (r2 == null || r2.membership != Membership.invite) {
          try {
            await voiceRoom.invite(u2);
          } on MatrixException catch (e) {
            // "already in the room" o. ä. ist ok.
            if (e.error != MatrixError.M_FORBIDDEN) rethrow;
          }
        }
        await c2.joinRoom(voiceRoom.id);
        await c2.waitForRoomInSync(voiceRoom.id, join: true)
            .timeout(const Duration(seconds: 60));
      }

      final members = await voiceRoom.requestParticipants();
      final joined = members
          .where((m) => m.membership == Membership.join)
          .map((m) => m.id.toLowerCase())
          .toSet();
      expect(joined.contains(acc['user1']!.toLowerCase()), isTrue);
      expect(joined.contains(u2.toLowerCase()), isTrue,
          reason: 'user2 ist dem Voice-Raum nicht beigetreten');
      // ignore: avoid_print
      print('Voice-Testraum bereit: ${voiceRoom.id}');
    } finally {
      // Nur die Skript-Geräte abmelden – GUI-Sessions bleiben unberührt.
      try {
        await c1.logout();
      } catch (_) {}
      try {
        await c2.logout();
      } catch (_) {}
      await c1.dispose(closeDatabase: false);
      await c2.dispose(closeDatabase: false);
    }
  },
      skip: skip,
      timeout: const Timeout(Duration(minutes: 5)));
}
