import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:ocideck/models/settings.dart';
import 'package:ocideck/models/slide.dart';
import 'package:ocideck/widgets/presentation/fullscreen_presenter.dart';

Widget _host(List<Slide> slides) {
  return MaterialApp(
    home: FullscreenPresenter(
      slides: slides,
      projectPath: null,
      themeProfile: const ThemeProfile(),
      initialIndex: 0,
    ),
  );
}

void main() {
  final slides = [
    Slide.create(
      SlideType.bullets,
    ).copyWith(title: 'Eerste', bullets: ['a'], notes: 'Mijn spiekbriefje'),
    Slide.create(SlideType.bullets).copyWith(title: 'Tweede', bullets: ['b']),
  ];

  test('dual-screen mode is available on every desktop platform', () {
    expect(
      shouldUseDualScreen(
        isMacOS: true,
        isWindows: false,
        isLinux: false,
        displayCount: 2,
      ),
      isTrue,
    );
    expect(
      shouldUseDualScreen(
        isMacOS: false,
        isWindows: true,
        isLinux: false,
        displayCount: 2,
      ),
      isTrue,
    );
    expect(
      shouldUseDualScreen(
        isMacOS: false,
        isWindows: false,
        isLinux: true,
        displayCount: 2,
      ),
      isTrue,
    );
  });

  test('dual-screen mode requires a desktop platform and two displays', () {
    expect(
      shouldUseDualScreen(
        isMacOS: true,
        isWindows: false,
        isLinux: false,
        displayCount: 1,
      ),
      isFalse,
    );
    expect(
      shouldUseDualScreen(
        isMacOS: false,
        isWindows: false,
        isLinux: false,
        displayCount: 2,
      ),
      isFalse,
    );
  });

  test('autoplay audio or video takes precedence over slide timing', () {
    expect(
      autoAdvanceWaitsForMedia(
        Slide.create(SlideType.bullets).copyWith(
          advanceDuration: 3,
          audioPath: 'sound.mp3',
          audioAutoplay: true,
        ),
      ),
      isTrue,
    );
    expect(
      autoAdvanceWaitsForMedia(
        Slide.create(SlideType.video).copyWith(
          advanceDuration: 3,
          videoPath: 'movie.mp4',
          videoAutoplay: true,
        ),
      ),
      isTrue,
    );
    expect(
      autoAdvanceWaitsForMedia(
        Slide.create(SlideType.video).copyWith(
          advanceDuration: 3,
          videoPath: 'movie.mp4',
          videoAutoplay: false,
        ),
      ),
      isFalse,
    );
  });

  testWidgets('slide timer does not interrupt autoplay media', (tester) async {
    final mediaSlides = [
      Slide.create(SlideType.video).copyWith(
        title: 'Video blijft staan',
        videoPath: '/tmp/does-not-exist.mp4',
        videoAutoplay: true,
        advanceDuration: 3,
      ),
      Slide.create(SlideType.bullets).copyWith(title: 'Na video'),
    ];

    await tester.pumpWidget(_host(mediaSlides));
    await tester.pump(const Duration(seconds: 4));

    expect(find.text('Video blijft staan'), findsOneWidget);
    expect(find.text('Na video'), findsNothing);
    await tester.pumpWidget(const SizedBox());
  });

  testWidgets('checklist changes during presenting are persisted', (
    tester,
  ) async {
    Slide? updated;
    final checklistSlides = [
      Slide.create(SlideType.bullets).copyWith(
        title: 'Taken',
        bullets: ['[ ] Live afvinken'],
        listStyle: ListStyle.checklist,
      ),
    ];
    await tester.pumpWidget(
      MaterialApp(
        home: FullscreenPresenter(
          slides: checklistSlides,
          projectPath: null,
          themeProfile: const ThemeProfile(),
          initialIndex: 0,
          onSlideChanged: (slide) => updated = slide,
        ),
      ),
    );
    await tester.pump();

    await tester.tap(
      find.byKey(const ValueKey('checklist-preview-toggle-0-0')),
    );
    await tester.pump();

    expect(updated?.bullets, ['[x] Live afvinken']);
    expect(find.text('☑ '), findsOneWidget);
  });

  testWidgets('starts in audience view without presenter chrome', (
    tester,
  ) async {
    await tester.pumpWidget(_host(slides));
    await tester.pump();

    expect(find.text('Eerste'), findsOneWidget);
    expect(find.text('1 / 2'), findsNothing); // no audience chrome
    expect(find.byIcon(Icons.help_outline), findsNothing);
    expect(find.byIcon(Icons.grid_view_rounded), findsNothing);
    expect(find.byIcon(Icons.co_present_outlined), findsNothing);
    expect(find.byIcon(Icons.close), findsNothing);
    expect(find.text('NOTITIES'), findsNothing); // presenter-only
    expect(find.text('VOLGENDE'), findsNothing);

    await tester.pumpWidget(const SizedBox()); // dispose → cancel clock timer
  });

  testWidgets('P toggles presenter view with notes and next slide', (
    tester,
  ) async {
    tester.view.physicalSize = const Size(1280, 800);
    tester.view.devicePixelRatio = 1.0;
    addTearDown(tester.view.resetPhysicalSize);
    addTearDown(tester.view.resetDevicePixelRatio);

    await tester.pumpWidget(_host(slides));
    await tester.pump();

    await tester.sendKeyEvent(LogicalKeyboardKey.keyP);
    await tester.pump();

    expect(find.text('NOTITIES'), findsOneWidget);
    expect(find.text('Mijn spiekbriefje'), findsOneWidget);
    expect(find.text('VOLGENDE'), findsOneWidget);
    expect(find.text('Slide 1 / 2'), findsOneWidget);

    await tester.pumpWidget(const SizedBox());
  });

  testWidgets('advancing updates the notes shown in presenter view', (
    tester,
  ) async {
    tester.view.physicalSize = const Size(1280, 800);
    tester.view.devicePixelRatio = 1.0;
    addTearDown(tester.view.resetPhysicalSize);
    addTearDown(tester.view.resetDevicePixelRatio);

    await tester.pumpWidget(_host(slides));
    await tester.pump();

    await tester.sendKeyEvent(LogicalKeyboardKey.keyP);
    await tester.pump();
    expect(find.text('Mijn spiekbriefje'), findsOneWidget);

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
    await tester.pump();

    // Slide 2 has no notes → placeholder, and we're on its end-of-deck next.
    expect(find.text('Geen notities voor deze slide.'), findsOneWidget);
    expect(find.text('Einde van de presentatie'), findsOneWidget);
    expect(find.text('Slide 2 / 2'), findsOneWidget);

    await tester.pumpWidget(const SizedBox());
  });

  testWidgets('B blanks the audience screen and toggles back', (tester) async {
    await tester.pumpWidget(_host(slides));
    await tester.pump();
    expect(find.text('Eerste'), findsOneWidget);

    // Blank to black: the slide disappears.
    await tester.sendKeyEvent(LogicalKeyboardKey.keyB);
    await tester.pump();
    expect(find.text('Eerste'), findsNothing);

    // Same key restores the slide.
    await tester.sendKeyEvent(LogicalKeyboardKey.keyB);
    await tester.pump();
    expect(find.text('Eerste'), findsOneWidget);

    await tester.pumpWidget(const SizedBox());
  });

  testWidgets('a blanked screen resumes on the next navigation press', (
    tester,
  ) async {
    await tester.pumpWidget(_host(slides));
    await tester.pump();

    await tester.sendKeyEvent(LogicalKeyboardKey.keyB);
    await tester.pump();
    expect(find.text('Eerste'), findsNothing);

    // First arrow un-blanks without advancing (still on slide 1).
    await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
    await tester.pump();
    expect(find.text('Eerste'), findsOneWidget);

    await tester.pumpWidget(const SizedBox());
  });

  testWidgets('G opens the grid and tapping a tile jumps to that slide', (
    tester,
  ) async {
    tester.view.physicalSize = const Size(1280, 800);
    tester.view.devicePixelRatio = 1.0;
    addTearDown(tester.view.resetPhysicalSize);
    addTearDown(tester.view.resetDevicePixelRatio);

    await tester.pumpWidget(_host(slides));
    await tester.pump();

    await tester.sendKeyEvent(LogicalKeyboardKey.keyG);
    await tester.pump();
    expect(find.text('Slide-overzicht'), findsOneWidget);

    // Tap the tile for slide 2.
    await tester.tap(find.text('2'));
    await tester.pump();

    // Grid closed and we jumped to slide 2.
    expect(find.text('Slide-overzicht'), findsNothing);
    expect(find.text('Tweede'), findsOneWidget);

    await tester.pumpWidget(const SizedBox());
  });

  testWidgets('End jumps to the last slide and Home back to the first', (
    tester,
  ) async {
    await tester.pumpWidget(_host(slides));
    await tester.pump();
    expect(find.text('Eerste'), findsOneWidget);

    await tester.sendKeyEvent(LogicalKeyboardKey.end);
    await tester.pump();
    expect(find.text('Tweede'), findsOneWidget);

    await tester.sendKeyEvent(LogicalKeyboardKey.home);
    await tester.pump();
    expect(find.text('Eerste'), findsOneWidget);

    await tester.pumpWidget(const SizedBox());
  });

  testWidgets('grid arrow keys move a cursor and Enter jumps to it', (
    tester,
  ) async {
    tester.view.physicalSize = const Size(1280, 800);
    tester.view.devicePixelRatio = 1.0;
    addTearDown(tester.view.resetPhysicalSize);
    addTearDown(tester.view.resetDevicePixelRatio);

    await tester.pumpWidget(_host(slides));
    await tester.pump();

    await tester.sendKeyEvent(LogicalKeyboardKey.keyG);
    await tester.pump();
    expect(find.text('Slide-overzicht'), findsOneWidget);

    // Move the cursor to slide 2 with the arrow key, then choose it.
    await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
    await tester.pump();
    await tester.sendKeyEvent(LogicalKeyboardKey.enter);
    await tester.pump();

    expect(find.text('Slide-overzicht'), findsNothing);
    expect(find.text('Tweede'), findsOneWidget);

    await tester.pumpWidget(const SizedBox());
  });

  testWidgets('typing a number and Enter jumps to that slide', (tester) async {
    final three = [
      ...slides,
      Slide.create(SlideType.bullets).copyWith(title: 'Derde', bullets: ['c']),
    ];
    await tester.pumpWidget(_host(three));
    await tester.pump();
    expect(find.text('Eerste'), findsOneWidget);

    // Type "3" → a badge appears, Enter jumps to slide 3.
    await tester.sendKeyEvent(LogicalKeyboardKey.digit3);
    await tester.pump();
    expect(find.text('3 / 3'), findsOneWidget); // badge "<typed> / <total>"

    await tester.sendKeyEvent(LogicalKeyboardKey.enter);
    await tester.pump();
    // Badge gone, now actually on slide 3.
    expect(find.text('Derde'), findsOneWidget);
    expect(find.text('3 / 3'), findsNothing);
    expect(find.byIcon(Icons.south_east), findsNothing); // badge icon gone

    await tester.pumpWidget(const SizedBox());
  });

  testWidgets('? toggles the shortcut cheatsheet', (tester) async {
    await tester.pumpWidget(_host(slides));
    await tester.pump();
    expect(find.text('Toetsenlegenda'), findsNothing);

    await tester.sendKeyEvent(LogicalKeyboardKey.keyH);
    await tester.pump();
    expect(find.text('Toetsenlegenda'), findsOneWidget);
    expect(find.text('Scherm wisselen (meerdere schermen)'), findsOneWidget);

    await tester.sendKeyEvent(LogicalKeyboardKey.escape);
    await tester.pump();
    expect(find.text('Toetsenlegenda'), findsNothing);
    // Esc closed the help, not the presentation.
    expect(find.text('Eerste'), findsOneWidget);

    await tester.pumpWidget(const SizedBox());
  });

  testWidgets('Escape closes the grid before it would exit', (tester) async {
    tester.view.physicalSize = const Size(1280, 800);
    tester.view.devicePixelRatio = 1.0;
    addTearDown(tester.view.resetPhysicalSize);
    addTearDown(tester.view.resetDevicePixelRatio);

    await tester.pumpWidget(_host(slides));
    await tester.pump();

    await tester.sendKeyEvent(LogicalKeyboardKey.keyG);
    await tester.pump();
    expect(find.text('Slide-overzicht'), findsOneWidget);

    // Esc closes the grid and leaves us in the presentation (no exit).
    await tester.sendKeyEvent(LogicalKeyboardKey.escape);
    await tester.pump();
    expect(find.text('Slide-overzicht'), findsNothing);
    expect(find.text('Eerste'), findsOneWidget);

    await tester.pumpWidget(const SizedBox());
  });
}
