//
//  ReplacementMoleculeBehavior.swift
//  MVMCoreUI
//
//  Created by Kyle Hedden on 9/12/23.
//  Copyright © 2023 Verizon Wireless. All rights reserved.
//

import Foundation
import MVMCore

public class ReplaceableMoleculeBehaviorModel: PageBehaviorModelProtocol {
    public class var identifier: String { "replaceMoleculeBehavior" }
    public var shouldAllowMultipleInstances: Bool { true }
    public var moleculeIds: [String]
}

public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior {
    var moleculeIds: [String]
    var modulesToListenFor: [String]
    private var observingForResponses: NSObjectProtocol?
    private var delegateObject: MVMCoreUIDelegateObject?
    
    public required init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) {
        moleculeIds = (model as! ReplaceableMoleculeBehaviorModel).moleculeIds
        let shouldListenForListUpdates = delegateObject?.moleculeListDelegate != nil
        if shouldListenForListUpdates {
            modulesToListenFor = []
            listenForModuleUpdates()
        } else {
            modulesToListenFor = moleculeIds
            stopListeningForModuleUpdates()
        }
        self.delegateObject = delegateObject
        guard let pageType = delegateObject?.moleculeDelegate?.getTemplateModel()?.pageType else { return }
        MVMCoreViewControllerMappingObject.shared()?.addOptionalModules(toMapping: moleculeIds, forPageType: pageType)
    }
    
    public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) {
        self.delegateObject = delegateObject
        let shouldListenForListUpdates = delegateObject?.moleculeListDelegate != nil
        if shouldListenForListUpdates {
            modulesToListenFor = []
            listenForModuleUpdates()
        } else {
            modulesToListenFor = moleculeIds
            stopListeningForModuleUpdates()
        }
        
       let moleculeModels = moleculeIds.compactMap { moleculeId in
            do {
                return try delegateObject?.moleculeDelegate?.getModuleWithName(moleculeId)
            } catch {
                let coreError = MVMCoreErrorObject.createErrorObject(for: error, location: String(describing: type(of: self)))!
                if let error = error as? HumanReadableDecodingErrorProtocol {
                    coreError.messageToLog = "Error decoding replacement \"\(moleculeId)\": \(error.readableDescription)"
                }
                MVMCoreLoggingHandler.shared()?.addError(toLog: coreError)
                return nil
            }
        }
        if moleculeModels.count > 0 {
            delegateObject?.moleculeDelegate?.replaceMoleculeData(moleculeModels, completionHandler: nil)
        }
    }
    
    private func listenForModuleUpdates() {
        guard observingForResponses == nil else { return }
        let pageUpdateQueue = OperationQueue()
        pageUpdateQueue.maxConcurrentOperationCount = 1
        pageUpdateQueue.qualityOfService = .userInteractive
        observingForResponses = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil, queue: pageUpdateQueue, using: responseJSONUpdated(notification:))
    }
    
    private func stopListeningForModuleUpdates() {
        guard let observingForResponses = observingForResponses else { return }
        NotificationCenter.default.removeObserver(observingForResponses)
        self.observingForResponses = nil
    }
    
    @objc func responseJSONUpdated(notification: Notification) {
        guard let modulesLoaded = notification.userInfo?.optionalDictionaryForKey(KeyModuleMap) else { return }
        let modules: [MoleculeModelProtocol] = moleculeIds.compactMap { moleculeId in
            guard let json = modulesLoaded.optionalDictionaryForKey(moleculeId) else { return nil }
            do {
                return try convertToModel(moduleJSON: json)
            } catch {
                let coreError = MVMCoreErrorObject.createErrorObject(for: error, location: String(describing: type(of: self)))!
                if let error = error as? HumanReadableDecodingErrorProtocol {
                    coreError.messageToLog = "Error decoding replacement \"\(moleculeId)\": \(error.readableDescription)"
                }
                MVMCoreLoggingHandler.shared()?.addError(toLog: coreError)
                return nil
            }
        }
        guard modules.count > 0 else { return }
        #if LOGGING
        let requestParams = (notification.userInfo?["MVMCoreLoadObject"] as? MVMCoreLoadObject)?.requestParameters
        MVMCoreLoggingHandler.shared()?.handleDebugMessage("Replacing \(modules.map { $0.id }) from \(requestParams?.url?.absoluteString ?? "unknown"), e2eId: \(requestParams?.identifier ?? "unknown")")
        #endif
        delegateObject?.moleculeDelegate?.replaceMoleculeData(modules) { replacedModels in
            let modules = replacedModels.compactMap { modulesLoaded.dictionaryForKey($0.id) }
            guard let viewController = self.delegateObject?.moleculeDelegate as? MVMCoreViewControllerProtocol else { return }
            modules.forEach { MVMCoreUILoggingHandler.shared()?.defaultLogPageUpdate(forController: viewController, from: $0) }
        }
    }
    
    private func convertToModel(moduleJSON: [String: Any]) throws -> MoleculeModelProtocol {
        guard let moleculeName = moduleJSON.optionalStringForKey(KeyMoleculeName),
              let modelType = ModelRegistry.getType(for: moleculeName, with: MoleculeModelProtocol.self) else {
            throw ModelRegistry.Error.decoderErrorModelNotMapped(identifer: moduleJSON.optionalStringForKey(KeyMoleculeName))
        }
        return try modelType.decode(jsonDict: moduleJSON as [String : Any]) as! MoleculeModelProtocol
    }
}
