//
//  Persistence.swift
//  Shared
//
//  Created by Trey Tartt on 1/5/22.
//

import CoreData
import SwiftUI

class PersistenceController {
    @AppStorage(UserDefaultsStore.Keys.useCloudKit.rawValue, store: GroupUserDefaults.groupDefaults) private var useCloudKit = false
    
    static let shared = PersistenceController.persistenceController
        
    private static var persistenceController: PersistenceController {
        return PersistenceController(inMemory: false)
    }
    
    public var viewContext: NSManagedObjectContext {
        return PersistenceController.shared.container.viewContext
    }
    
    public lazy var childContext: NSManagedObjectContext = {
        NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
    }()
    
    public var switchContainerListeners = [(() -> Void)]()
    
    private var editedDataClosure = [() -> Void]()
    
    public var earliestEntry: MoodEntry? {
        let fetchRequest = NSFetchRequest<MoodEntry>(entityName: "MoodEntry")
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "forDate", ascending: true)]
        let first = try! viewContext.fetch(fetchRequest).first
        return first ?? nil
    }
    
    public var latestEntry: MoodEntry? {
        let fetchRequest = NSFetchRequest<MoodEntry>(entityName: "MoodEntry")
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "forDate", ascending: true)]
        let last = try! viewContext.fetch(fetchRequest).last
        return last ?? nil
    }

    lazy var container: NSPersistentContainer = {
        setupContainer()
    }()
    
    func switchContainer() {
        try? viewContext.save()
        container = setupContainer()
        for item in switchContainerListeners {
            item()
        }
    }
    
    public func addNewDataListener(closure: @escaping (() -> Void)) {
        editedDataClosure.append(closure)
    }
    
    public func saveAndRunDataListerners() {
        do {
            try viewContext.save()
            
            for closure in editedDataClosure {
                closure()
            }
        } catch {
            print(error)
        }
    }
    
    private func setupContainer() -> NSPersistentContainer {
        if useCloudKit {
            container = NSPersistentCloudKitContainer(name: "Feels")
        } else {
            container = NSCustomPersistentContainer(name: "Feels")
        }
            
        for description in container.persistentStoreDescriptions {
            description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
            description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
            description.setOption(true as NSNumber, forKey: NSMigratePersistentStoresAutomaticallyOption)
            description.setOption(true as NSNumber, forKey: NSInferMappingModelAutomaticallyOption)
        }
        
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            self.container.viewContext.automaticallyMergesChangesFromParent = true

            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        
        return container
    }
        
    init(inMemory: Bool = false) {
        container = setupContainer()
    }
}

extension NSManagedObjectContext {
    /// Executes the given `NSBatchDeleteRequest` and directly merges the changes to bring the given managed object context up to date.
    ///
    /// - Parameter batchDeleteRequest: The `NSBatchDeleteRequest` to execute.
    /// - Throws: An error if anything went wrong executing the batch deletion.
    public func executeAndMergeChanges(using batchDeleteRequest: NSBatchDeleteRequest) throws {
        batchDeleteRequest.resultType = .resultTypeObjectIDs
        let result = try execute(batchDeleteRequest) as? NSBatchDeleteResult
        let changes: [AnyHashable: Any] = [NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? []]
        NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [self])
    }
}

class NSCustomPersistentContainer: NSPersistentContainer {
    override open class func defaultDirectoryURL() -> URL {
#if DEBUG
        var storeURLDebug = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.groupShareIdDebug)
        storeURLDebug = storeURLDebug?.appendingPathComponent("Feels-Debug.sqlite")
        return storeURLDebug!
#endif
        var storeURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.groupShareId)
        storeURL = storeURL?.appendingPathComponent("Feels.sqlite")
        return storeURL!
    }
}
