import 'dart:ffi';
import 'dart:io';

import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:sqlcipher_flutter_libs/sqlcipher_flutter_libs.dart';
import 'package:sqlite3/open.dart';
import 'package:sqlite3/sqlite3.dart';

/// Routes package:sqlite3 to the **SQLCipher** build instead of plain SQLite.
///
/// - Android: the bundled SQLCipher `.so` (from sqlcipher_flutter_libs).
/// - Linux: the system `libsqlcipher.so` (dev machines / CI install it).
/// - iOS & macOS: SQLCipher is linked into the app binary — no override needed.
void useSqlCipher() {
  open
    ..overrideFor(OperatingSystem.android, openCipherOnAndroid)
    ..overrideFor(OperatingSystem.linux, _openLinuxCipher);
}

DynamicLibrary _openLinuxCipher() {
  // sqlcipher_flutter_libs statically links SQLCipher inside its plugin .so and
  // exports the sqlite3_* symbols — and it is ABI-matched to the `sqlite3` Dart
  // package. Prefer it; the system libsqlcipher is a different build (linked to
  // system libcrypto) and segfaults on open. Fall back to the system lib only
  // if the bundled plugin isn't present (e.g. some test/CI layouts).
  const candidates = [
    'libsqlcipher_flutter_libs_plugin.so',
    'libsqlcipher.so',
    'libsqlcipher.so.1',
    'libsqlcipher.so.0',
  ];
  Object? lastError;
  for (final name in candidates) {
    try {
      return DynamicLibrary.open(name);
    } on ArgumentError catch (e) {
      lastError = e;
    }
  }
  throw StateError('Could not load SQLCipher (tried $candidates): $lastError');
}

/// Opens [file] as an encrypted database using the raw 256-bit [keyHex].
///
/// Verifies SQLCipher is actually linked (`PRAGMA cipher_version`) and refuses
/// to fall back to plaintext — enforcing "no plaintext at rest, ever".
QueryExecutor openEncryptedExecutor(File file, String keyHex) {
  return LazyDatabase(() async {
    if (Platform.isAndroid) {
      await applyWorkaroundToOpenSqlCipherOnOldAndroidVersions();
    }
    return NativeDatabase.createInBackground(
      file,
      isolateSetup: useSqlCipher,
      setup: (db) => applyKeyAndVerify(db, keyHex),
    );
  });
}

/// Applies the SQLCipher key to [db] and asserts encryption is active.
///
/// `x'…'` passes the key as raw bytes, skipping the KDF (our key is already a
/// random 256-bit value from the OS keystore). Exposed for the security test.
void applyKeyAndVerify(Database db, String keyHex) {
  db.execute('PRAGMA key = "x\'$keyHex\'";');
  final cipher = db.select('PRAGMA cipher_version;');
  if (cipher.isEmpty) {
    throw StateError(
      'SQLCipher is not linked: PRAGMA cipher_version is empty. Encryption at '
      'rest is mandatory — refusing to open a plaintext database.',
    );
  }
}
