import 'dart:math';
import 'dart:typed_data';
import 'package:encrypt/encrypt.dart' as encrypt;
import 'package:flutter/foundation.dart';
import 'package:secure_string_operations/secure_string_operations.dart';

import '../../constant/char_map.dart';
import '../../env/env.dart';
import '../../main.dart';
import '../../print.dart';

class EncryptionHelper {
  static EncryptionHelper? _instance;

  late final encrypt.Key key;
  late final encrypt.IV iv; // Legacy CBC IV (kept for backward-compat)

  /// Prefix to distinguish new GCM ciphertext from old CBC ciphertext
  static const String _gcmPrefix = 'GCM:';

  EncryptionHelper._(this.key, this.iv);

  static EncryptionHelper get instance {
    if (_instance == null) {
      throw Exception(
          "EncryptionHelper is not initialized. Call `await EncryptionHelper.initialize()` in main.");
    }
    return _instance!;
  }

  /// Initializes and stores the instance globally
  static Future<void> initialize() async {
    if (_instance != null) {
      debugPrint("EncryptionHelper is already initialized.");
      return;
    }
    debugPrint("Initializing EncryptionHelper...");
    var keyOfApp = r(Env.keyOfApp).toString().split(Env.addd)[0];
    var initializationVector =
        r(Env.initializationVector).toString().split(Env.addd)[0];

    _instance = EncryptionHelper._(
      encrypt.Key.fromUtf8(keyOfApp!),
      encrypt.IV.fromUtf8(initializationVector!),
    );
    debugPrint("EncryptionHelper initialized successfully.");
  }

  /// ✅ FIX H-04: Encrypts a string using AES-256-GCM with a random IV
  /// Format: "GCM:<base64_iv>:<base64_ciphertext>"
  String encryptData(String plainText) {
    try {
      final randomIv = _generateRandomIv(12); // 12-byte nonce recommended for GCM
      final gcmEncrypter =
          encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.gcm));
      final encrypted = gcmEncrypter.encrypt(plainText, iv: randomIv);
      // Prepend GCM prefix + IV so the server/decoder knows the format
      return '$_gcmPrefix${randomIv.base64}:${encrypted.base64}';
    } catch (e) {
      debugPrint('GCM Encryption failed, falling back to CBC: $e');
      // Fallback to CBC for environments that don't support GCM
      try {
        final cbcEncrypter =
            encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
        final encrypted = cbcEncrypter.encrypt(plainText, iv: iv);
        return encrypted.base64;
      } catch (e2) {
        debugPrint('CBC Encryption Error: $e2');
        return '';
      }
    }
  }

  /// ✅ FIX H-04: Decrypts a string — supports both GCM (new) and CBC (legacy)
  String decryptData(String encryptedText) {
    try {
      if (encryptedText.startsWith(_gcmPrefix)) {
        // New GCM format: "GCM:<iv_b64>:<cipher_b64>"
        final parts = encryptedText.substring(_gcmPrefix.length).split(':');
        if (parts.length != 2) {
          debugPrint('Invalid GCM format, falling back to CBC');
          return _decryptLegacyCbc(encryptedText);
        }
        final gcmIv = encrypt.IV.fromBase64(parts[0]);
        final gcmEncrypter =
            encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.gcm));
        return gcmEncrypter.decrypt(
            encrypt.Encrypted.fromBase64(parts[1]),
            iv: gcmIv);
      }
      // Legacy CBC format (no prefix)
      return _decryptLegacyCbc(encryptedText);
    } catch (e) {
      debugPrint('Decryption Error: $e');
      try {
        return _decryptLegacyCbc(encryptedText);
      } catch (_) {
        return '';
      }
    }
  }

  /// Legacy CBC decryption (backward compatibility with existing data)
  String _decryptLegacyCbc(String encryptedText) {
    final cbcEncrypter =
        encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
    final encrypted = encrypt.Encrypted.fromBase64(encryptedText);
    return cbcEncrypter.decrypt(encrypted, iv: iv);
  }

  /// Generates a cryptographically secure random IV
  encrypt.IV _generateRandomIv(int length) {
    final random = Random.secure();
    final bytes = List<int>.generate(length, (_) => random.nextInt(256));
    return encrypt.IV(Uint8List.fromList(bytes));
  }
}

r(String string) {
  return X.r(X.r(X.r(string, cn), cC), cs).toString();
}

c(String string) {
  return X.c(X.c(X.c(string, cn), cC), cs).toString();
}
