import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:marianum_mobile/app.dart';
import 'package:marianum_mobile/main.dart' as app;
import 'package:marianum_mobile/routing/app_routes.dart';
import 'package:marianum_mobile/state/app/modules/app_modules.dart';
import 'package:marianum_mobile/state/app/modules/settings/bloc/settings_cubit.dart';
import 'package:marianum_mobile/view/pages/talk/widgets/chat_tile.dart';

/// Marketing-Screenshot-Lauf für die Stores. Loggt sich über den Demo-Login
/// (`demo@…`, siehe DemoMode) ein und nimmt einen kuratierten Satz Screens auf:
/// Stundenplan (hell + dunkel), Talk-Liste, Talk-Chat, Dateien, „Mehr"-Bereich
/// und Einstellungen. Ausgeführt über `flutter drive` mit
/// test_driver/integration_test.dart, orchestriert von tool/screenshots.sh
/// (Phone / 7″ / 10″).
///
/// Kein Netzwerk: der Demo-Modus beantwortet allen Backend-Verkehr aus
/// Fixtures, daher ist der Lauf deterministisch.
Future<void> main() async {
  final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  testWidgets('Store-Screenshots über den Demo-Login', (tester) async {
    // Der integration_test-Harness installiert eigene Error-Handler. app()
    // überschreibt sie in main() durch die produktiven (ClientErrorReporter),
    // was die End-of-Test-Buchführung des Harness sprengt (_pendingExceptionDetails).
    // Daher vorher sichern und nach main() zurücksetzen.
    final harnessFlutterOnError = FlutterError.onError;
    final harnessDispatcherOnError = PlatformDispatcher.instance.onError;

    _log('main() starten');
    await app.main();

    FlutterError.onError = harnessFlutterOnError;
    PlatformDispatcher.instance.onError = harnessDispatcherOnError;
    await tester.pump(const Duration(seconds: 1));

    _log('Login');
    await _login(tester);

    _log('warte auf App-Shell');
    await _pumpUntil(
      tester,
      find.byType(App),
      timeout: const Duration(seconds: 20),
    );
    _log('warte auf Post-Login-Splash-Ende');
    await _pumpUntilGone(
      tester,
      find.byKey(const ValueKey('post-login-splash')),
      timeout: const Duration(seconds: 12),
    );
    await tester.pump(const Duration(seconds: 1));

    // Screenshots auf Android brauchen eine einmalige Umwandlung der
    // Flutter-Surface in eine lesbare Image-Texture. Erst hier – nach Login und
    // Splash –, damit der sichtbare Screen erst kurz vor der Aufnahme „einfriert".
    if (Platform.isAndroid) {
      _log('convertFlutterSurfaceToImage');
      await binding.convertFlutterSurfaceToImage();
    }

    // 1 + 2: Stundenplan in beiden Themes.
    await _goTo(tester, Modules.timetable);
    _setTheme(tester, ThemeMode.light);
    await _capture(tester, binding, '1_stundenplan_hell');
    _setTheme(tester, ThemeMode.dark);
    await _capture(tester, binding, '2_stundenplan_dunkel');

    // Restlicher Satz einheitlich im hellen Theme.
    _setTheme(tester, ThemeMode.light);

    // 3: Talk-Liste.
    await _goTo(tester, Modules.talk);
    await _capture(tester, binding, '3_talk_liste');

    // 4: Talk-Chat (ersten Chat der Liste öffnen).
    await tester.tap(find.byType(ChatTile).first);
    await _capture(tester, binding, '4_talk_chat');
    _popPushedPage(tester);
    await tester.pump(const Duration(milliseconds: 500));

    // 5: Dateien.
    await _goTo(tester, Modules.files);
    await _capture(tester, binding, '5_dateien');

    // 6: „Mehr"-Bereich (letzter Tab, hinter den Modul-Tabs).
    _goToMore(tester);
    await _capture(tester, binding, '6_mehr');

    // 7: Einstellungen (Vollbild-Push).
    AppRoutes.openSettings(tester.element(find.byType(App)));
    await _capture(tester, binding, '7_einstellungen');
    _popPushedPage(tester);

    _log('fertig');
  });
}

void _log(String message) => debugPrint('SHOTS: $message');

Future<void> _login(WidgetTester tester) async {
  final loginVisible = await _pumpUntil(
    tester,
    find.byKey(const Key('login-username-field')),
  );
  if (!loginVisible) {
    _log('kein Login-Screen sichtbar – bereits angemeldet, überspringe Login');
    return;
  }
  await tester.enterText(
    find.byKey(const Key('login-username-field')),
    'demo@screenshots',
  );
  await tester.enterText(find.byKey(const Key('login-password-field')), 'demo');
  await tester.pump();
  await tester.tap(find.byKey(const Key('login-submit-button')));
  await tester.pump(const Duration(milliseconds: 500));
}

/// Setzt das App-Theme über den [SettingsCubit] (MaterialApp folgt
/// `settings.appTheme`). `val(write: true)` plant den Emit als Microtask.
void _setTheme(WidgetTester tester, ThemeMode mode) {
  final context = tester.element(find.byType(App));
  context.read<SettingsCubit>().val(write: true).appTheme = mode;
}

/// Navigiert zu [module] – über den Bottom-Tab, wenn er in der Leiste liegt,
/// sonst als Vollbild-Push. Größenunabhängig, da die Tab-Anzahl je Gerät variiert.
Future<void> _goTo(WidgetTester tester, Modules module) async {
  final appFinder = find.byType(App);
  if (appFinder.evaluate().isEmpty) {
    _log('WARN: App-Shell fehlt, kann $module nicht ansteuern');
    return;
  }
  final context = tester.element(appFinder);
  if (!AppRoutes.goToTab(context, module)) {
    final resolved = AppModule.modules(context)[module];
    if (resolved != null) AppRoutes.openModule(context, resolved);
  }
}

/// Springt auf den „Mehr"-Tab. Der liegt hinter den Modul-Tabs, sein Index ist
/// also die Anzahl der Bottom-Bar-Module.
void _goToMore(WidgetTester tester) {
  final context = tester.element(find.byType(App));
  final moreIndex = AppModule.getBottomBarModules(context).length;
  app.Main.bottomNavigator.jumpToTab(moreIndex);
}

/// Schließt einen per [AppRoutes] gepushten Vollbild-Screen wieder.
void _popPushedPage(WidgetTester tester) {
  final navigator = AppRoutes.rootNavigatorKey.currentState;
  if (navigator != null && navigator.canPop()) navigator.pop();
}

/// Lässt die Oberfläche zur Ruhe kommen und legt den Screenshot ab.
Future<void> _capture(
  WidgetTester tester,
  IntegrationTestWidgetsFlutterBinding binding,
  String name,
) async {
  // Feste Setzzeit statt „warte auf Spinner-Ende": die Demo-Fixtures kommen
  // synchron (keine künstlichen Delays), aber Module wie Dateien/Chat laden erst
  // beim Navigieren nach. Eine globale CircularProgressIndicator-Prüfung taugt
  // nicht, weil die PersistentTabView alle Tabs am Leben hält und
  // Avatar-Platzhalter dauerhaft einen Spinner zeigen. In 500ms-Schritten
  // pumpen, damit Timer/Microtasks (u.a. der Theme-Emit) durchlaufen.
  await _settle(tester, const Duration(seconds: 6));
  _log('-> $name: takeScreenshot');
  await binding.takeScreenshot(name);
}

/// Pumpt [duration] in 500ms-Schritten ab, damit die App async Fixtures laden
/// und ihre Frames rendern kann (Ersatz für pumpAndSettle, das an den
/// Dauer-Animationen hängen bleibt).
Future<void> _settle(WidgetTester tester, Duration duration) async {
  final steps = duration.inMilliseconds ~/ 500;
  for (var i = 0; i < steps; i++) {
    await tester.pump(const Duration(milliseconds: 500));
  }
}

/// Pumpt in kurzen Schritten, bis [finder] erscheint oder [timeout] abläuft.
/// Gibt zurück, ob [finder] gefunden wurde.
Future<bool> _pumpUntil(
  WidgetTester tester,
  Finder finder, {
  Duration timeout = const Duration(seconds: 10),
}) async {
  final end = DateTime.now().add(timeout);
  while (DateTime.now().isBefore(end)) {
    await tester.pump(const Duration(milliseconds: 200));
    if (finder.evaluate().isNotEmpty) return true;
  }
  return false;
}

/// Gegenstück zu [_pumpUntil]: pumpt, bis [finder] nichts mehr trifft.
Future<bool> _pumpUntilGone(
  WidgetTester tester,
  Finder finder, {
  Duration timeout = const Duration(seconds: 10),
}) async {
  final end = DateTime.now().add(timeout);
  while (DateTime.now().isBefore(end)) {
    await tester.pump(const Duration(milliseconds: 200));
    if (finder.evaluate().isEmpty) return true;
  }
  return false;
}
