import UserNotifications
import Intents

class NotificationService: UNNotificationServiceExtension {

  private var contentHandler: ((UNNotificationContent) -> Void)?
  private var bestAttemptContent: UNMutableNotificationContent?

  override func didReceive(
    _ request: UNNotificationRequest,
    withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
  ) {
    self.contentHandler = contentHandler
    self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

    guard var content = bestAttemptContent else {
      contentHandler(request.content)
      return
    }

    log("NSE didReceive called. Title=\(content.title), Body=\(content.body)")
    log("userInfo keys: \(content.userInfo.keys)")

    // Read config from App Group
    guard let config = ConfigReader.readConfig() else {
      log("No config found in App Group, passing through.")
      contentHandler(content)
      return
    }
    log("Config loaded. senderIdKey=\(config.senderIdKey), avatarUrlKey=\(config.avatarUrlKey ?? "nil")")

    // Call developer hook if available
    content = NotificationHook.willProcessNotification(content: content)

    let userInfo = content.userInfo

    // Extract sender ID (required)
    guard let senderId = ConfigReader.extractValue(from: userInfo, keyPath: config.senderIdKey) else {
      log("Could not extract senderId from payload using key '\(config.senderIdKey)'. userInfo: \(userInfo)")
      contentHandler(content)
      return
    }
    log("Extracted senderId=\(senderId)")

    // Extract optional fields
    let avatarUrl = config.avatarUrlKey.flatMap { ConfigReader.extractValue(from: userInfo, keyPath: $0) }
    let conversationId = config.conversationIdKey.flatMap { ConfigReader.extractValue(from: userInfo, keyPath: $0) }
    let groupName = config.groupNameKey.flatMap { ConfigReader.extractValue(from: userInfo, keyPath: $0) }
    let groupAvatarUrl = config.groupAvatarUrlKey.flatMap { ConfigReader.extractValue(from: userInfo, keyPath: $0) }

    // Determine if this is a group notification
    let isGroup = groupName != nil || groupAvatarUrl != nil

    // Resolve avatar
    let targetAvatarUrl = isGroup ? (groupAvatarUrl ?? avatarUrl) : avatarUrl
    let targetId = isGroup ? (conversationId ?? senderId) : senderId

    Task {
      let avatarImage = await resolveAvatar(url: targetAvatarUrl, id: targetId)

      // Build INPerson
      var personNameComponents = PersonNameComponents()
      // Use notification title as display name
      personNameComponents.givenName = content.title

      let personHandle = INPersonHandle(value: senderId, type: .unknown)
      let senderPerson = INPerson(
        personHandle: personHandle,
        nameComponents: personNameComponents,
        displayName: content.title,
        image: avatarImage,
        contactIdentifier: nil,
        customIdentifier: senderId
      )

      // Build INSendMessageIntent
      // DM: empty recipients to avoid auto-generated "To you & Someone" subtitle
      // Group: sender in recipients; speakableGroupName overrides as subtitle
      let intent = INSendMessageIntent(
        recipients: isGroup ? [senderPerson] : [],
        outgoingMessageType: .unknown,
        content: content.body,
        speakableGroupName: groupName.map { INSpeakableString(spokenPhrase: $0) },
        conversationIdentifier: conversationId ?? senderId,
        serviceName: nil,
        sender: senderPerson,
        attachments: nil
      )

      // If group, set the group avatar on the intent
      if isGroup, let groupImage = avatarImage {
        intent.setImage(groupImage, forParameterNamed: \.speakableGroupName)
      }

      // Donate interaction
      let interaction = INInteraction(intent: intent, response: nil)
      interaction.direction = .incoming
      try? await interaction.donate()

      // Update notification content with the intent
      do {
        let updatedContent = try content.updating(from: intent)
        self.log("Communication notification built successfully for senderId=\(senderId)")
        contentHandler(updatedContent)
      } catch {
        self.log("Failed to update content with intent: \(error.localizedDescription)")
        contentHandler(content)
      }
    }
  }

  override func serviceExtensionTimeWillExpire() {
    // Deliver whatever we have
    if let contentHandler = contentHandler, let content = bestAttemptContent {
      contentHandler(content)
    }
  }

  // MARK: - Avatar Resolution

  private func resolveAvatar(url: String?, id: String) async -> INImage? {
    guard let appGroupUrl = ConfigReader.appGroupContainerUrl() else { return fallbackAvatar() }
    let avatarsDir = appGroupUrl.appendingPathComponent("avatars", isDirectory: true)

    if let url = url {
      // Check cache: look for file matching this id + url hash
      let urlHash = AvatarCache.md5Hash(url)
      let cachedFile = avatarsDir.appendingPathComponent("\(id)_\(urlHash).jpg")

      if FileManager.default.fileExists(atPath: cachedFile.path),
         let data = try? Data(contentsOf: cachedFile) {
        log("Cache hit for \(id)")
        return INImage(imageData: data)
      }

      // Try downloading
      if let imageUrl = URL(string: url),
         let (data, _) = try? await URLSession.shared.data(from: imageUrl) {
        // Save to cache
        try? FileManager.default.createDirectory(at: avatarsDir, withIntermediateDirectories: true)
        // Remove old cached files for this id
        AvatarCache.removeOldFiles(for: id, in: avatarsDir)
        try? data.write(to: cachedFile, options: .atomic)

        // LRU eviction
        AvatarCache.evictIfNeeded(in: avatarsDir)

        log("Downloaded and cached avatar for \(id)")
        return INImage(imageData: data)
      }

      // Download failed — check for stale cache (any file for this id)
      let staleFile = AvatarCache.findAnyFile(for: id, in: avatarsDir)
      if let staleFile = staleFile, let data = try? Data(contentsOf: staleFile) {
        log("Using stale cache for \(id)")
        return INImage(imageData: data)
      }
    }

    // No URL or all attempts failed
    return fallbackAvatar()
  }

  private func fallbackAvatar() -> INImage? {
    // Look for bundled default avatar in the extension bundle
    if let url = Bundle.main.url(forResource: "default-avatar", withExtension: "png"),
       let data = try? Data(contentsOf: url) {
      return INImage(imageData: data)
    }
    return nil
  }

  // MARK: - Logging

  private func log(_ message: String) {
    guard ConfigReader.isLoggingEnabled() else { return }
    NSLog("[ExpoNSE] \(message)")
  }
}
