//
//  GMFGSelfInstallTimeTracker.swift
//  MVM5G
//
//  Created by Gujuluva Santharam, Ajai Prabhu on 05/02/21.
//  Copyright © 2021 Kyle. All rights reserved.
//

import Foundation

enum MFFGHSPageDisplayEvent {
    case show
    case hide(_ elapse: TimeDuration)
}

final class GMFGSelfInstallTimeTracker {
    // MARK: - Private Propeties
    /* Elapsed will give us the actual time spent by user in the self setup */
    private(set) var elapsed: Double = GMFGStorageManager.retrieve(key: GMFGConstant.TimeTracker.details)?["elapsedTime"] as? Double ?? 0
    private var sessionStartTime: Double = Date().timeIntervalSince1970 // this will make sure not to calculate with zero
    private var sessionEndTime: Double = 0
    private var pageStartTime: Double = 0
    private var didWentBgState: Bool = false // to track transion of bg/fg states to send analytics
    private var foregroundObserver: NSObjectProtocol?
    private var currentSessionInterval: Double = 0
    // MARK: - Public Properties
    static var shared: GMFGSelfInstallTimeTracker = GMFGSelfInstallTimeTracker()
    
    // MARK: - Initialisers
    private init() {
        if #available(iOS 13.0, *) {
            NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: UIScene.willDeactivateNotification, object: nil)
        } else {
            NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: UIApplication.willResignActiveNotification, object: nil)
        }
        
        foregroundObserver = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [weak self] notification in
            self?.beginSession()
        }
    }
    
    deinit {
        if #available(iOS 13.0, *) {
            NotificationCenter.default.removeObserver(self, name: UIScene.willDeactivateNotification, object: nil)
        } else {
            NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil)
        }
        NotificationCenter.default.removeObserver(foregroundObserver as Any)
    }
    
    // MARK: - Public Helpers
    
    func beginSession() {
        sessionStartTime = currentTime()
    }
    
    func endSession() {
        let newSessionEndTime = currentTime()
        let previousSessionTime = elapsed
        if sessionEndTime != 0 {
            elapsed += newSessionEndTime - sessionEndTime
        } else {
            elapsed += newSessionEndTime - sessionStartTime
        }
        currentSessionInterval = elapsed - previousSessionTime
        sessionEndTime = newSessionEndTime
        
        func handleLargeTiming() {
            // NOTE: Fallback approach to avoid sending unimaginable time spent all sessions to server & Reset time
            let currentAllSpendSessionTimeInMonths = elapsed.month
            if currentAllSpendSessionTimeInMonths > 1 {
                trackGemini(value: ["LASTHappen": true,
                                    "LASTSReceived" : elapsed.millisecond,
                                    "LASTSStartTime": sessionStartTime,
                                    "LASTSEndTime": sessionEndTime,
                                    "LASTSCurrentTime": newSessionEndTime],
                            logType: .UI)
               elapsed = previousSessionTime
               sessionStartTime = currentTime()
               sessionEndTime = currentTime()
            }
        }
        
        handleLargeTiming()
        GMFGStorageManager.addEntries(dictionary: ["elapsedTime": elapsed], key: GMFGConstant.TimeTracker.details)
    }
    
    // MARK:- Page Tracking
    
    func trackPageAppear(with pageJSON: [String: Any]?) {
        pageStartTime = currentTime()
    }
    
    func trackPageDisappear(with pageJSON: [String: Any]?) {
        let timeDuration = TimeDuration(start: pageStartTime, end: currentTime())
        endSession()
        logPageDisplayEvent(of: .hide(timeDuration), with: pageJSON)
        if didWentBgState { didWentBgState = false } // reset back to default state
    }
}

struct TimeDuration {
    let start: TimeInterval
    let end: TimeInterval
}

extension TimeDuration {
    var duration: TimeInterval {
        return end - start
    }
}

// MARK: - Private Helpers
private extension GMFGSelfInstallTimeTracker {
    
    // MARK: - Private Helpers
    func currentTime() -> Double {
        return Date().timeIntervalSince1970
    }
    
    @objc
    func willResignActive(_ notification: Notification) {
        endSession()
        didWentBgState = true // reset once logged
    }
    
    func preparePageDisappear(with pageDuration: TimeDuration) -> [String: Any] {
        var params: [String: Any] = fetchDefaultAdditionalInfo()
        params["eventName"] = "stop"
        params["startTime"] = Int(pageDuration.start.millisecond)
        params["endTime"] = Int(pageDuration.end.millisecond)
        params["timeSpendPage"] = Int(pageDuration.duration.millisecond)
        params["didEnterBg"] = didWentBgState
        return params
    }
    
    func preparePageAppear() -> [String: Any] {
        var params: [String: Any] = fetchDefaultAdditionalInfo()
        params["eventName"] = "start"
        params["timeSpendPage"] = 0
        return params
    }
    
    func fetchDefaultAdditionalInfo() -> [String: Any] {
        return [ "eventType": "pageDisplay",
                 "timeSpentAllSessions": Int(GMFGSelfInstallTimeTracker.shared.elapsed.millisecond), // to convert seconds to milliseconds
                 "timeSpentSession": Int(currentSessionInterval.millisecond)
        ] as [String : Any]
    }
}

extension TimeInterval {
    var hour: Int {
        Int(self/3600)
    }
    var millisecond: Int {
        Int(self*1000)
    }
    var day: Int {
        hour/24
    }
    var month: Int {
        day/30
    }
}

// MARK: - Analytics Protocol Conformance
extension GMFGSelfInstallTimeTracker: MFFGHSAnalyticsProtocol  {
    func logPageDisplayEvent(of type: MFFGHSPageDisplayEvent, with pageJson: [String: Any]?) {
        let additionalData: [String: Any]
        switch type {
        case .show:
            additionalData = preparePageAppear()
        case let .hide(elapsed):
            additionalData = preparePageDisappear(with: elapsed)
        }
        trackPage(data: pageJson, additionalData: additionalData)
    }
}
