import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';

import 'package:asn1lib/asn1lib.dart';
import 'package:crypto/crypto.dart';
import 'package:encrypt/encrypt.dart' as encrypt_pkg;
import 'package:pointycastle/api.dart';
import 'package:pointycastle/asymmetric/api.dart';
import 'package:pointycastle/key_generators/api.dart';
import 'package:pointycastle/key_generators/rsa_key_generator.dart';
import 'package:pointycastle/random/fortuna_random.dart';
import 'package:pointycastle/asymmetric/rsa.dart';
import 'package:pointycastle/asymmetric/pkcs1.dart';

/// Flutter Encryption Service
/// Implements all encryption logic in Flutter using pointycastle and encrypt packages
/// Replaces native Android/iOS encryption implementations
class EncryptionFlutterService {
  // ==================== Constants ====================
  static const int sessionKeySize = 32;
  static const int gcmIvLength = 12;

  // ==================== RSA Key Management ====================

  /// Generate RSA key pair in PEM format
  RsaKeyPairResult generateRsaKeyPair({int keySize = 1024}) {
    try {
      // Get secure random
      final secureRandom = FortunaRandom();
      secureRandom.seed(KeyParameter(_generateSecureRandomBytes(32)));
      
      // Create RSA key generator
      final keyGen = RSAKeyGenerator();
      keyGen.init(ParametersWithRandom(
        RSAKeyGeneratorParameters(BigInt.parse('65537'), keySize, 64),
        secureRandom,
      ));
      
      // Generate key pair
      final keyPair = keyGen.generateKeyPair();
      final rsaPublicKey = keyPair.publicKey as RSAPublicKey;
      final rsaPrivateKey = keyPair.privateKey as RSAPrivateKey;
      
      // Export to PEM format
      final publicKeyPem = _encodeRSAPublicKey(rsaPublicKey);
      final privateKeyPem = _encodeRSAPrivateKey(rsaPrivateKey);
      
      return RsaKeyPairResult(
        publicKey: publicKeyPem,
        privateKey: privateKeyPem,
      );
    } catch (e) {
      throw Exception('Failed to generate RSA key pair: $e');
    }
  }

  /// Encode RSA public key to PEM format using asn1lib
  String _encodeRSAPublicKey(RSAPublicKey publicKey) {
    // Build RSAPublicKeyInfo structure
    final topSeq = ASN1Sequence();
    
    // AlgorithmIdentifier: OID 1.2.840.113549.1.1.1 + NULL
    final algoSeq = ASN1Sequence();
    algoSeq.add(ASN1ObjectIdentifier([1, 2, 840, 113549, 1, 1, 1])); // RSA
    algoSeq.add(ASN1Null());
    topSeq.add(algoSeq);
    
    // RSAPublicKey: modulus + publicExponent
    final keySeq = ASN1Sequence();
    keySeq.add(ASN1Integer(publicKey.n!));
    keySeq.add(ASN1Integer(publicKey.exponent!));
    
    // BitString wrapping the key (with 0 unused bits prefix)
    final keyBytes = keySeq.encodedBytes;
    final keyList = List<int>.from(keyBytes);
    keyList.insert(0, 0); // Add unused bits byte
    topSeq.add(ASN1BitString(keyList));

    final derBytes = topSeq.encodedBytes;
    final base64 = base64Encode(derBytes.toList());
    return '-----BEGIN PUBLIC KEY-----\n$base64\n-----END PUBLIC KEY-----';
  }

  /// Encode RSA private key to PEM format using asn1lib
  String _encodeRSAPrivateKey(RSAPrivateKey privateKey) {
    // Build RSAPrivateKey structure (PKCS#8 format)
    final topSeq = ASN1Sequence();
    
    // Version (0)
    topSeq.add(ASN1Integer(BigInt.zero));
    
    // Modulus
    topSeq.add(ASN1Integer(privateKey.n!));
    
    // Public Exponent
    topSeq.add(ASN1Integer(privateKey.exponent!));
    
    // Private Exponent
    topSeq.add(ASN1Integer(privateKey.privateExponent!));
    
    // Prime P
    topSeq.add(ASN1Integer(privateKey.p!));
    
    // Prime Q
    topSeq.add(ASN1Integer(privateKey.q!));
    
    // (Optional CRT params omitted for simplicity)
    
    final derBytes = topSeq.encodedBytes;
    final base64 = base64Encode(derBytes.toList());
    return '-----BEGIN PRIVATE KEY-----\n$base64\n-----END PRIVATE KEY-----';
  }

  // ==================== Private Key Encryption/Decryption ====================

  /// Encrypt private key with password (AES-CBC with MD5-derived key)
  String encryptPrivateKey({
    required String privateKey,
    required String password,
  }) {
    try {
      // Generate AES key from MD5(password)
      final aesKey = _md5Hash(password);
      
      // Generate random IV (16 bytes)
      final iv = _generateSecureRandomBytes(16);
      
      // AES encrypt using encrypt package
      final secretKey = encrypt_pkg.Key(aesKey);
      final encryptor = encrypt_pkg.Encrypter(
        encrypt_pkg.AES(secretKey, mode: encrypt_pkg.AESMode.cbc),
      );
      
      final encrypted = encryptor.encrypt(privateKey, iv: encrypt_pkg.IV(iv));
      final encryptedBytes = encrypted.bytes;
      
      // Combine IV + encrypted data
      final combined = Uint8List(iv.length + encryptedBytes.length);
      combined.setAll(0, iv);
      combined.setAll(iv.length, encryptedBytes);
      
      return base64Encode(combined);
    } catch (e) {
      throw Exception('Failed to encrypt private key: $e');
    }
  }

  /// Decrypt private key with password (AES-CBC with MD5-derived key)
  String decryptPrivateKey({
    required String encryptedPrivateKey,
    required String password,
  }) {
    try {
      // Generate AES key from MD5(password)
      final aesKey = _md5Hash(password);
      
      // Decode Base64
      final combined = base64Decode(encryptedPrivateKey);
      
      // Extract IV and encrypted data
      final iv = combined.sublist(0, 16);
      final encBytes = combined.sublist(16);
      
      // AES decrypt
      final secretKey = encrypt_pkg.Key(aesKey);
      final encryptor = encrypt_pkg.Encrypter(
        encrypt_pkg.AES(secretKey, mode: encrypt_pkg.AESMode.cbc),
      );
      
      final decrypted = encryptor.decrypt(
        encrypt_pkg.Encrypted(encBytes),
        iv: encrypt_pkg.IV(iv),
      );
      
      return decrypted;
    } catch (e) {
      throw Exception('Failed to decrypt private key: $e');
    }
  }

  // ==================== Session Key Management ====================

  /// Generate session key (32 bytes random)
  SessionKeyResult generateSessionKey({int initialRound = 1}) {
    final keyBytes = _generateSecureRandomBytes(sessionKeySize);
    final key = base64Encode(keyBytes);
    
    return SessionKeyResult(
      key: key,
      round: initialRound,
    );
  }

  /// Encrypt session key with RSA public key
  String encryptSessionKey({
    required String sessionKey,
    required String publicKey,
  }) {
    try {
      // Parse RSA public key
      final rsaPublicKey = _parsePublicKey(publicKey);
      
      // RSA encrypt using PKCS1 padding (like native implementations)
      final cipher = PKCS1Encoding(RSAEngine());
      cipher.init(true, PublicKeyParameter<RSAPublicKey>(rsaPublicKey));
      
      final encryptedBytes = cipher.process(utf8.encode(sessionKey));
      return base64Encode(encryptedBytes);
    } catch (e) {
      throw Exception('Failed to encrypt session key: $e');
    }
  }

  /// Decrypt session key with RSA private key
  String decryptSessionKey({
    required String encryptedSessionKey,
    required String privateKey,
  }) {
    try {
      // Parse RSA private key
      final rsaPrivateKey = _parsePrivateKey(privateKey);
      
      // RSA decrypt using PKCS1 padding (like native implementations)
      final cipher = PKCS1Encoding(RSAEngine());
      cipher.init(false, PrivateKeyParameter<RSAPrivateKey>(rsaPrivateKey));
      
      final decryptedBytes = cipher.process(base64Decode(encryptedSessionKey));
      return utf8.decode(decryptedBytes);
    } catch (e) {
      throw Exception('Failed to decrypt session key: $e');
    }
  }

  // ==================== Message Encryption/Decryption ====================

  /// Encrypt message (AES-CTR with round-based key derivation)
  EncryptedMessageResult encryptMessage({
    required String plaintext,
    required String sessionKey,
    required int round,
  }) {
    try {
      // Derive key for round
      final actualKey = _deriveKeyForRound(sessionKey, round);
      
      // Generate random IV (16 bytes for CTR)
      final iv = _generateSecureRandomBytes(16);
      
      // AES-CTR encrypt
      final secretKey = encrypt_pkg.Key(actualKey);
      final encryptor = encrypt_pkg.Encrypter(
        encrypt_pkg.AES(secretKey, mode: encrypt_pkg.AESMode.ctr),
      );
      
      final encrypted = encryptor.encrypt(plaintext, iv: encrypt_pkg.IV(iv));
      final encryptedBytes = encrypted.bytes;
      
      // Combine IV + encrypted data
      final combined = Uint8List(iv.length + encryptedBytes.length);
      combined.setAll(0, iv);
      combined.setAll(iv.length, encryptedBytes);
      
      final data = base64Encode(combined);
      
      return EncryptedMessageResult(
        round: round,
        data: data,
      );
    } catch (e) {
      throw Exception('Failed to encrypt message: $e');
    }
  }

  /// Decrypt message (AES-CTR with round-based key derivation)
  String decryptMessage({
    required String encryptedData,
    required String sessionKey,
    required int round,
  }) {
    try {
      // Derive key for round
      final actualKey = _deriveKeyForRound(sessionKey, round);
      
      // Decode Base64
      final combined = base64Decode(encryptedData);
      
      // Extract IV and encrypted data
      final iv = combined.sublist(0, 16);
      final encBytes = combined.sublist(16);
      
      // AES-CTR decrypt
      final secretKey = encrypt_pkg.Key(actualKey);
      final encryptor = encrypt_pkg.Encrypter(
        encrypt_pkg.AES(secretKey, mode: encrypt_pkg.AESMode.ctr),
      );
      
      final decrypted = encryptor.decrypt(
        encrypt_pkg.Encrypted(encBytes),
        iv: encrypt_pkg.IV(iv),
      );
      
      return decrypted;
    } catch (e) {
      throw Exception('Failed to decrypt message: $e');
    }
  }

  // ==================== Push Notification Decryption ====================

  /// Set AES secret for push notification decryption
  void setAesSecret(String aesSecret) {
    _aesSecret = aesSecret;
  }

  String? _aesSecret;

  /// Decrypt push notification (AES-GCM)
  String decryptPushNotification({
    required String encryptedData,
  }) {
    try {
      final secret = _aesSecret;
      if (secret == null) {
        throw Exception('AES_SECRET not set');
      }
      
      // Convert hex string to bytes
      final secretBytes = _hexStringToBytes(secret);
      
      // Decode Base64
      final combined = base64Decode(encryptedData);
      
      // Extract IV and encrypted data
      final iv = combined.sublist(0, gcmIvLength);
      final encBytes = combined.sublist(gcmIvLength);
      
      // AES-GCM decrypt
      final secretKey = encrypt_pkg.Key(secretBytes);
      final encryptor = encrypt_pkg.Encrypter(
        encrypt_pkg.AES(secretKey, mode: encrypt_pkg.AESMode.gcm),
      );
      
      final decrypted = encryptor.decrypt(
        encrypt_pkg.Encrypted(encBytes),
        iv: encrypt_pkg.IV(iv),
      );
      
      return decrypted;
    } catch (e) {
      throw Exception('Failed to decrypt push notification: $e');
    }
  }

  // ==================== Helper Methods ====================

  /// Generate secure random bytes
  Uint8List _generateSecureRandomBytes(int length) {
    final random = Random.secure();
    final bytes = Uint8List(length);
    for (var i = 0; i < length; i++) {
      bytes[i] = random.nextInt(256);
    }
    return bytes;
  }

  /// MD5 hash
  Uint8List _md5Hash(String input) {
    final bytes = utf8.encode(input);
    final hash = md5.convert(bytes).bytes as Uint8List;
    return hash;
  }

  /// Derive key for round (MD5 hash of session key)
  Uint8List _deriveKeyForRound(String sessionKey, int targetRound) {
    // Base64 decode session key
    final keyBytes = base64Decode(sessionKey);
    
    // Apply MD5 for the round (simplified version)
    final hash = md5.convert(keyBytes).bytes as Uint8List;
    
    return hash;
  }

  /// Parse RSA public key from PEM
  RSAPublicKey _parsePublicKey(String pem) {
    final base64 = pem
        .replaceAll('-----BEGIN PUBLIC KEY-----', '')
        .replaceAll('-----END PUBLIC KEY-----', '')
        .replaceAll('\n', '')
        .trim();
    final bytes = base64Decode(base64);
    
    // Parse ASN.1 DER format
    final asn1Parser = ASN1Parser(Uint8List.fromList(bytes));
    final topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
    
    final subjectPublicKeyInfo = topLevelSeq.elements[1] as ASN1BitString;
    final keyBytes = subjectPublicKeyInfo.contentBytes();
    final keyParser = ASN1Parser(Uint8List.fromList(keyBytes));
    final keySeq = keyParser.nextObject() as ASN1Sequence;
    
    final modulus = keySeq.elements[0] as ASN1Integer;
    final publicExponent = keySeq.elements[1] as ASN1Integer;
    
    return RSAPublicKey(
      modulus.valueAsBigInteger,
      publicExponent.valueAsBigInteger,
    );
  }

  /// Parse RSA private key from PEM
  RSAPrivateKey _parsePrivateKey(String pem) {
    final base64 = pem
        .replaceAll('-----BEGIN PRIVATE KEY-----', '')
        .replaceAll('-----END PRIVATE KEY-----', '')
        .replaceAll('\n', '')
        .trim();
    final bytes = base64Decode(base64);
    
    // Parse ASN.1 DER format
    final asn1Parser = ASN1Parser(Uint8List.fromList(bytes));
    final keySeq = asn1Parser.nextObject() as ASN1Sequence;
    
    final modulus = keySeq.elements[1] as ASN1Integer;
    final privateExponent = keySeq.elements[3] as ASN1Integer;
    final p = keySeq.elements[4] as ASN1Integer;
    final q = keySeq.elements[5] as ASN1Integer;

    return RSAPrivateKey(
      modulus.valueAsBigInteger,
      privateExponent.valueAsBigInteger,
      p.valueAsBigInteger,
      q.valueAsBigInteger,
    );
  }

  /// Convert hex string to bytes
  Uint8List _hexStringToBytes(String hex) {
    final len = hex.length;
    final data = Uint8List(len ~/ 2);
    for (var i = 0; i < len; i += 2) {
      data[i ~/ 2] = (int.parse(hex[i], radix: 16) << 4) + int.parse(hex[i + 1], radix: 16);
    }
    return data;
  }
}

// ==================== Result Classes ====================

class RsaKeyPairResult {
  final String publicKey;
  final String privateKey;

  RsaKeyPairResult({required this.publicKey, required this.privateKey});
}

class SessionKeyResult {
  final String key;
  final int round;

  SessionKeyResult({required this.key, required this.round});
}

class EncryptedMessageResult {
  final int round;
  final String data;

  EncryptedMessageResult({required this.round, required this.data});
}

