//
//  NotificationService.swift
//  NotificationService
//
//  Created by Tobi on 03.04.25.
//

import UserNotifications
import CryptoKit
import Foundation

class NotificationService: UNNotificationServiceExtension {
    
    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?
    
    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
        
        if let bestAttemptContent = bestAttemptContent {
                        
            guard let _ = bestAttemptContent.userInfo as? [String: Any],
                  let push_data = bestAttemptContent.userInfo["push_data"] as? String else {
                return contentHandler(bestAttemptContent);
            }
            
            let data = getPushNotificationData(pushDataJson: push_data)
            
            if data != nil {
                bestAttemptContent.title = data!.title;
                bestAttemptContent.body = data!.body;
                bestAttemptContent.threadIdentifier = String(format: "%d", data!.notificationId)
            } else {
                bestAttemptContent.title = "\(bestAttemptContent.title) [10]"
            }
            
            contentHandler(bestAttemptContent)
        }
    }
    
    override func serviceExtensionTimeWillExpire() {
        // Called just before the extension will be terminated by the system.
        // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
        if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }
    
}


enum PushKind: String, Codable {
    case text
    case twonly
    case video
    case image
    case contactRequest
    case acceptRequest
    case storedMediaFile
    case reaction
    case testNotification
    case reopenedMedia
}

import CryptoKit
import Foundation
import Security

func getPushNotificationData(pushDataJson: String) -> (title: String, body: String, notificationId: Int)? {
    // Decode the pushDataJson
    guard let pushData = decodePushData(pushDataJson) else {
        NSLog("Failed to decode push data")
        return nil
    }

    var pushKind: PushKind?
    var displayName: String?
    var fromUserId: Int?

    // Check the keyId
    if pushData.keyId == 0 {
        let key = "InsecureOnlyUsedForAddingContact".data(using: .utf8)!.map { Int($0) }
        pushKind = tryDecryptMessage(key: key, pushData: pushData)
    } else {
        let pushKeys = getPushKey()
        if pushKeys != nil {
            for (userId, userKeys) in pushKeys! {
                for key in userKeys.keys {
                    if key.id == pushData.keyId {
                        pushKind = tryDecryptMessage(key: key.key, pushData: pushData)
                        if pushKind != nil {
                            displayName = userKeys.displayName
                            fromUserId = userId
                            break
                        }
                    }
                }
                // Found correct key and user
                if displayName != nil { break }
            }
        } else {
            NSLog("pushKeys are empty")
        }
    }

    // Handle the push notification based on the pushKind
    if let pushKind = pushKind {
        
        if pushKind == .testNotification {
            return ("Test Notification", "This is a test notification.", 0)
        } else if displayName != nil && fromUserId != nil {
            return (displayName!, getPushNotificationText(pushKind: pushKind), fromUserId!)
        } else {
            return ("", getPushNotificationTextWithoutUserId(pushKind: pushKind), 1)
        }
        
    } else {
        NSLog("Failed to decrypt message or pushKind is nil")
    }
    return nil
}

func tryDecryptMessage(key: [Int], pushData: PushNotification) -> PushKind? {
    // Convert the key from [Int] to Data
    let keyData = Data(key.map { UInt8($0) }) // Convert Int to UInt8
    
    guard let nonceData = Data(base64Encoded: pushData.nonce),
              let cipherTextData = Data(base64Encoded: pushData.cipherText),
              let macData = Data(base64Encoded: pushData.mac) else {
        NSLog("Failed to decode base64 strings")
        return nil
    }

    do {
        // Create a nonce for ChaChaPoly
        let nonce = try ChaChaPoly.Nonce(data: nonceData)
        
        // Create a sealed box for ChaChaPoly
        let sealedBox = try ChaChaPoly.SealedBox(nonce: nonce, ciphertext: cipherTextData, tag: macData)
        
        // Decrypt the data using the key
        let decryptedData = try ChaChaPoly.open(sealedBox, using: SymmetricKey(data: keyData))
        
        // Convert decrypted data to a string
        if let decryptedMessage = String(data: decryptedData, encoding: .utf8) {
            NSLog("Decrypted message: \(decryptedMessage)")
            
            // Here you can determine the PushKind based on the decrypted message
            return determinePushKind(from: decryptedMessage)
        }
    } catch {
        NSLog("Decryption failed: \(error)")
    }
    
    return nil
}

// Placeholder function to determine PushKind from the decrypted message
func determinePushKind(from message: String) -> PushKind? {
    // Implement your logic to determine the PushKind based on the message content
    // For example, you might check for specific keywords or formats in the message
    // This is just a placeholder implementation
    if message.contains("text") {
        return .text
    } else if message.contains("video") {
        return .video
    } else if message.contains("image") {
        return .image
    } else if message.contains("twonly") {
        return .twonly
    } else if message.contains("contactRequest") {
        return .contactRequest
    } else if message.contains("acceptRequest") {
        return .acceptRequest
    } else if message.contains("storedMediaFile") {
        return .storedMediaFile
    } else if message.contains("reaction") {
        return .reaction
    } else if message.contains("testNotification") {
        return .testNotification
    } else if message.contains("reopenedMedia") {
        return .reopenedMedia
    } else {
        return nil // Unknown PushKind
    }
}


func decodePushData(_ json: String) -> PushNotification? {
    // First, decode the base64 string
    guard let base64Data = Data(base64Encoded: json) else {
        NSLog("Failed to decode base64 string")
        return nil
    }
    
    // Convert the base64 decoded data to a JSON string
    guard let jsonString = String(data: base64Data, encoding: .utf8) else {
        NSLog("Failed to convert base64 data to JSON string")
        return nil
    }
    
    // Convert the JSON string to Data
    guard let jsonData = jsonString.data(using: .utf8) else {
        NSLog("Failed to convert JSON string to Data")
        return nil
    }
    
    do {
        // Use JSONDecoder to decode the JSON data into a PushNotification instance
        let decoder = JSONDecoder()
        let pushNotification = try decoder.decode(PushNotification.self, from: jsonData)
        return pushNotification
    } catch {
        NSLog("Error decoding JSON: \(error)")
        return nil
    }
}

struct PushNotification: Codable {
    let keyId: Int
    let nonce: String
    let cipherText: String
    let mac: String

    // You can add custom coding keys if the JSON keys differ from the property names
    enum CodingKeys: String, CodingKey {
        case keyId
        case nonce
        case cipherText
        case mac
    }
}

struct PushKeyMeta: Codable {
    let id: Int
    let key: [Int]
    let createdAt: Date
    
    enum CodingKeys: String, CodingKey {
        case id
        case key
        case createdAt
    }
}

struct PushUser: Codable {
    let displayName: String
    let keys: [PushKeyMeta]
    
    enum CodingKeys: String, CodingKey {
        case displayName
        case keys
    }
}

func getPushKey() -> [Int: PushUser]? {
    // Retrieve the data from secure storage (Keychain)
    guard let data = readFromKeychain(key: "receivingPushKeys") else {
        print("No data found for key: receivingPushKeys")
        return nil
    }
    
    do {
        // Decode the JSON data into a dictionary
        let jsonData = data.data(using: .utf8)!
        let jsonMap = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any]
        
        var pushKeys: [Int: PushUser] = [:]
        
        // Iterate through the JSON map and decode each PushUser
        for (key, value) in jsonMap ?? [:] {
            if let userData = try? JSONSerialization.data(withJSONObject: value, options: []),
               let pushUser = try? JSONDecoder().decode(PushUser.self, from: userData) {
                pushKeys[Int(key)!] = pushUser
            }
        }
        
        return pushKeys
    } catch {
        print("Error decoding JSON: \(error)")
        return nil
    }
}

// Helper function to read from Keychain
func readFromKeychain(key: String) -> String? {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: key,
        kSecReturnData as String: kCFBooleanTrue!,
        kSecMatchLimit as String: kSecMatchLimitOne,
        kSecAttrAccessGroup as String: "CN332ZUGRP.eu.twonly.shared" // Use your access group
    ]
    
    var dataTypeRef: AnyObject? = nil
    let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
    
    if status == errSecSuccess {
        if let data = dataTypeRef as? Data {
            return String(data: data, encoding: .utf8)
        }
    }
    
    return nil
}

func getPushNotificationText(pushKind: PushKind) -> String {
    let systemLanguage = Locale.current.languageCode ?? "en" // Get the current system language

    var pushNotificationText: [PushKind: String] = [:]

    // Define the messages based on the system language
    if systemLanguage.contains("de") { // German
        pushNotificationText = [
            .text: "hat dir eine Nachricht gesendet.",
            .twonly: "hat dir ein twonly gesendet.",
            .video: "hat dir ein Video gesendet.",
            .image: "hat dir ein Bild gesendet.",
            .contactRequest: "möchte sich mit dir vernetzen.",
            .acceptRequest: "ist jetzt mit dir vernetzt.",
            .storedMediaFile: "hat dein Bild gespeichert.",
            .reaction: "hat auf dein Bild reagiert.",
            .reopenedMedia: "Dein Bild wurde erneut geöffnet."
        ]
    } else { // Default to English
        pushNotificationText = [
            .text: "has sent you a message.",
            .twonly: "has sent you a twonly.",
            .video: "has sent you a video.",
            .image: "has sent you an image.",
            .contactRequest: "wants to connect with you.",
            .acceptRequest: "is now connected with you.",
            .storedMediaFile: "has stored your image.",
            .reaction: "has reacted to your image.",
            .reopenedMedia: "Your image was reopened."
        ]
    }

    // Return the corresponding message or an empty string if not found
    return pushNotificationText[pushKind] ?? ""
}

func getPushNotificationTextWithoutUserId(pushKind: PushKind) -> String {
    let systemLanguage = Locale.current.languageCode ?? "en" // Get the current system language

    var pushNotificationText: [PushKind: String] = [:]

    // Define the messages based on the system language
    if systemLanguage.contains("de") { // German
        pushNotificationText = [
            .text: "Du hast eine Nachricht erhalten.",
            .twonly: "Du hast ein twonly erhalten.",
            .video: "Du hast ein Video erhalten.",
            .image: "Du hast ein Bild erhalten.",
            .contactRequest: "Du hast eine Kontaktanfrage erhalten.",
            .acceptRequest: "Deine Kontaktanfrage wurde angenommen.",
            .storedMediaFile: "Dein Bild wurde gespeichert.",
            .reaction: "Du hast eine Reaktion auf dein Bild erhalten.",
            .reopenedMedia: "hat dein Bild erneut geöffnet."
        ]
    } else { // Default to English
        pushNotificationText = [
            .text: "You got a message.",
            .twonly: "You got a twonly.",
            .video: "You got a video.",
            .image: "You got an image.",
            .contactRequest: "You got a contact request.",
            .acceptRequest: "Your contact request has been accepted.",
            .storedMediaFile: "Your image has been saved.",
            .reaction: "You got a reaction to your image.",
            .reopenedMedia: "has reopened your image."
        ]
    }

    // Return the corresponding message or an empty string if not found
    return pushNotificationText[pushKind] ?? ""
}
