//
//  AnagogBridgeAPI.swift
//
//  Copyright © 2022 Anagog Ltd. All rights reserved.
//

import UIKit
import JedAIAppKit
import JedAIKit
import JedAIMetricsKit
import JedAIJEMAKit
import JedAIReportKit
#if canImport(JedAIToolboxKit)
import JedAIToolboxKit
#endif
import CoreLocation

class AnagogBridgeAPI {
    
    private(set) var debugScreenController: UIViewController? = nil
    
    internal weak var sdkWrapper: AnagogSDK?
    
    // MARK: - Initialization
    
    init() {}
    
    
    // MARK: - Micromoments
    
    func fireMicromoment(_ micromoment: Dictionary<String, Any>) throws {
        guard getJedAIStatus() else {
            throw JedAIAPIError.jedaiNotStartedYet
        }
        guard let identifier = micromoment["identifier"] as? String else {
            throw JedAIAPIError.micromomentIdentfierIsMissing
        }
        var applicationEvent = ApplicationEvent(identifier)
        
        if let textParameters = micromoment["textParameters"] as? Dictionary<String, String> {
            for (name, text) in textParameters {
                applicationEvent = applicationEvent.addParameter(name: name, text: text)
            }
        }
        if let numericParameters = micromoment["numericParameters"] as? Dictionary<String, NSNumber> {
            for (name, number) in numericParameters {
                applicationEvent = applicationEvent.addParameter(name: name, number: number.floatValue)
            }
        }
        if let booleanParameters = micromoment["booleanParameters"] as? Dictionary<String, Bool> {
            for (name, bool) in booleanParameters {
                applicationEvent = applicationEvent.addParameter(name: name, boolean: bool)
            }
        }
        applicationEvent.post()
    }
    
    
    // MARK: - Microsegments
    
    func setUserDefinedString(_ customName: String, value: String, callback: @escaping (Error?) -> Void) {
        JedAIApp.safe {
            JedAIMetricsModel.Metric.UserDefinedString.set(customName: customName, value: value)
            callback(nil)
        } onFailure: {
            callback(JedAIAPIError.safeError)
        }
    }
    
    func setUserDefinedInteger(_ customName: String, value: Int, callback: @escaping (Error?) -> Void) {
        JedAIApp.safe {
            JedAIMetricsModel.Metric.UserDefinedInteger.set(customName: customName, value: value)
            callback(nil)
        } onFailure: {
            callback(JedAIAPIError.safeError)
        }
    }
    
    func setUserDefinedDecimal(_ customName: String, value: Double, callback: @escaping (Error?) -> Void) {
        JedAIApp.safe {
            JedAIMetricsModel.Metric.UserDefinedDecimal.set(customName: customName, value: value)
            callback(nil)
        } onFailure: {
            callback(JedAIAPIError.safeError)
        }
    }
    
    func getUserDefinedString(_ customName: String, callback: @escaping (Result<String, Error>) -> Void) {
        JedAIApp.safe {
            let value = JedAIMetricsModel.Metric.UserDefinedString.get(customName: customName) as String
            callback(.success(value))
        } onFailure: {
            callback(.failure(JedAIAPIError.safeError))
        }
    }
    
    func getUserDefinedInteger(_ customName: String, callback: @escaping (Result<Int, Error>) -> Void) {
        JedAIApp.safe {
            let value = JedAIMetricsModel.Metric.UserDefinedInteger.get(customName: customName) as Int
            callback(.success(value))
        } onFailure: {
            callback(.failure(JedAIAPIError.safeError))
        }
    }
    
    func getUserDefinedDecimal(_ customName: String, callback: @escaping (Result<Double, Error>) -> Void) {
        JedAIApp.safe {
            let value = JedAIMetricsModel.Metric.UserDefinedDecimal.get(customName: customName) as Double
            callback(.success(value))
        } onFailure: {
            callback(.failure(JedAIAPIError.safeError))
        }
    }
    
    
    // MARK: - Campaigns
    
    func downloadCampaigns() throws {
        guard getJedAIStatus() else {
            throw JedAIAPIError.jedaiNotStartedYet
        }
        DispatchQueue.global().async {
            JedAICommandEvent(command: "enforceJEMASync").post()
        }
    }
    
    func simulateCampaign(_ campaignIdentifier: String? = nil) throws {
        guard getJedAIStatus() else {
            throw JedAIAPIError.jedaiNotStartedYet
        }
        JedAIJEMA.shared.simulateCampaign(campaignIdentifier)
    }
    
    func getCampaigns(_ completion: @escaping (Result<[[String: String]], Error>) -> Void) {
        DispatchQueue.global(qos: .userInitiated).async {
            guard self.getJedAIStatus() else {
                completion(.failure(JedAIAPIError.jedaiNotStartedYet))
                return
            }
            let campaigns = JedAIJEMA.shared.loadActiveCampaigns().map { campaign in
                return [
                    "title": campaign.title,
                    "identifier": campaign.identifier
                ]
            }
            completion(.success(campaigns))
        }
    }
    
    
    // MARK: - Reprots
    
    func forceScheduleReports() throws {
        guard getJedAIStatus() else {
            throw JedAIAPIError.jedaiNotStartedYet
        }
        JedAICommandEvent(command: "enforceReportGeneration").post()
    }
    
    func requestMicrosegmentsSnapshotReport() throws {
        guard getJedAIStatus() else {
            throw JedAIAPIError.jedaiNotStartedYet
        }
        JedAIApp.shared.exportDataEnrichment(requestedMetrics: nil) { [weak self] snapshot in
            os_log_file("[package] microsegmentsSnapshotReportHandler executed with payload: %@", snapshot)
            let payload: [String: [String: Any]] = snapshot.mapValues { value in
                var map = [String: Any]()
                if let string = value as? String {
                    map["s"] = string
                }
                if let number = value as? Double {
                    map["n"] = number
                }
                return map
            }
            self?.sdkWrapper?.eventEmitterManager.sendEvent(.onSnapshotReport, payload: payload)
        }
    }
    
    func requestEncodedEnrichmentReport() throws {
        guard getJedAIStatus() else {
            throw JedAIAPIError.jedaiNotStartedYet
        }
        JedAIReport.shared.encodedEnrichmentReportHandler = { [weak self] report in
            os_log_file("[package] encodedEnrichmentReportHandler executed with payload: %@", report)
            self?.sdkWrapper?.eventEmitterManager.sendEvent(.onEncodedEnrichmentReport, payload: report)
        }
        JedAIReport.shared.requestEncodedEnrichmentReport()
    }
    
    func exportDataEnrichment(requestedMetrics: [String]?, _ completion: @escaping (Result<[String: Any], Error>) -> Void) {
        guard getJedAIStatus() else {
            completion(.failure(JedAIAPIError.jedaiNotStartedYet))
            return
        }
        JedAIApp.shared.exportDataEnrichment(requestedMetrics: requestedMetrics) { snapshot in
            os_log_file("[package] microsegmentsSnapshotReportHandler executed with payload: %@", snapshot)
            completion(.success(snapshot))
        }
    }
    
    
    // MARK: - JedAI
    
    func enableJedAI(_ callback: @escaping () -> ()) {
        self.sdkWrapper?.stateManager.enableJedAI(launchOptions: nil, callback: callback)
    }
    
    func disableJedAI(_ callback: @escaping () -> ()) {
        self.sdkWrapper?.stateManager.disableJedAI(cleanup: false, callback: callback)
    }
    
    func disableAndPurgeJedAI(_ callback: @escaping () -> ()) {
        self.sdkWrapper?.stateManager.disableJedAI(cleanup: true, callback: callback)
    }
    
    func getJedAIStatus() -> Bool {
        JedAI.shared.state == JedAIState.active
    }
    
    func shareFeedback(completion: @escaping (Error?) -> Void) {
        DispatchQueue.main.async {
            guard self.getJedAIStatus() else {
                completion(JedAIAPIError.jedaiNotStartedYet)
                return
            }
            AnagogShareService.shareFeedback { activityType, completed, returnedItems, error in
                if let error {
                    completion(error)
                }
                else if completed == false {
                    completion(JedAIAPIError.customError("Share extension returned 'false'"))
                }
                else {
                    completion(nil)
                }
            }
        }
    }
    
    func generateFeedback(completion: @escaping (Result<String, Error>) -> Void) {
        DispatchQueue.main.async {
            guard self.getJedAIStatus() else {
                completion(.failure(JedAIAPIError.jedaiNotStartedYet))
                return
            }

            AnagogShareService.generateFeedback { result in
                switch result {
                case .success(let url):
                    completion(.success(url.path))
                case .failure(let error):
                    completion(.failure(error))
                }
            }
        }
    }
    
    func getVersion() -> String {
        return JedAI.shared.api.version
    }
    
    func openDebuggingScreen(additionalInfo info: [String: Any]?, completion: @escaping (Error?) -> Void) {
        DispatchQueue.main.async {
#if canImport(JedAIToolboxKit)
            guard let rootController = UIApplication.shared.delegate?.window??.rootViewController else {
                completion(JedAIAPIError.customError("Unable to find rootViewController"))
                return
            }
            guard #available(iOS 15.0, *) else {
                let alert = UIAlertController(title: "Unsupported iOS Version", message: "Debugging Screen is available for iOS 15 and above", preferredStyle: .alert)
                alert.addAction(.init(title: "Ok", style: .cancel))
                rootController.present(alert, animated: true)
                completion(JedAIAPIError.minimalOSVersion("15"))
                return
            }
            
            var ext: AnagogDebugScreen?
            if let info {
                ext = AnagogDebugScreen(json: info)
            }
            
            let controller = JedAIToolbox.shared.makeJedAIDebugViewController(extension: ext)
            rootController.present(controller, animated: true)
#else
            completion(JedAIAPIError.toolboxNotFound)
#endif
        }
    }
    
    
    // MARK: - Onboarding
    
    func onboardingStart() throws {
        guard getJedAIStatus() else {
            throw JedAIAPIError.jedaiNotStartedYet
        }
        JedAIApp.onboarding.start()
    }
    
    func onboardingComplete() throws {
        guard getJedAIStatus() else {
            throw JedAIAPIError.jedaiNotStartedYet
        }
        JedAIApp.onboarding.complete()
    }
    
    func onboardingMarkAsCompleted() throws {
        guard getJedAIStatus() else {
            throw JedAIAPIError.jedaiNotStartedYet
        }
        JedAIApp.onboarding.markAsComplete()
    }
    
    func onboardingReset() throws {
        guard getJedAIStatus() else {
            throw JedAIAPIError.jedaiNotStartedYet
        }
        JedAIApp.onboarding.reset()
    }
    
    
    // MARK: - PageTracker
    
    func enterPage(_ pageName: String) throws {
        guard getJedAIStatus() else {
            throw JedAIAPIError.jedaiNotStartedYet
        }
        JedAIApp.pageTracker.enterPage(pageName: pageName)
    }
    
    func exitPage() throws {
        guard getJedAIStatus() else {
            throw JedAIAPIError.jedaiNotStartedYet
        }
        JedAIApp.pageTracker.exitPage()
    }
    
    
    // MARK: - Other
    
    func sendUserInteractionEvent(campaignIdentifier: String, triggerType triggerTypeString: String) throws {
        guard getJedAIStatus() else {
            throw JedAIAPIError.jedaiNotStartedYet
        }
        let triggerType: JEMAUserInteractionEvent.TriggerType
        switch triggerTypeString {
        case "DISMISSED":
            triggerType = .notificationDismissed
        case "TIMEOUT":
            triggerType = .notificationTimeout
        case "TRIGGERED":
            triggerType = .acceptedTrigger
        case "CONVERSION":
            triggerType = .conversion
        case "CLICKED":
            triggerType = .notificationClick
        default:
            throw JedAIAPIError.unknownTriggerType(triggerTypeString)
        }
        
        JEMAUserInteractionEvent(campaignIdentifier: campaignIdentifier, triggerType: triggerType).post()
    }
    
    func handleUNUserNotificationCenter() {
        self.sdkWrapper?.handleUNUserNotificationCenter()
    }
    
    
    // MARK: - Logs
    
    @objc
    func logInfo(_ message: String) {
        os_log_file("%@", type: .info, message)
    }
    
    @objc
    func logError(_ message: String) {
        os_log_file("%@", type: .error, message)
    }
    
}

enum JedAIAPIError: LocalizedError {
    case micromomentIdentfierIsMissing
    case unknownTriggerType(String)
    case jedaiNotStartedYet
    case minimalOSVersion(String)
    case toolboxNotFound
    case safeError
    case customError(String)
    
    var errorDescription: String? {
        switch self {
        case .micromomentIdentfierIsMissing:
            return "Micro-Moment Identifier is missing"
        case .unknownTriggerType(let string):
            return "Unknown TriggerType: \"\(string)\""
        case .jedaiNotStartedYet:
            return "JedAI is not running"
        case .minimalOSVersion(let version):
            return "This API is available only for iOS \(version) and above"
        case .toolboxNotFound:
            return "JedAI/Toolbox framework not found"
        case .safeError:
            return "'JedAIApp.safe' method failed"
        case .customError(let message):
            return message
        }
    }
}
