import Foundation
import Darwin
import MachO
import React
import Security

/** Self-contained iOS RASP probe. Add JSOExpectedTeamIdentifier to Info.plist to pin signing identity. */
@objc(JsoRasp)
class JsoRasp: NSObject {
  @objc static func requiresMainQueueSetup() -> Bool { false }

  @objc(probe:rejecter:)
  func probe(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    let jailbreak = isJailbroken()
    let images = loadedImages()
    var tools: [String] = []
    if images.contains(where: { $0.contains("frida") }) { tools.append("Frida") }
    if images.contains(where: { $0.contains("substrate") || $0.contains("substitute") }) { tools.append("Substrate") }
    if FileManager.default.fileExists(atPath: "/.installed_unc0ver") { tools.append("unc0ver") }
    if FileManager.default.fileExists(atPath: "/var/jb") { tools.append("Dopamine") }
    if FileManager.default.fileExists(atPath: "/.bootstrapped_electra") { tools.append("checkra1n") }
    resolve([
      "platform": "ios", "isJailbroken": jailbreak, "isDebuggable": isDebuggerAttached(),
      "isEmulator": isSimulator(), "hooksDetected": !tools.filter { $0 == "Frida" || $0 == "Substrate" }.isEmpty,
      "signatureTampered": !signatureMatchesConfiguredTeam(), "unofficialStore": hasEmbeddedProvisioningProfile(),
      "detectedTools": Array(Set(tools)).sorted()
    ])
  }

  private func isJailbroken() -> Bool {
    #if targetEnvironment(simulator)
    return false
    #else
    let paths = ["/Applications/Cydia.app", "/Applications/Sileo.app", "/Library/MobileSubstrate/MobileSubstrate.dylib", "/bin/bash", "/usr/sbin/sshd", "/etc/apt", "/var/jb"]
    if paths.contains(where: FileManager.default.fileExists) { return true }
    let probe = "/private/jso-\(UUID().uuidString)"
    do { try "probe".write(toFile: probe, atomically: true, encoding: .utf8); try? FileManager.default.removeItem(atPath: probe); return true } catch { return false }
    #endif
  }

  private func loadedImages() -> [String] {
    (0..<_dyld_image_count()).compactMap { index in
      guard let name = _dyld_get_image_name(index) else { return nil }
      return String(cString: name).lowercased()
    }
  }

  private func isDebuggerAttached() -> Bool {
    var info = kinfo_proc()
    var size = MemoryLayout<kinfo_proc>.stride
    var mib = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()]
    return sysctl(&mib, 4, &info, &size, nil, 0) == 0 && (info.kp_proc.p_flag & P_TRACED) != 0
  }

  private func isSimulator() -> Bool {
    #if targetEnvironment(simulator)
    return true
    #else
    return false
    #endif
  }

  private func signingTeamIdentifier() -> String? {
    guard let task = SecTaskCreateFromSelf(kCFAllocatorDefault) else { return nil }
    return SecTaskCopyValueForEntitlement(task, "com.apple.developer.team-identifier" as CFString, nil) as? String
  }

  private func signatureMatchesConfiguredTeam() -> Bool {
    guard let expected = Bundle.main.object(forInfoDictionaryKey: "JSOExpectedTeamIdentifier") as? String, !expected.isEmpty else { return true }
    return signingTeamIdentifier() == expected
  }

  private func hasEmbeddedProvisioningProfile() -> Bool {
    Bundle.main.path(forResource: "embedded", ofType: "mobileprovision") != nil
  }
}
