//
//  GoogleAnalyticsPluginAdapter.swift
//  ZappAnalyticsPluginGAtvOS
//
//  Created by Anton Kononenko on 10/17/19.
//  Copyright © 2019 Applicaster. All rights reserved.
//

import Foundation
import ZappCore

@objc open class APTimedEvent: NSObject {
    public var eventName: String
    public var parameters: [String: Any]?
    public var startTime: Date

    public init(eventName: String,
                parameters: [String: Any]?,
                startTime: Date) {
        self.eventName = eventName
        self.parameters = parameters
        self.startTime = startTime
    }
}

@objc open class GoogleAnalyticsPluginAdapter: NSObject, ZPAdapterProtocol {
    public var configurationJSON: NSDictionary?

    // used for optional support for timed events implementation
    var timedEventsDictionary: [String: APTimedEvent] = [:]

    /// Plugin configuration keys
    struct PluginKeys {
        static let trackingID = "tracker_id"
        static let screenViewsEsnabled = "screen_views_enabled"
    }

    /// Google Analytics Manager
    var manager: MeasurementProtocolManager?

    /// Define if screen view enabled in plugin
    var isScreenViewsEnabled: Bool = false

    var defaultEventParameters: [String: Any] = [:]

    public required init(configurationJSON: NSDictionary?) {
        self.configurationJSON = configurationJSON
    }

    public required override init() {
        super.init()
    }

    /// Devide event name on category and label
    ///
    /// - Parameter value: value to devide
    /// - Returns: Return array of devided title
    func separatedEvents(forEventString value: String) -> [String] {
        var result: [String] = []

        guard String.isNotEmptyOrWhitespace(value) else {
            return result
        }

        var eventSeparated = value.components(separatedBy: ":")
        if eventSeparated.count == 1 {
            eventSeparated = value.components(separatedBy: "-")
        }

        if eventSeparated.count == 2 {
            for (_, eventValue) in eventSeparated.prefix(2).enumerated() {
                if let trimmedValue = trimmString(string: eventValue) {
                    result.append(trimmedValue)
                }
            }
        } else if let trimmedValue = trimmString(string: value) {
            result.append(trimmedValue)
        }

        return result
    }

    /// Trimm whitespaces from string
    ///
    /// - Parameter string: string to trimm
    /// - Returns: if string not empty return trimmed string otherwise nil
    func trimmString(string: String) -> String? {
        let trimmedString = string.trimmingCharacters(in: .whitespaces)
        return String.isNotEmptyOrWhitespace(trimmedString) ? trimmedString : nil
    }

    /// Create label for analytics event
    ///
    /// - Parameter params: parameters to combine
    /// - Returns: label string
    func analyticsString(fromParams params: [String: Any]) -> String {
        var mergedParams = ""
        let sortedParams = Array(params.keys).sorted()
        for (index, paramName) in sortedParams.enumerated() {
            if let paramValue = params[paramName] {
                let paramValueString = String(describing: paramValue)
                mergedParams = mergedParams.appendingFormat("%@=%@",
                                                            paramName,
                                                            paramValueString)
                if index < sortedParams.count - 1 {
                    mergedParams += "; "
                }
            }
        }

        return mergedParams.count > 0 ? mergedParams : ""
    }

    public func registerTimedEvent(_ eventName: String, parameters: [String: Any]?) {
        if let currentEvent = self.timedEventsDictionary[eventName] {
            processEndTimedEvent(currentEvent.eventName, parameters: currentEvent.parameters)
        }

        let timedEvent = APTimedEvent(eventName: eventName,
                                      parameters: parameters,
                                      startTime: Date())
        timedEventsDictionary[eventName] = timedEvent
    }

    public func processEndTimedEvent(_ eventName: String, parameters: [String: Any]?) {
        if let timedEvent = self.timedEventsDictionary[eventName] {
            // Handle merging parameters with the later ones overriding the earlier ones if needed
            let parameters = parameters ?? [:] as [String: Any]
            let timedEventParameters = timedEvent.parameters ?? [:] as [String: Any]
            var mergedParameters = timedEventParameters.merge(parameters)
            mergedParameters["Event Duration"] = "\(Int(abs(timedEvent.startTime.timeIntervalSinceNow)))" as Any
            timedEvent.parameters = mergedParameters
            sendEvent(timedEvent.eventName,
                       parameters: mergedParameters)
            timedEventsDictionary.removeValue(forKey: eventName)
        }
    }
}

extension GoogleAnalyticsPluginAdapter: AnalyticsProviderProtocol {

    public var providerName: String {
        return "Google Analytics"
    }

    public func prepareProvider(_ mandatoryDefaultParams: [String: Any],
                                completion: (_ isReady: Bool) -> Void) {
        guard let trackingID = self.configurationJSON?[PluginKeys.trackingID] as? String else {
            completion(false)
            return
        }

        if let enableScreenViews = self.configurationJSON?[PluginKeys.screenViewsEsnabled] as? Bool {
            isScreenViewsEnabled = enableScreenViews
        }
        defaultEventParameters = mandatoryDefaultParams
        manager = MeasurementProtocolManager(trackingID: trackingID)
        completion(true)
    }

    @objc public func sendEvent(_ eventName: String,
                                parameters: [String: Any]?) {
        let events = separatedEvents(forEventString: eventName)
        let combinedProperties = combineProperties(combinedWithEventParams: parameters)
        let eventLabel = analyticsString(fromParams: combinedProperties)

        manager?.event(category: events.first ?? "",
                       action: events.last ?? "",
                       label: eventLabel,
                       customParameters: CustomDemensionMappingHelper.paramsForCustomDemensions(customParamenters: combinedProperties))
    }

    @objc public func sendScreenEvent(_ screenName: String,
                                      parameters: [String: Any]?) {
        guard isScreenViewsEnabled else {
            return
        }
        manager?.screenView(screenName: screenName)
    }

    @objc public func startObserveTimedEvent(_ eventName: String,
                                             parameters: [String: Any]?) {
        registerTimedEvent(eventName,
                           parameters: parameters)
    }

    @objc public func stopObserveTimedEvent(_ eventName: String,
                                            parameters: [String: Any]?) {
        processEndTimedEvent(eventName,
                             parameters: parameters)
    }

    @objc public func combineProperties(combinedWithEventParams eventParams: [String: Any]?) -> [String: Any] {
        var properties: [String: Any] = [:]
        if let eventParams = eventParams {
            properties = eventParams
        }

        // return merging gathered properties with default properties
        return properties.merge(defaultEventParameters)
    }
}
