import 'dart:async';
import 'dart:io';
import 'dart:ui';

import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:flutter_filament/widgets/filament_widget.dart';
import '../lib/main.dart' as app;

void main() {
  final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized()
      as IntegrationTestWidgetsFlutterBinding;

  late String platformIdentifier;
  if (Platform.isIOS) {
    platformIdentifier = "ios";
  } else if (Platform.isAndroid) {
    platformIdentifier = "android";
  } else if (Platform.isMacOS) {
    platformIdentifier = "macos";
  } else if (Platform.isWindows) {
    platformIdentifier = "windows";
  } else if (Platform.isLinux) {
    platformIdentifier = "linux";
  } else {
    throw Exception("Unexpected platform");
  }

  int _counter = 0;

  Future _snapshot(WidgetTester tester, String label, [int seconds = 0]) async {
    await tester.pumpAndSettle(Duration(milliseconds: 16));
    for (int i = 0; i < seconds; i++) {
      await Future.delayed(Duration(seconds: 1));
      await tester.pumpAndSettle(Duration(milliseconds: 16));
    }
    await tester.pumpAndSettle(Duration(milliseconds: 16));
    var screenshotPath = '$platformIdentifier/${_counter}_$label';
    if (Platform.isIOS) {
      // this is currently hanging on Android
      // see https://github.com/flutter/flutter/issues/127306
      // it is also not yet implemented on Windows or MacOS
      await binding.convertFlutterSurfaceToImage();
      final bytes = await binding.takeScreenshot(screenshotPath);
    }
    _counter++;
  }

  Future tap(WidgetTester tester, String label, [int seconds = 0]) async {
    var target = find.text(label, skipOffstage: false);

    if (!target.hasFound) {
      print("Couldn't find target, waiting 100ms");
      await tester.pump(const Duration(milliseconds: 100));
      target = find.text(label);
    }

    await tester.tap(target.first);
    await _snapshot(tester, label.replaceAll(RegExp("[ -:]"), ""), seconds);
  }

  Future<void> pumpUntilFound(
    WidgetTester tester,
    Finder finder, {
    Duration timeout = const Duration(seconds: 30),
  }) async {
    bool timerDone = false;
    final timer = Timer(
        timeout, () => throw TimeoutException("Pump until has timed out"));
    while (timerDone != true) {
      await tester.pump();

      final found = tester.any(finder);
      if (found) {
        timerDone = true;
      }
    }
    timer.cancel();
  }

  testWidgets('test', (WidgetTester tester) async {
    app.main();
    await pumpUntilFound(tester, find.byType(app.ExampleWidget));

    await tester.pumpAndSettle();

    await _snapshot(tester, "fresh");

    await tap(tester, "Controller / Viewer");
    await tap(tester, "Create FilamentController (default ubershader)");
    await tap(tester, "Controller / Viewer");
    await tap(tester, "Create FilamentViewer",
        4); // on older devices this may take a while, so let's insert a length delay

    await tap(tester, "Scene");
    await tap(tester, "Rendering");
    await tap(tester, "Set continuous rendering to true");

    await tap(tester, "Scene");
    await tap(tester, "Assets");
    await tap(tester, "Shapes");
    await tap(tester, "Load GLB");

    await tester.pump();

    await tap(tester, "Scene");
    await tap(tester, "Assets");
    await tap(tester, "Load skybox", 1);

    await tap(tester, "Scene");
    await tap(tester, "Assets");
    await tap(tester, "Load IBL", 1);

    final Offset pointerLocation =
        tester.getCenter(find.byType(FilamentWidget));
    TestPointer testPointer = TestPointer(1, PointerDeviceKind.mouse);

    // scroll/zoom
    testPointer.hover(pointerLocation);
    await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, 1.0)));
    await tester.pumpAndSettle();
    await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, 1.0)));
    await tester.pumpAndSettle();
    await _snapshot(tester, "zoomin");

    // rotate
    testPointer =
        TestPointer(1, PointerDeviceKind.mouse, null, kTertiaryButton);
    testPointer.hover(pointerLocation);
    await tester.sendEventToBinding(testPointer.down(pointerLocation));
    await tester.pumpAndSettle();
    await tester.sendEventToBinding(
        testPointer.move(pointerLocation + Offset(10.0, 10.0)));
    await tester.pumpAndSettle();
    await tester.sendEventToBinding(
        testPointer.move(pointerLocation + Offset(20.0, 20.0)));
    await tester.pumpAndSettle();
    await tester.sendEventToBinding(testPointer.up());

    await _snapshot(tester, "rotate", 2);

    // pan
    testPointer = TestPointer(1, PointerDeviceKind.mouse, null, kPrimaryButton);
    testPointer.hover(pointerLocation);
    await tester.sendEventToBinding(testPointer.down(pointerLocation));
    await tester
        .sendEventToBinding(testPointer.move(pointerLocation + Offset(0, 1.0)));

    for (int i = 0; i < 60; i++) {
      await tester.sendEventToBinding(testPointer
          .move(pointerLocation + Offset(i.toDouble() * 2, i.toDouble() * 2)));
      await tester.pumpAndSettle();
    }
    await tester.sendEventToBinding(testPointer.up());
    await tester.pumpAndSettle();

    await _snapshot(tester, "pan");

    await tap(tester, "Scene");
    await tap(tester, "Assets");
    await tap(tester, "Shapes");
    await tap(tester, "Transform to unit cube");

    await tap(tester, "Scene");
    await tap(tester, "Assets");
    await tap(tester, "Shapes");
    await tap(tester, "Set position to 1, 1, -1");

    await tap(tester, "Scene");
    await tap(tester, "Camera");
    await tap(tester, "Disable frustum culling");

    await tap(tester, "Scene");
    await tap(tester, "Rendering");
    await tap(tester, "Set tone mapping to linear");

    await tap(tester, "Scene");
    await tap(tester, "Camera");
    await tap(tester, 'Set to first camera in last added asset');

    await tap(tester, "Move to last added asset");
    await tap(tester, "Move to 1, 1, -1");

    await tap(tester, 'Toggle viewport size', 1);
    await tap(tester, 'Toggle viewport size', 1);
    await tap(tester, 'Toggle viewport size', 1);
  });
}
