//
//  Persistence+Notification.swift
//  Mastodon
//
//  Created by MainasuK on 2022-1-21.
//

import CoreData
import CoreDataStack
import Foundation
import MastodonSDK
import class CoreDataStack.Notification

extension Persistence.Notification {
    
    public struct PersistContext {
        public let domain: String
        public let entity: Mastodon.Entity.Notification
        public let me: MastodonUser
        public let networkDate: Date

        public init(
            domain: String,
            entity: Mastodon.Entity.Notification,
            me: MastodonUser,
            networkDate: Date
        ) {
            self.domain = domain
            self.entity = entity
            self.me = me
            self.networkDate = networkDate
        }
    }
    
    public struct PersistResult {
        public let notification: Notification
        public let isNewInsertion: Bool
        
        public init(
            notification: Notification,
            isNewInsertion: Bool
        ) {
            self.notification = notification
            self.isNewInsertion = isNewInsertion
        }
    }
    
    public static func createOrMerge(
        in managedObjectContext: NSManagedObjectContext,
        context: PersistContext
    ) -> PersistResult {
        
        if let old = fetch(in: managedObjectContext, context: context) {
            merge(object: old, context: context)
            return PersistResult(
                notification: old,
                isNewInsertion: false
            )
        } else {
            let accountResult = Persistence.MastodonUser.createOrMerge(
                in: managedObjectContext,
                context: Persistence.MastodonUser.PersistContext(
                    domain: context.domain,
                    entity: context.entity.account,
                    cache: nil,
                    networkDate: context.networkDate
                )
            )
            let account = accountResult.user
            
            let status: Status? = {
                guard let entity = context.entity.status else { return nil }
                let result = Persistence.Status.createOrMerge(
                    in: managedObjectContext,
                    context: Persistence.Status.PersistContext(
                        domain: context.domain,
                        entity: entity,
                        me: context.me,
                        statusCache: nil,
                        userCache: nil,
                        networkDate: context.networkDate
                    )
                )
                return result.status
            }()
                            
            let relationship = Notification.Relationship(
                account: account,
                status: status
            )
            
            let object = create(
                in: managedObjectContext,
                context: context,
                relationship: relationship
            )

            return PersistResult(
                notification: object,
                isNewInsertion: true
            )
        }
    }
    
}

extension Persistence.Notification {
    
    public static func fetch(
        in managedObjectContext: NSManagedObjectContext,
        context: PersistContext
    ) -> Notification? {
        let request = Notification.sortedFetchRequest
        request.predicate = Notification.predicate(
            domain: context.me.domain,
            userID: context.me.id,
            id: context.entity.id
        )
        request.fetchLimit = 1
        do {
            return try managedObjectContext.fetch(request).first
        } catch {
            assertionFailure(error.localizedDescription)
            return nil
        }
    }
    
    @discardableResult
    public static func create(
        in managedObjectContext: NSManagedObjectContext,
        context: PersistContext,
        relationship: Notification.Relationship
    ) -> Notification {
        let property = Notification.Property(
            entity: context.entity,
            domain: context.me.domain,
            userID: context.me.id,
            networkDate: context.networkDate
        )
        let object = Notification.insert(
            into: managedObjectContext,
            property: property,
            relationship: relationship
        )
        update(object: object, context: context)
        return object
    }
    
    public static func merge(
        object: Notification,
        context: PersistContext
    ) {
        guard context.networkDate > object.updatedAt else { return }
        let property = Notification.Property(
            entity: context.entity,
            domain: context.me.domain,
            userID: context.me.id,
            networkDate: context.networkDate
        )
        object.update(property: property)
        
        if let status = object.status, let entity = context.entity.status {
            let property = Status.Property(
                entity: entity,
                domain: context.domain,
                networkDate: context.networkDate
            )
            status.update(property: property)
        }
        
        let accountProperty = MastodonUser.Property(
            entity: context.entity.account,
            domain: context.domain,
            networkDate: context.networkDate
        )
        object.account.update(property: accountProperty)
        
        if let author = object.status, let entity = context.entity.status {
            let property = Status.Property(
                entity: entity,
                domain: context.domain,
                networkDate: context.networkDate
            )
            author.update(property: property)
        }
        
        update(object: object, context: context)
    }
    
    private static func update(
        object: Notification,
        context: PersistContext
    ) {
        // do nothing
    }
    
}
