//
//  Persistence.swift
//  Padel Tournament
//
//  Created by Razmig Sarkissian on 24/02/2023.
//

import CoreData

class PersistenceController: NSObject {
    static let shared = PersistenceController()
    
    private static var _model: NSManagedObjectModel?
    private static func model(name: String) throws -> NSManagedObjectModel {
        if _model == nil {
            _model = try loadModel(name: name, bundle: Bundle.main)
        }
        return _model!
    }
    private static func loadModel(name: String, bundle: Bundle) throws -> NSManagedObjectModel {
        guard let modelURL = bundle.url(forResource: name, withExtension: "momd") else {
            throw CoreDataError.modelURLNotFound(forResourceName: name)
        }
        
        guard let model = NSManagedObjectModel(contentsOf: modelURL) else {
            throw CoreDataError.modelLoadingFailed(forURL: modelURL)
        }
        return model
    }
    
    enum CoreDataError: Error {
        case modelURLNotFound(forResourceName: String)
        case modelLoadingFailed(forURL: URL)
    }
    
    lazy var localContainer : NSPersistentContainer = {
        let baseURL = NSPersistentContainer.defaultDirectoryURL()
        let storeFolderURL = baseURL.appendingPathComponent("CoreDataStores")
        let localStoreFolderURL = storeFolderURL.appendingPathComponent("Local")
        
        let fileManager = FileManager.default
        for folderURL in [localStoreFolderURL] where !fileManager.fileExists(atPath: folderURL.path) {
            do {
                try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil)
            } catch {
                fatalError("#\(#function): Failed to create the store folder: \(error)")
            }
        }
        
        let container = NSPersistentContainer(name: "PadelClubApp", managedObjectModel: try! Self.model(name: "PadelClubApp"))
        
        guard let localStoreDescription = container.persistentStoreDescriptions.first!.copy() as? NSPersistentStoreDescription else {
            fatalError("#\(#function): Copying the private store description returned an unexpected value.")
        }
        localStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
        localStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
        localStoreDescription.setValue("DELETE" as NSObject, forPragmaNamed: "journal_mode")

        localStoreDescription.url = localStoreFolderURL.appendingPathComponent("local.sqlite")
        
        var storeDescriptions = [localStoreDescription]
        
        /**
         Load the persistent stores.
         */
        container.persistentStoreDescriptions = storeDescriptions
        
        container.loadPersistentStores(completionHandler: { (loadedStoreDescription, error) in
            guard error == nil else {
                fatalError("#\(#function): Failed to load persistent stores:\(error!)")
            }
//            if UserDefaults.standard.string(forKey: "lastDataSource") == nil {
//                UserDefaults.standard.setValue("09-2023", forKey: "lastDataSource")
//            }
        })
        container.viewContext.automaticallyMergesChangesFromParent = true
        container.viewContext.name = "viewContext"
        container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        container.viewContext.undoManager = nil
        container.viewContext.shouldDeleteInaccessibleFaults = true
        container.viewContext.transactionAuthor = PersistenceController.authorName

        return container
    }()
    
    
    /// Creates and configures a private queue context.
    private func newTaskContext() -> NSManagedObjectContext {
        // Create a private queue context.
        /// - Tag: newBackgroundContext
        let taskContext = localContainer.newBackgroundContext()
        taskContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        // Set unused undoManager to nil for macOS (it is nil by default on iOS)
        // to reduce resource requirements.
        taskContext.undoManager = nil
        return taskContext
    }
    
    func batchInsertPlayers(_ importedPlayers: [FederalPlayer], importingDate: Date) async {
        guard !importedPlayers.isEmpty else { return }
        let context = newTaskContext()
        
        context.performAndWait {
            context.transactionAuthor = PersistenceController.remoteDataImportAuthorName
            
            let batchInsert = self.newBatchInsertRequest(with: importedPlayers, importingDate: importingDate)
            do {
                let result = try context.execute(batchInsert) as? NSBatchInsertResult
                if let objectIDs = result?.result as? [NSManagedObjectID], !objectIDs.isEmpty {
                    let save = [NSInsertedObjectsKey: objectIDs]
                    NSManagedObjectContext.mergeChanges(fromRemoteContextSave: save, into: [localContainer.viewContext])
                }
                
            } catch {
                print(error.localizedDescription)
            }
        }
    }
    
    private func newBatchInsertRequest(with imported: [FederalPlayer], importingDate: Date)
    -> NSBatchInsertRequest {
        // 1
        var index = 0
        let total = imported.count
        let replacements: [(Character, Character)] = [("Á", "ç"), ("‡", "à"), ("Ù", "ô"), ("Ë", "è"), ("Ó", "î"), ("Î", "ë"), ("…", "É"), ("Ô", "ï"), ("È", "é"), ("«", "Ç"), ("»", "È"), ("ù", "ô")]


        let replacementsCharacters = replacements
        var fixApril2024 = false
        //april 04-2024 bug with accent characters / adobe / fft
        if URL.importDateFormatter.string(from: importingDate) == "04-2024" {
            fixApril2024 = true
        }

        // 2
        let batchInsert = NSBatchInsertRequest(
            entity: ImportedPlayer.entity()) { (managedObject: NSManagedObject) -> Bool in
                // 3
                guard index < total else { return true }
                
                if let importedPlayer = managedObject as? ImportedPlayer {
                    // 4
                    let data = imported[index]
                    importedPlayer.license = data.license
                    importedPlayer.ligueName = data.ligue
                    if fixApril2024 {
                        importedPlayer.ligueName?.replace(characters: replacementsCharacters)
                    }

                    importedPlayer.rank = Int64(data.rank)
                    importedPlayer.points = data.points ?? 0
                    importedPlayer.assimilation = data.assimilation
                    importedPlayer.country = data.country
                    if fixApril2024 {
                        importedPlayer.country?.replace(characters: replacementsCharacters)
                    }
                    importedPlayer.tournamentCount = Int64(data.tournamentCount ?? 0)
                    importedPlayer.lastName = data.lastName.trimmed.uppercased()
                    if fixApril2024 {
                        importedPlayer.lastName?.replace(characters: replacementsCharacters)
                    }
                    importedPlayer.firstName = data.firstName.trimmed.capitalized
                    if fixApril2024 {
                        importedPlayer.firstName?.replace(characters: replacementsCharacters)
                    }
                    
                    importedPlayer.fullName = data.firstName.trimmed.capitalized + " " + data.lastName.trimmed.uppercased()
                    if fixApril2024 {
                        importedPlayer.fullName?.replace(characters: replacementsCharacters)
                    }
                    importedPlayer.clubName = data.club
                    if fixApril2024 {
                        importedPlayer.clubName?.replace(characters: replacementsCharacters)
                    }
                    importedPlayer.clubCode = data.clubCode.replaceCharactersFromSet(characterSet: .whitespaces)
                    importedPlayer.male = data.isMale
                    importedPlayer.importDate = importingDate
                }
                
                // 5
                index += 1
                return false
            }
        return batchInsert
    }
    
    // MARK: - History Management
    private static let authorName = "Padel Tournament"
    private static let remoteDataImportAuthorName = "Data Import"
    
    private func mergeChanges(from transactions: [NSPersistentHistoryTransaction]) {
        let context = localContainer.viewContext
        context.perform {
            transactions.forEach { transaction in
                guard let userInfo = transaction.objectIDNotification().userInfo else {
                    return
                }
                
                NSManagedObjectContext.mergeChanges(fromRemoteContextSave: userInfo, into: [context])
            }
        }
    }
}
