//
//  NotificationService.swift
//  SolarNotifyService
//
//  Created by LittleSheep on 2024/12/8.
//

import UserNotifications
import Intents

enum ParseNotificationPayloadError: Error {
    case missingMetadata(String)
    case missingAvatarUrl(String)
}

class NotificationService: UNNotificationServiceExtension {
    
    private var contentHandler: ((UNNotificationContent) -> Void)?
    private var bestAttemptContent: UNMutableNotificationContent?
    
    private func fetchAvatarImage(from url: String, completion: @escaping (INImage?) -> Void) {
        guard let imageURL = URL(string: url) else {
            completion(nil)
            return
        }
        
        // Define a cache location based on the URL hash
        let cacheFileName = imageURL.lastPathComponent
        let tempDirectory = FileManager.default.temporaryDirectory
        let cachedFileUrl = tempDirectory.appendingPathComponent(cacheFileName)
        
        // Check if the image is already cached
        if FileManager.default.fileExists(atPath: cachedFileUrl.path) {
            do {
                let data = try Data(contentsOf: cachedFileUrl)
                let cachedImage = INImage(imageData: data) // No optional binding here
                completion(cachedImage)
                return
            } catch {
                print("Failed to load cached avatar image: \(error.localizedDescription)")
                try? FileManager.default.removeItem(at: cachedFileUrl) // Clear corrupted cache
            }
        }
        
        // Download the image if not cached
        let session = URLSession(configuration: .default)
        session.downloadTask(with: imageURL) { localUrl, response, error in
            if let error = error {
                print("Failed to fetch avatar image: \(error.localizedDescription)")
                completion(nil)
                return
            }
            
            guard let localUrl = localUrl, let data = try? Data(contentsOf: localUrl) else {
                print("Failed to fetch data for avatar image.")
                completion(nil)
                return
            }
            
            do {
                // Cache the downloaded file
                try FileManager.default.moveItem(at: localUrl, to: cachedFileUrl)
            } catch {
                print("Failed to cache avatar image: \(error.localizedDescription)")
            }
            
            // Create INImage from the downloaded data
            let inImage = INImage(imageData: data) // Create directly
            completion(inImage)
        }.resume()
    }
    
    override func didReceive(
        _ request: UNNotificationRequest,
        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
    ) {
        self.contentHandler = contentHandler
        guard let bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent else {
            contentHandler(request.content)
            return
        }
        self.bestAttemptContent = bestAttemptContent
        
        do {
            try processNotification(request: request, content: bestAttemptContent)
        } catch {
            contentHandler(bestAttemptContent)
        }
    }
    
    override func serviceExtensionTimeWillExpire() {
        if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }
    
    private func processNotification(request: UNNotificationRequest, content: UNMutableNotificationContent) throws {
        switch content.categoryIdentifier {
        case "messaging.message", "messaging.callStart":
            try handleMessagingNotification(request: request, content: content)
        default:
            try handleDefaultNotification(content: content)
        }
    }
    
    private func handleMessagingNotification(request: UNNotificationRequest, content: UNMutableNotificationContent) throws {
        guard let metadata = content.userInfo["metadata"] as? [AnyHashable: Any] else {
            throw ParseNotificationPayloadError.missingMetadata("The notification has no metadata.")
        }
        
        guard let avatarIdentifier = metadata["avatar"] as? String else {
            throw ParseNotificationPayloadError.missingAvatarUrl("The notification has no avatar.")
        }
        
        let avatarUrl = getAttachmentUrl(for: avatarIdentifier)
        fetchAvatarImage(from: avatarUrl) { [weak self] inImage in
            guard let self = self else { return }
            
            let handle = INPersonHandle(value: "\(metadata["user_id"] ?? "")", type: .unknown)
            let sender = INPerson(
                personHandle: handle,
                nameComponents: nil,
                displayName: content.title,
                image: inImage,
                contactIdentifier: nil,
                customIdentifier: nil
            )
            
            if content.categoryIdentifier == "messaging.callStart" {
                let intent = self.createCallIntent(with: sender)
                self.donateInteraction(for: intent)
                let updatedContent = try? request.content.updating(from: intent)
                self.contentHandler?(updatedContent ?? content)
            } else {
                let intent = self.createMessageIntent(with: sender, metadata: metadata, body: content.body)
                self.donateInteraction(for: intent)
                let updatedContent = try? request.content.updating(from: intent)
                self.contentHandler?(updatedContent ?? content)
            }
        }
    }
    
    private func handleDefaultNotification(content: UNMutableNotificationContent) throws {
        guard let metadata = content.userInfo["metadata"] as? [AnyHashable: Any] else {
            throw ParseNotificationPayloadError.missingMetadata("The notification has no metadata.")
        }
        
        if let imageIdentifier = metadata["image"] as? String {
            attachMedia(to: content, withIdentifier: imageIdentifier)
        } else if let avatarIdentifier = metadata["avatar"] as? String {
            attachMedia(to: content, withIdentifier: avatarIdentifier)
        }
        
        contentHandler?(content)
    }
    
    private func attachMedia(to content: UNMutableNotificationContent, withIdentifier identifier: String) {
        let attachmentUrl = getAttachmentUrl(for: identifier)
        
        guard let remoteUrl = URL(string: attachmentUrl) else {
            print("Invalid URL for attachment: \(attachmentUrl)")
            return
        }
        
        // Define a cache location based on the identifier
        let tempDirectory = FileManager.default.temporaryDirectory
        let cachedFileUrl = tempDirectory.appendingPathComponent(identifier)
        
        if FileManager.default.fileExists(atPath: cachedFileUrl.path) {
            // Use cached file
            attachLocalMedia(to: content, from: cachedFileUrl, withIdentifier: identifier)
        } else {
            // Download and cache the file
            let session = URLSession(configuration: .default)
            session.downloadTask(with: remoteUrl) { [weak content] localUrl, response, error in
                guard let content = content else { return }
                
                if let error = error {
                    print("Failed to download media: \(error.localizedDescription)")
                    self.contentHandler?(content)
                    return
                }
                
                guard let localUrl = localUrl else {
                    print("No local file URL after download")
                    self.contentHandler?(content)
                    return
                }
                
                do {
                    // Move the downloaded file to the cache
                    try FileManager.default.moveItem(at: localUrl, to: cachedFileUrl)
                    self.attachLocalMedia(to: content, from: cachedFileUrl, withIdentifier: identifier)
                } catch {
                    print("Failed to cache media file: \(error.localizedDescription)")
                    self.contentHandler?(content)
                }
            }.resume()
        }
    }

    private func attachLocalMedia(to content: UNMutableNotificationContent, from localUrl: URL, withIdentifier identifier: String) {
        if let attachment = try? UNNotificationAttachment(identifier: identifier, url: localUrl) {
            content.attachments = [attachment]
        } else {
            print("Failed to create attachment from cached file: \(localUrl.path)")
        }
        self.contentHandler?(content)
    }
    
    private func createCallIntent(with sender: INPerson) -> INStartCallIntent {
        INStartCallIntent(
            callRecordFilter: nil,
            callRecordToCallBack: nil,
            audioRoute: .unknown,
            destinationType: .normal,
            contacts: [sender],
            callCapability: .unknown
        )
    }
    
    private func createMessageIntent(with sender: INPerson, metadata: [AnyHashable: Any], body: String) -> INSendMessageIntent {
        INSendMessageIntent(
            recipients: nil,
            outgoingMessageType: .outgoingMessageText,
            content: body,
            speakableGroupName: nil,
            conversationIdentifier: "\(metadata["channel_id"] ?? "")",
            serviceName: nil,
            sender: sender,
            attachments: nil
        )
    }
    
    private func donateInteraction(for intent: INIntent) {
        let interaction = INInteraction(intent: intent, response: nil)
        interaction.direction = .incoming
        interaction.donate(completion: nil)
    }
}
