import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:recording_tool/features/recording/platform/recording_channel_names.dart';
import 'package:recording_tool/features/recording/view-model/view_model_recording.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  const defaultClipboardTitle = '';
  const validClipboardText =
      '{"title":"王东方 丨李想 空中格斗赛","startTimestamp":1717334400,"endTimestamp":1717334400,"filename":"选手名称_选手ID_赛事名称_赛项","address":"广州市番禺区·粤港澳大湾区青年人才双创小镇"}';

  Future<void> setClipboardText(String? text) async {
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(SystemChannels.platform, (call) async {
          if (call.method == 'Clipboard.getData') {
            return text == null ? null : <String, dynamic>{'text': text};
          }
          return null;
        });
  }

  tearDown(() {
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(SystemChannels.platform, null);
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(
          const MethodChannel(RecordingChannelNames.method),
          null,
        );
  });

  group('RecordingViewModel', () {
    test('initializes with default clipboard and session state', () {
      final container = ProviderContainer();
      addTearDown(container.dispose);

      final model = container.read(recordingViewModelProvider);
      expect(model.hasValidClipboardInfo, isFalse);
      expect(model.clipboardRecordingModel.title, defaultClipboardTitle);
      expect(model.session.isPreviewReady, isFalse);
      expect(model.session.isRecording, isFalse);
      expect(model.session.zoomRatio, 1.0);
      expect(model.session.minZoomRatio, 1.0);
      expect(model.session.maxZoomRatio, 3.0);
    });
  });

  group('RecordingViewModel.setZoomRatio', () {
    test('updates zoom ratio from native response', () async {
      TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
          .setMockMethodCallHandler(
            const MethodChannel(RecordingChannelNames.method),
            (call) async {
              expect(call.method, 'setZoomRatio');
              expect(call.arguments, <String, dynamic>{'zoomRatio': 2.0});
              return <String, dynamic>{
                'zoomRatio': 2.0,
                'minZoomRatio': 1.0,
                'maxZoomRatio': 3.0,
              };
            },
          );
      final container = ProviderContainer();
      addTearDown(container.dispose);

      await container.read(recordingViewModelProvider.notifier).setZoomRatio(2);

      final session = container.read(recordingViewModelProvider).session;
      expect(session.zoomRatio, 2.0);
      expect(session.minZoomRatio, 1.0);
      expect(session.maxZoomRatio, 3.0);
      expect(session.errorMessage, isNull);
    });

    test('clamps requested zoom ratio before invoking native', () async {
      final calls = <MethodCall>[];
      TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
          .setMockMethodCallHandler(
            const MethodChannel(RecordingChannelNames.method),
            (call) async {
              calls.add(call);
              return <String, dynamic>{
                'zoomRatio': 1.0,
                'minZoomRatio': 1.0,
                'maxZoomRatio': 1.0,
              };
            },
          );
      final container = ProviderContainer();
      addTearDown(container.dispose);

      await container.read(recordingViewModelProvider.notifier).setZoomRatio(4);

      expect(calls.single.arguments, <String, dynamic>{'zoomRatio': 3.0});
      expect(container.read(recordingViewModelProvider).session.zoomRatio, 1.0);
    });

    test(
      'keeps previous zoom ratio and stores error when native fails',
      () async {
        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
            .setMockMethodCallHandler(
              const MethodChannel(RecordingChannelNames.method),
              (call) async {
                throw PlatformException(
                  code: 'ZOOM_FAILED',
                  message: 'Zoom is unavailable',
                );
              },
            );
        final container = ProviderContainer();
        addTearDown(container.dispose);

        await container
            .read(recordingViewModelProvider.notifier)
            .setZoomRatio(2);

        final session = container.read(recordingViewModelProvider).session;
        expect(session.zoomRatio, 1.0);
        expect(session.errorMessage, 'Zoom is unavailable');
      },
    );
  });

  group('recordingGalleryPermissionsForHost', () {
    test('requests only add-only photo permission on iOS', () {
      final permissions = recordingGalleryPermissionsForHost(
        isIOS: true,
        isAndroid: false,
      );

      expect(permissions, <Permission>[Permission.photosAddOnly]);
      expect(permissions, isNot(contains(Permission.photos)));
    });

    test('keeps Android gallery permissions unchanged', () {
      final permissions = recordingGalleryPermissionsForHost(
        isIOS: false,
        isAndroid: true,
      );

      expect(permissions, <Permission>[Permission.videos, Permission.storage]);
    });
  });

  group('RecordingViewModel.getClipboardContent', () {
    test(
      'updates state when clipboard contains valid mini program JSON',
      () async {
        await setClipboardText(validClipboardText);
        final container = ProviderContainer();
        addTearDown(container.dispose);

        final result = await container
            .read(recordingViewModelProvider.notifier)
            .getClipboardContent();

        expect(result, ClipboardReadResult.success);
        final model = container.read(recordingViewModelProvider);
        expect(model.hasValidClipboardInfo, isTrue);
        expect(model.clipboardRecordingModel.title, '王东方 丨李想 空中格斗赛');
        expect(model.clipboardRecordingModel.startTimestamp, 1717334400);
        expect(model.clipboardRecordingModel.endTimestamp, 1717334400);
        expect(model.clipboardRecordingModel.address, '广州市番禺区·粤港澳大湾区青年人才双创小镇');
        expect(model.clipboardRecordingModel.filename, '选手名称_选手ID_赛事名称_赛项');
      },
    );

    test('returns empty when clipboard is empty', () async {
      await setClipboardText('');
      final container = ProviderContainer();
      addTearDown(container.dispose);

      final result = await container
          .read(recordingViewModelProvider.notifier)
          .getClipboardContent();

      expect(result, ClipboardReadResult.empty);
      final model = container.read(recordingViewModelProvider);
      expect(model.hasValidClipboardInfo, isFalse);
      expect(model.clipboardRecordingModel.title, defaultClipboardTitle);
    });

    test('returns invalid when clipboard is not JSON', () async {
      await setClipboardText('hello');
      final container = ProviderContainer();
      addTearDown(container.dispose);

      final result = await container
          .read(recordingViewModelProvider.notifier)
          .getClipboardContent();

      expect(result, ClipboardReadResult.invalid);
      expect(
        container
            .read(recordingViewModelProvider)
            .clipboardRecordingModel
            .title,
        defaultClipboardTitle,
      );
      expect(
        container.read(recordingViewModelProvider).hasValidClipboardInfo,
        isFalse,
      );
    });

    test('returns invalid when clipboard JSON is not an object', () async {
      await setClipboardText('[1,2,3]');
      final container = ProviderContainer();
      addTearDown(container.dispose);

      final result = await container
          .read(recordingViewModelProvider.notifier)
          .getClipboardContent();

      expect(result, ClipboardReadResult.invalid);
      expect(
        container
            .read(recordingViewModelProvider)
            .clipboardRecordingModel
            .title,
        defaultClipboardTitle,
      );
    });

    test(
      'returns invalid when clipboard JSON misses required address',
      () async {
        await setClipboardText('{"title":"王东方 丨李想 空中格斗赛"}');
        final container = ProviderContainer();
        addTearDown(container.dispose);

        final result = await container
            .read(recordingViewModelProvider.notifier)
            .getClipboardContent();

        expect(result, ClipboardReadResult.invalid);
        expect(
          container
              .read(recordingViewModelProvider)
              .clipboardRecordingModel
              .title,
          defaultClipboardTitle,
        );
      },
    );

    test('updates state when clipboard omits optional timestamps', () async {
      await setClipboardText(
        '{"title":"郑昌梦 丨黄伟依 空中格斗赛 小学组","address":"广东省汕头市番禺区青蓝街 111 号","filename":"郑昌梦_黄伟依_6月3日测试-1_空中格斗赛"}',
      );
      final container = ProviderContainer();
      addTearDown(container.dispose);

      final result = await container
          .read(recordingViewModelProvider.notifier)
          .getClipboardContent();

      expect(result, ClipboardReadResult.success);
      final model = container.read(recordingViewModelProvider);
      expect(model.hasValidClipboardInfo, isTrue);
      expect(model.clipboardRecordingModel.startTimestamp, isNull);
      expect(model.clipboardRecordingModel.endTimestamp, isNull);
      expect(model.clipboardRecordingModel.filename, '郑昌梦_黄伟依_6月3日测试-1_空中格斗赛');
    });

    test('returns invalid when clipboard JSON has wrong field type', () async {
      await setClipboardText(
        '{"title":"王东方 丨李想 空中格斗赛","startTimestamp":"1717334400","endTimestamp":1717334400,"address":"广州市番禺区·粤港澳大湾区青年人才双创小镇"}',
      );
      final container = ProviderContainer();
      addTearDown(container.dispose);

      final result = await container
          .read(recordingViewModelProvider.notifier)
          .getClipboardContent();

      expect(result, ClipboardReadResult.invalid);
      expect(
        container
            .read(recordingViewModelProvider)
            .clipboardRecordingModel
            .title,
        defaultClipboardTitle,
      );
    });

    test('returns invalid when title is blank', () async {
      await setClipboardText(
        '{"title":"   ","startTimestamp":1717334400,"endTimestamp":1717334400,"address":"广州市"}',
      );
      final container = ProviderContainer();
      addTearDown(container.dispose);

      final result = await container
          .read(recordingViewModelProvider.notifier)
          .getClipboardContent();

      expect(result, ClipboardReadResult.invalid);
      expect(
        container.read(recordingViewModelProvider).hasValidClipboardInfo,
        isFalse,
      );
    });
  });
}
