import ExpoModulesCore
import Foundation
import CommonCrypto

public class ExpoNotificationServiceExtensionModule: Module {
  public func definition() -> ModuleDefinition {
    Name("ExpoNotificationServiceExtension")

    Function("configureNotificationExtension") { (configJson: String) in
      guard let appGroupId = Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as? String else {
        throw NSError(domain: "ExpoNSE", code: 1, userInfo: [
          NSLocalizedDescriptionKey: "AppGroupId not found in Info.plist. Did you configure the plugin?"
        ])
      }
      guard let containerUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupId) else {
        throw NSError(domain: "ExpoNSE", code: 2, userInfo: [
          NSLocalizedDescriptionKey: "Cannot access App Group container: \(appGroupId)"
        ])
      }

      let configDir = containerUrl.appendingPathComponent("Library", isDirectory: true)
      try FileManager.default.createDirectory(at: configDir, withIntermediateDirectories: true)

      let configUrl = configDir.appendingPathComponent("config.json")
      try configJson.write(to: configUrl, atomically: true, encoding: .utf8)
    }

    AsyncFunction("prefetchAvatar") { (url: String, id: String) in
      guard let appGroupId = Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as? String,
            let containerUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupId) else {
        throw NSError(domain: "ExpoNSE", code: 2, userInfo: [
          NSLocalizedDescriptionKey: "Cannot access App Group container"
        ])
      }

      guard let imageUrl = URL(string: url) else {
        throw NSError(domain: "ExpoNSE", code: 3, userInfo: [
          NSLocalizedDescriptionKey: "Invalid avatar URL: \(url)"
        ])
      }

      let avatarsDir = containerUrl.appendingPathComponent("avatars", isDirectory: true)
      try FileManager.default.createDirectory(at: avatarsDir, withIntermediateDirectories: true)

      let urlHash = Self.md5Hash(url)
      let filename = "\(id)_\(urlHash).jpg"
      let fileUrl = avatarsDir.appendingPathComponent(filename)

      // Skip if already cached with same URL
      if FileManager.default.fileExists(atPath: fileUrl.path) {
        return
      }

      // Remove old cached files for this id (different URL hash)
      let existingFiles = (try? FileManager.default.contentsOfDirectory(at: avatarsDir, includingPropertiesForKeys: nil)) ?? []
      for file in existingFiles where file.lastPathComponent.hasPrefix("\(id)_") {
        try? FileManager.default.removeItem(at: file)
      }

      // Download and save
      let (data, _) = try await URLSession.shared.data(from: imageUrl)
      try data.write(to: fileUrl, options: .atomic)
    }

    AsyncFunction("clearAvatarCache") {
      guard let appGroupId = Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as? String,
            let containerUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupId) else {
        return
      }

      let avatarsDir = containerUrl.appendingPathComponent("avatars", isDirectory: true)
      if FileManager.default.fileExists(atPath: avatarsDir.path) {
        try? FileManager.default.removeItem(at: avatarsDir)
        try? FileManager.default.createDirectory(at: avatarsDir, withIntermediateDirectories: true)
      }
    }

    AsyncFunction("validateSetup") { (samplePayloadJson: String?) -> String in
      guard let appGroupId = Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as? String else {
        let result: [String: Any] = [
          "configValid": false,
          "appGroupAccessible": false,
          "cachedAvatarCount": 0
        ]
        return Self.jsonString(result)
      }

      guard let containerUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupId) else {
        let result: [String: Any] = [
          "configValid": false,
          "appGroupAccessible": false,
          "cachedAvatarCount": 0
        ]
        return Self.jsonString(result)
      }

      // Check config
      let configUrl = containerUrl.appendingPathComponent("Library/config.json")
      let configData = try? Data(contentsOf: configUrl)
      let config = configData.flatMap { try? JSONSerialization.jsonObject(with: $0) as? [String: Any] }
      let configValid = config?["senderIdKey"] is String

      // Count cached avatars
      let avatarsDir = containerUrl.appendingPathComponent("avatars", isDirectory: true)
      let avatarFiles = (try? FileManager.default.contentsOfDirectory(atPath: avatarsDir.path)) ?? []
      let cachedCount = avatarFiles.filter { $0.hasSuffix(".jpg") }.count

      var result: [String: Any] = [
        "configValid": configValid,
        "appGroupAccessible": true,
        "cachedAvatarCount": cachedCount
      ]

      // Dry-run payload extraction
      if let payloadJson = samplePayloadJson,
         let payloadData = payloadJson.data(using: .utf8),
         let payload = try? JSONSerialization.jsonObject(with: payloadData) as? [String: Any],
         let validConfig = config {
        var parseResult: [String: Any?] = [
          "senderId": Self.extractValue(from: payload, keyPath: validConfig["senderIdKey"] as? String),
          "avatarUrl": Self.extractValue(from: payload, keyPath: validConfig["avatarUrlKey"] as? String),
          "conversationId": Self.extractValue(from: payload, keyPath: validConfig["conversationIdKey"] as? String),
          "groupName": Self.extractValue(from: payload, keyPath: validConfig["groupNameKey"] as? String),
          "groupAvatarUrl": Self.extractValue(from: payload, keyPath: validConfig["groupAvatarUrlKey"] as? String)
        ]
        result["payloadParseResult"] = parseResult
      }

      return Self.jsonString(result)
    }
  }

  // MARK: - Helpers

  /// Extract a value from a nested dictionary using a dot-separated key path (e.g., "data.sender.id").
  private static func extractValue(from dict: [String: Any], keyPath: String?) -> String? {
    guard let keyPath = keyPath else { return nil }
    let keys = keyPath.split(separator: ".").map(String.init)
    var current: Any = dict
    for key in keys {
      guard let currentDict = current as? [String: Any], let next = currentDict[key] else {
        return nil
      }
      current = next
    }
    return current as? String
  }

  /// Simple MD5 hash for URL -> filename mapping.
  private static func md5Hash(_ string: String) -> String {
    let data = Data(string.utf8)
    var hash = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
    data.withUnsafeBytes { bytes in
      _ = CC_MD5(bytes.baseAddress, CC_LONG(data.count), &hash)
    }
    return hash.map { String(format: "%02x", $0) }.joined()
  }

  private static func jsonString(_ dict: [String: Any]) -> String {
    guard let data = try? JSONSerialization.data(withJSONObject: dict, options: [.sortedKeys]),
          let str = String(data: data, encoding: .utf8) else {
      return "{}"
    }
    return str
  }
}
