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

import 'package:flutter/foundation.dart'
    show ValueNotifier, debugDefaultTargetPlatformOverride;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:smplayer/core/infra/player_engine/media3_player_engine.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  const methodChannel = MethodChannel('smplayer/media3_engine');
  const eventChannel = MethodChannel('smplayer/media3_engine/events');

  setUp(() {
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(methodChannel, (call) async {
          switch (call.method) {
            case 'create':
              return <String, dynamic>{
                'playerId': 7,
                'ffmpegAvailable': true,
                'ffmpegVersion': 'test-ffmpeg',
              };
            case 'open':
            case 'setVolume':
            case 'setResizeMode':
            case 'replayKeyState':
            case 'dispose':
              return null;
          }
          fail('Unexpected media3 method call: ${call.method}');
        });
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(eventChannel, (call) async => null);
  });

  tearDown(() {
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(methodChannel, null);
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(eventChannel, null);
    debugDefaultTargetPlatformOverride = null;
  });

  group('Media3PlayerEngine 平台守卫', () {
    test('桌面（isAndroid=false）构造抛 UnsupportedError', () {
      expect(
        () => Media3PlayerEngine(isAndroid: false),
        throwsA(isA<UnsupportedError>()),
      );
    });

    test('Android（isAndroid=true）构造不抛', () {
      final engine = Media3PlayerEngine(isAndroid: true);
      addTearDown(engine.dispose);
      expect(engine.displayName, 'media3');
    });

    test('open 成功后请求 native 重放关键状态', () async {
      final calls = <String>[];
      TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
          .setMockMethodCallHandler(methodChannel, (call) async {
            calls.add(call.method);
            switch (call.method) {
              case 'create':
                return <String, dynamic>{
                  'playerId': 7,
                  'ffmpegAvailable': true,
                  'ffmpegVersion': 'test-ffmpeg',
                };
              case 'open':
              case 'setVolume':
              case 'replayKeyState':
              case 'dispose':
                return null;
            }
            fail('Unexpected media3 method call: ${call.method}');
          });

      final engine = Media3PlayerEngine(isAndroid: true);
      addTearDown(engine.dispose);

      await engine.open('http://127.0.0.1/video.mkv');

      expect(calls, containsAllInOrder(<String>['open', 'replayKeyState']));
    });
  });

  group('Media3PlayerEngine Android 视频视图', () {
    test('源码固定使用 Hybrid Composition，避免旧设备 TLHC/HC fallback 改变 HDR 路径', () {
      final source = File(
        'lib/core/infra/player_engine/media3_player_engine.dart',
      ).readAsStringSync();

      expect(source, contains('initExpensiveAndroidView'));
      expect(source, isNot(contains('initSurfaceAndroidView')));
    });

    test('native SurfaceView 不主动改 z-order，避免 Flutter HC 下黑屏', () {
      final source = File(
        'plugins/media3_engine/android/src/main/kotlin/com/smplayer/media3_engine/Media3PlatformView.kt',
      ).readAsStringSync();

      expect(source, isNot(contains('setZOrderMediaOverlay(true)')));
      expect(source, isNot(contains('setZOrderOnTop(true)')));
    });

    testWidgets('playerId 就位后使用原生 SurfaceView PlatformView', (tester) async {
      debugDefaultTargetPlatformOverride = TargetPlatform.android;
      try {
        final engine = Media3PlayerEngine(isAndroid: true);
        final fit = ValueNotifier<BoxFit>(BoxFit.contain);
        addTearDown(engine.dispose);
        addTearDown(fit.dispose);

        await tester.pumpWidget(
          Directionality(
            textDirection: TextDirection.ltr,
            child: Builder(
              builder: (context) =>
                  SizedBox.expand(child: engine.buildVideoView(context, fit)),
            ),
          ),
        );
        expect(find.byType(PlatformViewLink), findsNothing);

        await engine.open('http://127.0.0.1/video.mkv');
        await tester.pump();

        final view = tester.widget<PlatformViewLink>(
          find.byType(PlatformViewLink),
        );
        expect(view.viewType, 'smplayer/media3_surface');
      } finally {
        debugDefaultTargetPlatformOverride = null;
      }
    });

    testWidgets('open 等待 native ready 时仍先挂载视频 PlatformView', (tester) async {
      debugDefaultTargetPlatformOverride = TargetPlatform.android;
      final openCompleter = Completer<Object?>();
      TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
          .setMockMethodCallHandler(methodChannel, (call) async {
            switch (call.method) {
              case 'create':
                return <String, dynamic>{
                  'playerId': 7,
                  'ffmpegAvailable': true,
                  'ffmpegVersion': 'test-ffmpeg',
                };
              case 'open':
                return openCompleter.future;
              case 'setVolume':
              case 'setResizeMode':
              case 'replayKeyState':
              case 'dispose':
                return null;
            }
            fail('Unexpected media3 method call: ${call.method}');
          });

      try {
        final engine = Media3PlayerEngine(isAndroid: true);
        final fit = ValueNotifier<BoxFit>(BoxFit.contain);
        addTearDown(engine.dispose);
        addTearDown(fit.dispose);

        await tester.pumpWidget(
          Directionality(
            textDirection: TextDirection.ltr,
            child: Builder(
              builder: (context) =>
                  SizedBox.expand(child: engine.buildVideoView(context, fit)),
            ),
          ),
        );
        expect(find.byType(PlatformViewLink), findsNothing);

        final openFuture = engine.open('http://127.0.0.1/video.mkv');
        await tester.pump();
        await tester.pump();

        expect(openCompleter.isCompleted, isFalse);
        expect(find.byType(PlatformViewLink), findsOneWidget);

        openCompleter.complete();
        await openFuture;
      } finally {
        debugDefaultTargetPlatformOverride = null;
      }
    });

    testWidgets('playerId 就位与 fit 变化会同步 resize mode 到 native', (tester) async {
      debugDefaultTargetPlatformOverride = TargetPlatform.android;
      final resizeModes = <String>[];
      TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
          .setMockMethodCallHandler(methodChannel, (call) async {
            switch (call.method) {
              case 'create':
                return <String, dynamic>{
                  'playerId': 7,
                  'ffmpegAvailable': true,
                  'ffmpegVersion': 'test-ffmpeg',
                };
              case 'open':
              case 'setVolume':
              case 'dispose':
              case 'replayKeyState':
                return null;
              case 'setResizeMode':
                expect(call.arguments, containsPair('playerId', 7));
                resizeModes.add(
                  (call.arguments as Map<Object?, Object?>)['mode']! as String,
                );
                return null;
            }
            fail('Unexpected media3 method call: ${call.method}');
          });

      try {
        final engine = Media3PlayerEngine(isAndroid: true);
        final fit = ValueNotifier<BoxFit>(BoxFit.contain);
        addTearDown(engine.dispose);
        addTearDown(fit.dispose);

        await tester.pumpWidget(
          Directionality(
            textDirection: TextDirection.ltr,
            child: Builder(
              builder: (context) =>
                  SizedBox.expand(child: engine.buildVideoView(context, fit)),
            ),
          ),
        );

        await engine.open('http://127.0.0.1/video.mkv');
        await tester.pump();
        expect(resizeModes, <String>['fit']);

        fit.value = BoxFit.fill;
        await tester.pump();
        expect(resizeModes, <String>['fit', 'fill']);

        fit.value = BoxFit.cover;
        await tester.pump();
        expect(resizeModes, <String>['fit', 'fill', 'zoom']);

        fit.value = BoxFit.contain;
        await tester.pump();
        expect(resizeModes, <String>['fit', 'fill', 'zoom', 'fit']);
      } finally {
        debugDefaultTargetPlatformOverride = null;
      }
    });
  });

  group('Media3 native deferred prepare', () {
    test('源码包含 surface 到达超时和 pending open 取消完成逻辑', () {
      final source = File(
        'plugins/media3_engine/android/src/main/kotlin/com/smplayer/media3_engine/Media3PlayerInstance.kt',
      ).readAsStringSync();

      expect(source, contains('SURFACE_ATTACH_TIMEOUT_MS'));
      expect(source, contains('surface not attached within timeout'));
      expect(source, contains('cancelPendingOpen'));
      expect(source, contains('open_cancelled'));
      expect(source, contains('clearPendingSurfaceTimeout'));
    });

    test('MethodChannel 暴露 replayKeyState', () {
      final source = File(
        'plugins/media3_engine/android/src/main/kotlin/com/smplayer/media3_engine/Media3EnginePlugin.kt',
      ).readAsStringSync();

      expect(source, contains('"replayKeyState"'));
      expect(source, contains('player.replayKeyState()'));
    });
  });

  group('Media3 single-flight _ensureCreated', () {
    test('并发 prewarmNative + open 只执行一次 native create', () async {
      int createCallCount = 0;
      final createCompleter = Completer<Object?>();

      TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
          .setMockMethodCallHandler(methodChannel, (call) async {
            switch (call.method) {
              case 'create':
                createCallCount++;
                return createCompleter.future;
              case 'open':
                expect(
                  (call.arguments as Map)['playerId'],
                  equals(42),
                  reason: 'open must use the playerId returned by create',
                );
                return null;
              case 'setVolume':
              case 'replayKeyState':
              case 'dispose':
                return null;
            }
            fail('Unexpected media3 method call: ${call.method}');
          });

      final engine = Media3PlayerEngine(isAndroid: true);
      addTearDown(engine.dispose);

      final prewarmFuture = engine.prewarmNative();
      final openFuture = engine.open('http://127.0.0.1/video.mkv');

      expect(
        createCallCount,
        equals(1),
        reason: 'concurrent prewarm + open should share one create call',
      );

      createCompleter.complete(<String, dynamic>{
        'playerId': 42,
        'ffmpegAvailable': true,
        'ffmpegVersion': 'test-ffmpeg',
      });

      await prewarmFuture;
      await openFuture;

      expect(
        createCallCount,
        equals(1),
        reason: 'create must have been called exactly once',
      );
    });

    test('dispose 期间 create 返回时释放孤儿 native player', () async {
      final createCompleter = Completer<Object?>();
      final disposedPlayerIds = <int>[];

      TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
          .setMockMethodCallHandler(methodChannel, (call) async {
            switch (call.method) {
              case 'create':
                return createCompleter.future;
              case 'dispose':
                final disposePlayerId =
                    (call.arguments as Map)['playerId'] as int;
                disposedPlayerIds.add(disposePlayerId);
                return null;
              case 'setVolume':
                return null;
            }
            fail('Unexpected media3 method call: ${call.method}');
          });

      final engine = Media3PlayerEngine(isAndroid: true);

      final prewarmFuture = engine.prewarmNative();

      await engine.dispose();

      createCompleter.complete(<String, dynamic>{
        'playerId': 99,
        'ffmpegAvailable': true,
        'ffmpegVersion': 'test-ffmpeg',
      });

      await prewarmFuture;

      expect(
        disposedPlayerIds,
        contains(99),
        reason: 'orphan native player created after dispose must be released',
      );
    });

    test('create 失败后允许下次重试', () async {
      int createAttempts = 0;

      TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
          .setMockMethodCallHandler(methodChannel, (call) async {
            switch (call.method) {
              case 'create':
                createAttempts++;
                if (createAttempts == 1) {
                  throw PlatformException(
                    code: 'INIT_FAILED',
                    message: 'simulated first-create failure',
                  );
                }
                return <String, dynamic>{
                  'playerId': 7,
                  'ffmpegAvailable': true,
                  'ffmpegVersion': 'test-ffmpeg',
                };
              case 'open':
              case 'setVolume':
              case 'replayKeyState':
              case 'dispose':
                return null;
            }
            fail('Unexpected media3 method call: ${call.method}');
          });

      final engine = Media3PlayerEngine(isAndroid: true);
      addTearDown(engine.dispose);

      expect(engine.prewarmNative(), throwsA(isA<PlatformException>()));
      await Future<void>.delayed(Duration.zero);

      await engine.open('http://127.0.0.1/video.mkv');
      expect(createAttempts, equals(2), reason: 'retry after first failure');
    });
  });
}
