//
//  CryptoModule.swift
//  readyio-rn-wallet
//
//  Created by Nguyen Thinh on 21/05/2024.
//

import Foundation
import CryptoSwift
import SwCrypt
import Curve25519
import WalletCore

@objc(ReadyWalletCrypto)
class ReadyWalletCrypto: NSObject {
  @objc static func requiresMainQueueSetup() -> Bool { return true }
  
  func random(bytes: Int) -> Data? {
         let random = [UInt8](repeating: 0, count: bytes)
         let result = SecRandomCopyBytes(nil, bytes, UnsafeMutableRawPointer(mutating: random))
         
         guard result == errSecSuccess else {
             return nil
         }
         return Data(random)
     }
  
  func stringToBytes(_ string: String) -> [UInt8]? {
      // let length = string.characters.count
      // Updating / replacing previous (now commented) line for Swift 5 (2023-12)
      // No longer need reference characters.
      let length = string.count
      if length & 1 != 0 {
          return nil
      }
      var bytes = [UInt8]()
      bytes.reserveCapacity(length/2)
      var index = string.startIndex
      for _ in 0..<length/2 {
          let nextIndex = string.index(index, offsetBy: 2)
          if let b = UInt8(string[index..<nextIndex], radix: 16) {
              bytes.append(b)
          } else {
              return nil
          }
          index = nextIndex
      }
      return bytes
  }
  
  @objc
  func signCurve25519(_ privateKey: String, message:String,  resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock)-> Void
  {
    
    var randomData = random(bytes: Curve25519.randomLength)!
    do {
      let signature = try Curve25519.signature( for: Data(message.utf8), privateKey: Data(stringToBytes(privateKey)!),randomData: randomData )
      resolve(String("\(signature.bytes)"))
    }catch{
      print("Error",error)
    }
  }

  @objc
  func createWalletFromSeedPhrase(_ seedPhrase: String, path: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
    let param: NSMutableDictionary = NSMutableDictionary()

    // Initialize the HDWallet with the seed phrase and passphrase
    guard let wallet = HDWallet(mnemonic: seedPhrase, passphrase: "") else {
      param.setValue(false, forKey: "status")
      param.setValue("Invalid seed phrase.", forKey: "error")
      let status = try? JSONSerialization.data(withJSONObject: param, options: .prettyPrinted)
      let jsonStatus = String(data: status!, encoding: .utf8)
      resolve(jsonStatus)
      return
    }
    
    // Derive the default Ethereum address
    let derivationPath = path// Ethereum derivation path
    let privateKey = wallet.getKey(coin: .ethereum, derivationPath: derivationPath)
    let publicKey = privateKey.getPublicKeySecp256k1(compressed: true)
    let address = AnyAddress(publicKey: publicKey, coin: .ethereum)

    // Store the results in the dictionary
    param.setValue(address.description, forKey: "address")
    param.setValue(seedPhrase, forKey: "passphrase")
    param.setValue("0x" + privateKey.data.hexString, forKey: "privateKey")
    param.setValue("0x" + publicKey.data.hexString, forKey: "publicKey")
    
    // Convert the dictionary to JSON and resolve the promise
    do {
      let jsonData = try JSONSerialization.data(withJSONObject: param, options: .prettyPrinted)
      let jsonString = String(data: jsonData, encoding: .utf8)
      resolve(jsonString)
    } catch {
      param.setValue(false, forKey: "status")
      param.setValue("Failed to serialize JSON.", forKey: "error")
      let jsonStatus = try? JSONSerialization.data(withJSONObject: param, options: .prettyPrinted)
      let jsonString = String(data: jsonStatus!, encoding: .utf8)
      resolve(jsonString)
    }
  }

  @objc
  func encryptFile(_ inputFileUri:String, outputFileUri:String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void
  {

    //generate key
    var keyData = Data(count: 16)
    keyData.withUnsafeMutableBytes {
      SecRandomCopyBytes(kSecRandomDefault, 16, $0.baseAddress!)
    }
    let keyArray: [UInt8] = keyData.map{$0}
    
    let encodedKey:String = keyArray.toBase64()
    
    //generate iv
    var ivData = Data(count: 16)
    ivData.withUnsafeMutableBytes {ivBytes in
            SecRandomCopyBytes(kSecRandomDefault, 16, ivBytes)
    }
    let ivArray: [UInt8] = ivData.map{$0}
    
    let encodedIv:String = ivArray.toBase64()
    
    let nsData = NSData(contentsOfFile: inputFileUri)
    let data = nsData! as Data
    let encryptedBytes = try? CC.crypt(.encrypt, blockMode: .cbc, algorithm: .aes, padding: .pkcs7Padding, data: data, key: keyData, iv: ivData)
    
    let fileData = Data(bytes: encryptedBytes!)
    let encAdr = outputFileUri.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) ?? ""
    try? fileData.write(to: URL(string: "file://" + encAdr)!)
    
    let param:NSMutableDictionary = NSMutableDictionary()
    param.setValue(encodedKey, forKey: "key")
    param.setValue(encodedIv, forKey: "iv")
    
    let cryptoInfo =  try? JSONSerialization.data(withJSONObject: param, options: JSONSerialization.WritingOptions.prettyPrinted)
    let convertedcryptoInfo = String(data: cryptoInfo!, encoding: String.Encoding.utf8)
    resolve(convertedcryptoInfo!)

  }
  
  @objc
  func decryptFile(_ inputFileUri: String, outputFileUri: String, key: String, iv: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void{
    let keyDecoded = [UInt8](base64: key)
    let ivDecoded = [UInt8](base64: iv)
    
    let nsData = NSData(contentsOfFile: inputFileUri)
    let data = nsData! as Data
    
    let decryptedBytes = try? CC.crypt(.decrypt, blockMode: .cbc, algorithm: .aes, padding: .pkcs7Padding, data: data, key: Data(keyDecoded), iv: Data(ivDecoded))
    
    let fileData = Data(bytes: decryptedBytes!)
    let param:NSMutableDictionary = NSMutableDictionary()
   
    do{
      let url = URL(string: "file://" + outputFileUri)
      if url != nil{
        try? fileData.write(to: url!)
      }
    }catch{
      param.setValue(false, forKey: "status")
      let status =  try? JSONSerialization.data(withJSONObject: param, options: JSONSerialization.WritingOptions.prettyPrinted)
      let json_status = String(data: status!, encoding: String.Encoding.utf8)
      resolve(json_status)
    }
    
    param.setValue(true, forKey: "status")
    let status =  try? JSONSerialization.data(withJSONObject: param, options: JSONSerialization.WritingOptions.prettyPrinted)
    let json_status = String(data: status!, encoding: String.Encoding.utf8)
    resolve(json_status)
    
  }
}
