import 'dart:convert';

import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:spotiflac_android/services/platform_bridge.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();
  const backendChannel = MethodChannel('com.zarz.spotiflac/backend');

  tearDown(() async {
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(backendChannel, null);
  });

  test('display metadata uses the lightweight scan result directly', () async {
    final invokedMethods = <String>[];
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(backendChannel, (call) async {
          invokedMethods.add(call.method);
          if (call.method == 'readAudioMetadata') {
            return jsonEncode({
              'trackName': 'Song',
              'artistName': 'Artist',
              'bitDepth': 24,
              'sampleRate': 96000,
              'bitrate': 1800,
              'format': 'flac',
            });
          }
          fail('Unexpected full metadata read: ${call.method}');
        });

    final result = await PlatformBridge.readDisplayAudioMetadata(
      'content://downloads/song.flac',
    );

    expect(invokedMethods, ['readAudioMetadata']);
    expect(result['title'], 'Song');
    expect(result['artist'], 'Artist');
    expect(result['bit_depth'], 24);
    expect(result['sample_rate'], 96000);
    expect(result['audio_codec'], 'flac');
  });

  test(
    'display metadata falls back when scan only parsed the filename',
    () async {
      final invokedMethods = <String>[];
      TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
          .setMockMethodCallHandler(backendChannel, (call) async {
            invokedMethods.add(call.method);
            if (call.method == 'readAudioMetadata') {
              return jsonEncode({
                'trackName': 'Filename fallback',
                'metadataFromFilename': true,
                'format': 'flac',
              });
            }
            if (call.method == 'readFileMetadata') {
              return jsonEncode({
                'title': 'Embedded title',
                'audio_codec': 'opus',
              });
            }
            fail('Unexpected method: ${call.method}');
          });

      final result = await PlatformBridge.readDisplayAudioMetadata(
        'content://downloads/misnamed.flac',
      );

      expect(invokedMethods, ['readAudioMetadata', 'readFileMetadata']);
      expect(result['title'], 'Embedded title');
      expect(result['audio_codec'], 'opus');
    },
  );

  test('metadata search sends the explicitly selected extension', () async {
    MethodCall? capturedCall;
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(backendChannel, (call) async {
          capturedCall = call;
          return jsonEncode([
            {
              'id': 'track-1',
              'name': 'Song',
              'provider_id': 'selected-metadata',
            },
          ]);
        });

    final results = await PlatformBridge.searchTracksWithMetadataProvider(
      'selected-metadata',
      'Song Artist',
      limit: 5,
    );

    expect(capturedCall?.method, 'searchTracksWithMetadataProvider');
    expect(capturedCall?.arguments, {
      'extension_id': 'selected-metadata',
      'query': 'Song Artist',
      'limit': 5,
    });
    expect(results.single['provider_id'], 'selected-metadata');
  });

  test('metadata custom search sends the manifest track filter', () async {
    MethodCall? capturedCall;
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(backendChannel, (call) async {
          capturedCall = call;
          return jsonEncode([
            {'id': 'track-1', 'name': 'Song', 'provider_id': 'custom-metadata'},
          ]);
        });

    final results = await PlatformBridge.customSearchWithExtension(
      'custom-metadata',
      'Song Artist',
      options: {'limit': 5, 'filter': 'songs'},
    );

    expect(capturedCall?.method, 'customSearchWithExtension');
    final arguments = capturedCall?.arguments as Map<Object?, Object?>?;
    expect(arguments, isNotNull);
    expect(arguments?['extension_id'], 'custom-metadata');
    expect(arguments?['query'], 'Song Artist');
    expect(arguments?['options'], jsonEncode({'limit': 5, 'filter': 'songs'}));
    expect(arguments?['request_id'], isA<String>());
    expect(results.single['provider_id'], 'custom-metadata');
  });

  test('SAF inspection sends one batch and decodes repair results', () async {
    MethodCall? capturedCall;
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(backendChannel, (call) async {
          capturedCall = call;
          return jsonEncode({
            'results': [
              {
                'key': 'history-1',
                'status': 'found',
                'uri': 'content://tree/repaired.flac',
                'file_name': 'repaired.flac',
                'relative_dir': 'Artist/Album',
              },
            ],
          });
        });

    final results = await PlatformBridge.inspectSafFiles([
      {
        'key': 'history-1',
        'tree_uri': 'content://tree/root',
        'current_uri': 'content://tree/stale.flac',
        'relative_dir': 'Artist/Album',
        'file_names': ['song.flac', 'song_converted.flac'],
      },
    ]);

    expect(capturedCall?.method, 'inspectSafFiles');
    final arguments = capturedCall?.arguments as Map<Object?, Object?>;
    final requests = jsonDecode(arguments['requests_json'] as String) as List;
    expect(requests, hasLength(1));
    expect((requests.single as Map)['file_names'], hasLength(2));
    expect(results.single['status'], 'found');
    expect(results.single['relative_dir'], 'Artist/Album');
  });

  test('iOS bookmark access releases the matching native lease', () async {
    final calls = <MethodCall>[];
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(backendChannel, (call) async {
          calls.add(call);
          if (call.method == 'startAccessingIosBookmark') {
            return {'path': '/music', 'token': 'lease-1'};
          }
          if (call.method == 'stopAccessingIosBookmark') return null;
          fail('Unexpected method: ${call.method}');
        });

    final access = await PlatformBridge.startAccessingIosBookmark('bookmark');
    expect(access?.path, '/music');
    expect(access?.token, 'lease-1');
    await PlatformBridge.stopAccessingIosBookmark(access!);

    expect(calls[1].method, 'stopAccessingIosBookmark');
    expect(calls[1].arguments, {'token': 'lease-1'});
  });
}
