import 'dart:io';

import 'package:background_downloader/background_downloader.dart';
import 'package:drift/native.dart';
import 'package:flutter/services.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:twonly/core/bridge.dart' as bridge;
import 'package:twonly/core/bridge/wrapper/backup.dart';
import 'package:twonly/core/bridge/wrapper/key_manager.dart';
import 'package:twonly/core/frb_generated.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/callbacks/callbacks.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/json/backup.model.dart';
import 'package:twonly/src/model/json/userdata.model.dart'
    hide LastBackupUploadState;
import 'package:twonly/src/services/api/api.service.dart';
import 'package:twonly/src/services/backup.service.dart';
import 'package:twonly/src/services/user.service.dart';
import 'package:twonly/src/utils/keyvalue.dart';

import '../mocks/platform_channels.dart';

void main() {
  if (!Platform.isMacOS) {
    return;
  }

  TestWidgetsFlutterBinding.ensureInitialized();

  late Directory tempDir;
  late Map<String, dynamic> initialUserData;

  setUpAll(() async {
    const channel = MethodChannel('com.bbflight.background_downloader');
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(channel, (methodCall) async {
          if (methodCall.method == 'enqueue') {
            return true;
          }
          return null;
        });

    const pathProviderChannel = MethodChannel(
      'plugins.flutter.io/path_provider',
    );
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(pathProviderChannel, (methodCall) async {
          if (methodCall.method == 'getApplicationSupportDirectory') {
            return tempDir.path;
          }
          return null;
        });

    final dylibPath =
        '${Directory.current.path}/rust/target/debug/librust_lib_twonly.dylib';
    if (File(dylibPath).existsSync()) {
      await RustLib.init(
        externalLibrary: ExternalLibrary.open(dylibPath),
      );
    } else {
      await RustLib.init();
    }
    await initFlutterCallbacksForRust();

    tempDir = Directory.systemTemp.createTempSync('twonly_backup_test_');
    AppEnvironment.initTesting(
      customCacheDir: tempDir.path,
      customSupportDir: tempDir.path,
    );

    await bridge.initializeTwonlyFlutter(
      config: bridge.InitConfig(
        databaseDir: tempDir.path,
        dataDir: tempDir.path,
      ),
    );
  });

  setUp(() async {
    setupPlatformChannelMocks();
    await locator.reset();
    final dbFile = File('${tempDir.path}/twonly.sqlite');
    locator
      ..registerSingleton<TwonlyDB>(
        TwonlyDB(NativeDatabase(dbFile)),
      )
      ..registerSingleton<UserService>(UserService())
      ..registerSingleton<ApiService>(ApiService());

    userService.currentUser = UserData(
      userId: 1,
      username: 'test_user',
      displayName: 'Test User',
      subscriptionPlan: 'Free',
      currentSetupPage: null,
      appVersion: 100,
    );
    userService.isUserCreated = true;
    await UserService.save(userService.currentUser);
    initialUserData = (await KeyValueStore.get('user'))!;

    await RustBackupIdentity.setBackupPasswordKeys(
      password: 'strong_password',
      userId: 1,
    );
  });

  tearDown(() async {
    try {
      await twonlyDB.close();
    } catch (_) {}
  });

  tearDownAll(() async {
    if (tempDir.existsSync()) {
      try {
        tempDir.deleteSync(recursive: true);
      } catch (_) {}
    }
  });

  group('BackupService Tests', () {
    test('getData returns default backup status initially', () async {
      final data = await BackupService.getData();
      expect(data.identityState, LastBackupUploadState.none);
      expect(data.archiveState, LastBackupUploadState.none);
      expect(data.identityLastSuccessFull, isNull);
      expect(data.archiveLastSuccessFull, isNull);
    });

    test(
      'onBackupUpdated stream emits events when backup status changes',
      () async {
        var eventEmitted = false;
        final subscription = BackupService.onBackupUpdated.listen((_) {
          eventEmitted = true;
        });

        final dummyTask = UploadTask(url: 'http://localhost', filename: 'test');
        await BackupService.handleBackupStatusUpdate(
          'backup_identity',
          TaskStatusUpdate(dummyTask, TaskStatus.complete),
        );

        await Future.delayed(Duration.zero);
        expect(eventEmitted, isTrue);
        await subscription.cancel();
      },
    );

    test(
      'handleBackupStatusUpdate updates identity and archive status correctly',
      () async {
        // Test success update for identity status
        final dummyTask1 = UploadTask(
          url: 'http://localhost',
          filename: 'test',
        );
        await BackupService.handleBackupStatusUpdate(
          'backup_identity',
          TaskStatusUpdate(dummyTask1, TaskStatus.complete),
        );

        var data = await BackupService.getData();
        expect(data.identityState, LastBackupUploadState.success);
        expect(data.identityLastSuccessFull, isNotNull);

        // Test failure update for archive status
        final dummyTask2 = UploadTask(
          url: 'http://localhost',
          filename: 'test',
        );
        await BackupService.handleBackupStatusUpdate(
          'backup_archive',
          TaskStatusUpdate(dummyTask2, TaskStatus.failed),
        );

        data = await BackupService.getData();
        expect(data.archiveState, LastBackupUploadState.failed);
        expect(data.archiveLastSuccessFull, isNotNull);
      },
    );

    test(
      'startFullBackupRecovery returns usernameNotValid for offline/unknown user',
      () async {
        final error = await BackupService.startFullBackupRecovery(
          'unknown_user',
          'password',
        );
        expect(error, RecoveryError.usernameNotValid);
      },
    );

    test(
      'Full backup recovery flow restores identity and user.json archive successfully',
      () async {
        final initialBackupIdStr = await RustBackupIdentity.getBackupId();

        // 1. Create backups of baseline state purely natively to avoid background backup races
        final identityBytes = await RustBackupIdentity.getIdentityBackupBytes();
        final (_, archivePath) = await RustBackupArchive.createBackupArchive();

        // 2. Tamper with user.json data and verify alteration
        await KeyValueStore.put(
          'user',
          {'changed': true, 'username': 'tampered'},
        );

        final changedUserData = await KeyValueStore.get('user');
        expect(changedUserData?['changed'], isTrue);

        // 3. Trigger a change of the key_manager before restoring
        await RustBackupIdentity.importBackupPasswordKeys(
          backupId: List.filled(32, 1),
          encryptionKey: List.filled(32, 1),
        );

        final changedBackupIdStr = await RustBackupIdentity.getBackupId();
        expect(changedBackupIdStr, isNot(equals(initialBackupIdStr)));

        // 4. Restore identity and archive
        final backupKeys = await RustBackupIdentity.getBackupPasswordKeys(
          userId: 1,
          password: 'strong_password',
        );
        await RustBackupIdentity.restoreIdentityBackup(
          keys: backupKeys,
          encryptedBytes: identityBytes,
        );

        await RustBackupArchive.restoreBackupArchive(filePath: archivePath);

        final restoredBackupIdStr = await RustBackupIdentity.getBackupId();
        expect(restoredBackupIdStr, equals(initialBackupIdStr));

        // 5. Verify user.json data is fully restored
        final restoredUserData = await KeyValueStore.get('user');
        expect(
          restoredUserData?['username'],
          equals(initialUserData['username']),
        );
      },
    );

    test(
      'startPasswordlessBackupRecovery restores identity and returns tryAgainLater on download',
      () async {
        final keyManagerBytes = await RustKeyManager.serialize();

        final error = await BackupService.startPasswordlessBackupRecovery(
          1,
          'test_user',
          keyManagerBytes,
        );
        expect(error, RecoveryError.tryAgainLater);

        // Verify key manager was imported successfully
        final recoveredUserId = await RustKeyManager.getUserId();
        expect(recoveredUserId, 1);

        // Restore mock DB to prevent tearDown crash (deleteLocalUserData overrides it)
        locator.unregister<TwonlyDB>();
        final dbFile = File('${tempDir.path}/twonly.sqlite');
        locator.registerSingleton<TwonlyDB>(TwonlyDB(NativeDatabase(dbFile)));
      },
    );
  });
}
