import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/i18n/strings.g.dart';
import 'package:plezy/models/mpv_config_models.dart';
import 'package:plezy/screens/settings/mpv_config_screen.dart';
import 'package:plezy/services/base_shared_preferences_service.dart';
import 'package:plezy/services/settings_service.dart';
import 'package:plezy/theme/mono_theme.dart';
import 'package:plezy/widgets/dialog_action_button.dart';
import 'package:plezy/widgets/focusable_list_tile.dart';
import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart';
import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart';
import 'package:shared_preferences_platform_interface/types.dart';

import '../../test_helpers/prefs.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  setUpAll(() => LocaleSettings.setLocaleSync(AppLocale.en));

  testWidgets('typing burst coalesces to one full-document write', (tester) async {
    final backend = await _pumpEditor(tester, holdConfigWrites: true);

    await tester.enterText(find.byType(TextField), 'vo');
    await tester.enterText(find.byType(TextField), 'vol');
    await tester.enterText(find.byType(TextField), 'volume=80');

    await tester.pump(const Duration(milliseconds: 399));
    expect(backend.configWrites, isEmpty);

    await tester.pump(const Duration(milliseconds: 1));
    expect(backend.configWrites, ['volume=80']);
    expect(backend.maxActiveConfigWrites, 1);

    backend.completeNextConfigWrite();
    await tester.pump();

    expect(backend.durableConfig, 'volume=80');
  });

  testWidgets('new revisions wait for the active write and only the latest follows', (tester) async {
    final backend = await _pumpEditor(tester, holdConfigWrites: true);

    await tester.enterText(find.byType(TextField), 'first=1');
    await tester.pump(const Duration(milliseconds: 400));
    expect(backend.configWrites, ['first=1']);

    await tester.enterText(find.byType(TextField), 'second=2');
    await tester.enterText(find.byType(TextField), 'final=3');
    await tester.pump(const Duration(milliseconds: 400));

    expect(backend.configWrites, ['first=1']);
    expect(backend.activeConfigWrites, 1);

    backend.completeNextConfigWrite();
    await tester.pump();

    expect(backend.configWrites, ['first=1', 'final=3']);
    expect(backend.maxActiveConfigWrites, 1);
    expect(tester.widget<TextField>(find.byType(TextField)).controller!.text, 'final=3');

    backend.completeNextConfigWrite();
    await tester.pump();

    expect(backend.durableConfig, 'final=3');
  });

  testWidgets('manual Enter insertion joins the debounced writer', (tester) async {
    final backend = await _pumpEditor(tester, holdConfigWrites: true);

    await tester.enterText(find.byType(TextField), 'alpha=1');
    await tester.pump(const Duration(milliseconds: 400));
    backend.completeNextConfigWrite();
    await tester.pump();
    backend.configWrites.clear();

    await tester.sendKeyEvent(LogicalKeyboardKey.enter);
    expect(tester.widget<TextField>(find.byType(TextField)).controller!.text, 'alpha=1\n');

    await tester.pump(const Duration(milliseconds: 399));
    expect(backend.configWrites, isEmpty);
    await tester.pump(const Duration(milliseconds: 1));
    expect(backend.configWrites, ['alpha=1\n']);

    backend.completeNextConfigWrite();
    await tester.pump();
    expect(backend.durableConfig, 'alpha=1\n');
  });

  testWidgets('observed external replacement wins after an in-flight local write', (tester) async {
    final backend = await _pumpEditor(tester, holdConfigWrites: true);
    final settings = SettingsService.instance;

    await tester.enterText(find.byType(TextField), 'local=old');
    await tester.pump(const Duration(milliseconds: 400));
    expect(backend.configWrites, ['local=old']);

    unawaited(settings.write(SettingsService.mpvConfigText, 'external=new'));
    await tester.pump();
    expect(backend.configWrites, ['local=old', 'external=new']);

    backend.completeConfigWriteAt(1);
    await tester.pump();
    expect(tester.widget<TextField>(find.byType(TextField)).controller!.text, 'external=new');

    backend.completeNextConfigWrite();
    await tester.pump();
    expect(backend.configWrites, ['local=old', 'external=new', 'external=new']);

    backend.completeNextConfigWrite();
    await tester.pump();
    expect(backend.durableConfig, 'external=new');
  });

  testWidgets('focus loss flushes immediately and route pop waits for persistence', (tester) async {
    final backend = await _pumpEditor(tester, holdConfigWrites: true);

    await tester.enterText(find.byType(TextField), 'profile=gpu-hq');
    FocusManager.instance.primaryFocus?.unfocus();
    await tester.pump();

    expect(backend.configWrites, ['profile=gpu-hq']);

    unawaited(tester.binding.handlePopRoute());
    await tester.pump();
    expect(find.byType(MpvConfigScreen), findsOneWidget);

    backend.completeNextConfigWrite();
    await tester.pumpAndSettle();

    expect(find.byType(MpvConfigScreen), findsNothing);
    expect(find.text('home'), findsOneWidget);
    expect(backend.durableConfig, 'profile=gpu-hq');
  });

  testWidgets('dispose flushes its captured edit without using disposed UI', (tester) async {
    final backend = await _pumpEditor(tester, holdConfigWrites: true);

    await tester.enterText(find.byType(TextField), 'video-sync=display-resample');
    await tester.pumpWidget(const SizedBox.shrink());
    await tester.pump();

    expect(backend.configWrites, ['video-sync=display-resample']);

    backend.completeNextConfigWrite();
    await tester.pump();

    expect(tester.takeException(), isNull);
    expect(backend.durableConfig, 'video-sync=display-resample');
  });

  testWidgets('failed flush stays retryable and does not pop until retry succeeds', (tester) async {
    final backend = await _pumpEditor(tester, holdConfigWrites: true);
    const config = 'gpu-api=secret-test-value';

    await tester.enterText(find.byType(TextField), config);
    FocusManager.instance.primaryFocus?.unfocus();
    await tester.pump();

    backend.completeNextConfigWrite(error: PlatformException(code: 'write_failed'));
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 300));

    expect(find.text(t.settings.saveFailed), findsOneWidget);
    expect(find.textContaining('write_failed'), findsNothing);
    expect(backend.durableConfig, isNull);

    unawaited(tester.binding.handlePopRoute());
    await tester.pump();
    expect(backend.configWrites, [config, config]);
    expect(find.byType(MpvConfigScreen), findsOneWidget);

    backend.completeNextConfigWrite();
    await tester.pumpAndSettle();

    expect(find.byType(MpvConfigScreen), findsNothing);
    expect(backend.durableConfig, config);
  });

  testWidgets('saving a preset flushes the matching active text before success', (tester) async {
    final backend = await _pumpEditor(tester, holdConfigWrites: true);
    final settings = SettingsService.instance;

    await tester.enterText(find.byType(TextField), 'preset-source=yes');
    await tester.pump();
    final saveTile = tester.widget<FocusableListTile>(find.widgetWithText(FocusableListTile, t.mpvConfig.saveAsPreset));
    expect(saveTile.enabled, isTrue);
    saveTile.onTap!();
    await tester.pumpAndSettle();

    final dialog = find.byType(AlertDialog);
    expect(dialog, findsOneWidget);
    final nameField = find.descendant(of: dialog, matching: find.byType(TextField));
    expect(nameField, findsOneWidget);
    await tester.enterText(nameField, 'Saved');
    await tester.tap(find.widgetWithText(DialogActionButton, t.common.save));
    await tester.pump();

    expect(settings.read(SettingsService.mpvPresets), isEmpty);
    expect(find.text(t.mpvConfig.presetSaved), findsNothing);

    backend.completeNextConfigWrite();
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 300));

    final presets = settings.read(SettingsService.mpvPresets);
    expect(presets, hasLength(1));
    expect(presets.single.name, 'Saved');
    expect(presets.single.text, 'preset-source=yes');
    expect(find.text(t.mpvConfig.presetSaved), findsOneWidget);
  });

  testWidgets('preset replacement waits behind an active edit and wins last', (tester) async {
    final preset = MpvPreset(name: 'Cinema', text: 'profile=gpu-hq', createdAt: DateTime(2026));
    final backend = await _pumpEditor(tester, holdConfigWrites: true, presets: [preset]);

    await tester.enterText(find.byType(TextField), 'old-edit=yes');
    await tester.pump(const Duration(milliseconds: 400));
    expect(backend.configWrites, ['old-edit=yes']);

    await tester.tap(find.text('Cinema'));
    await tester.pump();
    expect(backend.configWrites, ['old-edit=yes']);
    expect(find.text(t.mpvConfig.presetLoaded), findsNothing);

    backend.completeNextConfigWrite();
    await tester.pump();
    expect(backend.configWrites, ['old-edit=yes', 'profile=gpu-hq']);
    expect(backend.maxActiveConfigWrites, 1);

    backend.completeNextConfigWrite();
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 300));

    expect(find.text(t.mpvConfig.presetLoaded), findsOneWidget);
    expect(backend.durableConfig, 'profile=gpu-hq');
  });

  testWidgets('clean external settings update replaces the editor', (tester) async {
    final backend = await _pumpEditor(tester);
    final settings = SettingsService.instance;

    await settings.write(SettingsService.mpvConfigText, 'external=yes');
    await tester.pump();

    expect(tester.widget<TextField>(find.byType(TextField)).controller!.text, 'external=yes');
    expect(backend.durableConfig, 'external=yes');
  });
}

Future<_ControlledPreferences> _pumpEditor(
  WidgetTester tester, {
  bool holdConfigWrites = false,
  List<MpvPreset> presets = const [],
}) async {
  resetSharedPreferencesForTest();
  final initial = <String, Object>{
    if (presets.isNotEmpty)
      SettingsService.mpvPresets.key: jsonEncode(presets.map((preset) => preset.toJson()).toList()),
  };
  final backend = _ControlledPreferences(initial)..holdConfigWrites = holdConfigWrites;
  SharedPreferencesAsyncPlatform.instance = backend;
  BaseSharedPreferencesService.resetForTesting();
  SettingsService.resetForTesting();
  await SettingsService.getInstance();

  final navigatorKey = GlobalKey<NavigatorState>();
  await tester.pumpWidget(
    TranslationProvider(
      child: MaterialApp(
        navigatorKey: navigatorKey,
        theme: monoTheme(dark: true),
        home: const Scaffold(body: Text('home')),
      ),
    ),
  );
  unawaited(navigatorKey.currentState!.push<void>(MaterialPageRoute<void>(builder: (_) => const MpvConfigScreen())));
  await tester.pumpAndSettle();
  return backend;
}

base class _ControlledPreferences extends InMemorySharedPreferencesAsync {
  _ControlledPreferences(super.data) : super.withData();

  bool holdConfigWrites = false;
  final List<String> configWrites = [];
  final List<Completer<void>> _configWriteGates = [];
  int activeConfigWrites = 0;
  int maxActiveConfigWrites = 0;
  String? durableConfig;

  @override
  Future<bool> setString(String key, String value, SharedPreferencesOptions options) async {
    if (key != SettingsService.mpvConfigText.key) return super.setString(key, value, options);

    configWrites.add(value);
    activeConfigWrites++;
    if (activeConfigWrites > maxActiveConfigWrites) maxActiveConfigWrites = activeConfigWrites;
    try {
      if (holdConfigWrites) {
        final gate = Completer<void>();
        _configWriteGates.add(gate);
        await gate.future;
      }
      final result = await super.setString(key, value, options);
      durableConfig = value;
      return result;
    } finally {
      activeConfigWrites--;
    }
  }

  void completeNextConfigWrite({Object? error}) {
    completeConfigWriteAt(0, error: error);
  }

  void completeConfigWriteAt(int index, {Object? error}) {
    final gate = _configWriteGates.removeAt(index);
    if (error == null) {
      gate.complete();
    } else {
      gate.completeError(error);
    }
  }
}
