import {createCheckers} from "ts-interface-checker"
import {EventEmitter} from 'events';

import ECommerce from "../ECommerce";
import Loyalty from "../Loyalty";
import Navigation from '../Navigation';
import PatientMgmt from "../PatientMgmt";

import {
    clearLocalStorageUserId,
    getDeviceId,
    getLocalStorageUserId,
    setLocalStorageUserId,
} from '../../drivers/CfSystem';

import ECommercePropertiesTI from "../ECommerce/typings-ti"
import BloodPropertiesTI from '../ECommerce/blood.typings-ti'
import OxygenPropertiesTI from '../ECommerce/oxygen.typings-ti'

import NavigationPropertiesTI, {IdentifyProperties} from '../Navigation/typings-ti'
import CommonTypesTI from '../../core/commonTypes-ti'

import CfSender from "../../core/CfSender";
import CfCore from '../../core/CfCore';
import CfAppStateMonitor from "../../core/CfAppStateMonitor";
import NudgeManager from '../../core/CfNudgeManager';

import CatalogRepository, {ICatalogRepository} from "../../core/repositories/catalog/CatalogRepository";
import {CTA, INudgesRepository} from "../../core/repositories/nudges/typings";
import NudgeRepositoryFactory from "../../core/repositories/nudges/NudgeFactory";

import {CfLogOptions, LogIngestorOptions} from '../../core/typings';

import ImpressionsDetector from "../../core/impressions"

import {
    ContentBlock,
    IdentityAction,
    NavigationTypes,
    NudgeAction,
    SiteCatalog,
    UserCatalog
} from '../Navigation/typings';
import NotificationFactory from "../../drivers/notifications/NotificationFactory";
import {NotificationDispatcher} from "../../drivers/notifications/typings";

import CfLocalStorage from "../../drivers/storage/CfLocalStorage";

import CfNetworkQueue from "../../core/CfNetworkQueue";

import NetworkFactory from "../../drivers/network/CfNetworkFactory";
import {NudgeManagerAction} from "../../core/CfNudgeManager.typings";
import {CfLogEvents, NudgeScreenType} from "../../core/commonTypes";
import CfExceptionHandler from "../../core/CfExceptionHandler";
import isBrowser from "is-in-browser";
import {convertParameterNodes} from "typedoc/dist/lib/converter/factories/signature";
import CallCenter from "../CallCenter";
import EmergencyMgmt from "../EmergencyMgmt";


export default class CfLog extends EventEmitter {
    private deviceId
    private sender
    private cfCore
    private cfNetwork
    private storageDriver
    private networkQueue
    private nudgeManager
    private notificationsEngine: NotificationDispatcher
    private nudgesRepository: INudgesRepository
    private catalogRepository: ICatalogRepository

    private DEFAULT_BASE_URL = 'https://api.causalfoundry.ai/v1'
    private DEFAULT_INGEST_PATH = '/ingest/log'
    private DEFAULT_NUDGE_FETCH_PATH = '/nudge/sdk'
    private DEFAULT_CATALOG_PATH = '/ingest/catalog'
    private DEFAULT_CDN_URL = 'https://cdn.causalfoundry.ai/'

    private DEFAULT_FLUSH_INTERVAL = 10000
    private DEFAULT_FLUSH_MAX_RETRIES = 10
    private DEFAULT_CACHE_EVENTS_KEY = 'cfLog'

    private options: CfLogOptions = {
        cdnUrl: this.DEFAULT_CDN_URL,
        baseUrl: this.DEFAULT_BASE_URL,
        allowAnonymousUsers: false,
        nudgeFetchPath: this.DEFAULT_NUDGE_FETCH_PATH,
        catalogPath: this.DEFAULT_CATALOG_PATH,
        activateNudgeMechanism: true,
        selfManagedNudges: false,
        defaultBlock: ContentBlock.Core,
        debug: true,
        isUsingAsInternalWrapper : false,
        pauseSDK: false
    }

    public constructor(key: string, options?: Partial<CfLogOptions>) {
        super()

        this.deviceId = getDeviceId()
        if (options) {
            this.options = Object.assign(this.options, options)
        }
        this.cfNetwork = NetworkFactory.getNetworkDriver(key)

        this.networkQueue = new CfNetworkQueue(this.cfNetwork)
        this.storageDriver = new CfLocalStorage()

        const logIngestorDefaultOptions: LogIngestorOptions = {
            flushInterval: this.DEFAULT_FLUSH_INTERVAL,
            flushMaxRetries: this.DEFAULT_FLUSH_MAX_RETRIES,
            ingestPath: this.DEFAULT_INGEST_PATH,
            cacheEventsInLocalstorage: true,
            cacheEventsKey: this.DEFAULT_CACHE_EVENTS_KEY,
        }

        const updatedIngestionOptions = {...logIngestorDefaultOptions, ...options}
        const logIngestorOptions = {...updatedIngestionOptions, baseUrl: this.options.baseUrl || this.DEFAULT_BASE_URL}

        this.sender = new CfSender(this.cfNetwork, logIngestorOptions)

        this.cfCore = CfCore.createInstance(
            this.sender,
            ImpressionsDetector,
            this.options.debug,
            this.options.defaultBlock,
            this.deviceId, this.options.isUsingAsInternalWrapper)

        this.nudgesRepository = NudgeRepositoryFactory.getNudgeRepository(
            `${this.options.baseUrl}${this.options.nudgeFetchPath}`,
            this.cfNetwork,
            this.storageDriver
        )

        this.catalogRepository = new CatalogRepository(
            `${this.options.baseUrl}${this.options.catalogPath}`,
            this.networkQueue,
            {}, this.options.pauseSDK
        )

        ECommerce.init(this.catalogRepository)
        Loyalty.init(this.catalogRepository)
        Navigation.init(this.catalogRepository)
        PatientMgmt.init(this.catalogRepository)
        EmergencyMgmt.init(this.catalogRepository)

        CfAppStateMonitor(this)

        if(this.options.allowAnonymousUsers && isBrowser && this.deviceId && (getLocalStorageUserId() === '')){
            this.fetchNudgesForAnonUsers(this.deviceId)
        }

    }

    /**
     * @ignore
     */
    private runNudgeManager(userId: string) {

        if(this.options.pauseSDK){
            return;
        }

        if (this.nudgeManager && !this.options.allowAnonymousUsers) {
            return
        }else {
            this.nudgeManager = null
        }

        if (!this.options.activateNudgeMechanism) {
            return
        }

        if (this.options.selfManagedNudges) {
            return
        }
        this.setupNudgeManager(userId, NudgeScreenType.None, false)

    }

    private fetchNudgesForScreen(userId: string, nudgeScreenType : NudgeScreenType) {

        if(this.options.pauseSDK){
            return
        }

        if (this.nudgeManager) {
            this.nudgeManager = null
        }

        if (!this.options.activateNudgeMechanism) {
            // console.error("[cfLog-nudge]", "enable activateNudgeMechanism in CfLogOptions")
            return
        }

        if (!this.options.selfManagedNudges) {
            // console.error("[cfLog-nudge]", "enable selfManagedNudges in CfLogOptions")
            return
        }

        this.setupNudgeManager(userId, nudgeScreenType, true)
    }

    private fetchNudgesForAnonUsers(deviceId: string) {
        if(this.options.pauseSDK){
            return
        }

        if (this.nudgeManager) {
            this.nudgeManager = null
        }
        this.setupNudgeManager(deviceId, NudgeScreenType.None, true)

    }


    private setupNudgeManager(userId: string, nudgeScreenType: NudgeScreenType, isOneTimeOnly: boolean){
        this.notificationsEngine = NotificationFactory.getNotificationEngine()

        this.nudgeManager = new NudgeManager(
            userId,
            this.notificationsEngine,
            this.nudgesRepository,
            nudgeScreenType, isOneTimeOnly
        )

        this.nudgeManager.on(NudgeManagerAction.Action, (callToActionData: CTA) => {
            this.emit(CfLogEvents.NudgeAction,  callToActionData.type, callToActionData.cta_resource)
        })

        this.nudgeManager.on(NudgeManagerAction.Open, (id, nudgeTime) => {
            Navigation.logPushNotificationEvent({
                ref: id,
                response: NudgeAction.Open,
                time: nudgeTime,
            })
        })

        this.nudgeManager.on(NudgeManagerAction.Discard, (id, nudgeTime) => {
            Navigation.logPushNotificationEvent({
                ref: id,
                response: NudgeAction.Discard,
                time: nudgeTime
            })
        })

        this.nudgeManager.on(NudgeManagerAction.Block, (id, nudgeTime) => {
            Navigation.logPushNotificationEvent({
                ref: id,
                response: NudgeAction.Block,
                time: nudgeTime
            })
        })

        this.nudgeManager.on(NudgeManagerAction.Shown, (id, nudgeTime) => {
            Navigation.logPushNotificationEvent({
                ref: id,
                response: NudgeAction.Shown,
                time: nudgeTime
            })
        })
    }

    public flush() {

        if(this.options.pauseSDK){
            return
        }

        this.sender.flush()
    }

    /**
     * Call this function whenever the application has information about a session event
     * (Register, Login, Logout)
     *
     * @param {IdentityAction} action
     * @param {string} userId
     * @param {UserCatalog} userProps
     * @param {string} referralCode
     */
    public identify(action: IdentityAction, userId: string, userProps? : UserCatalog, referralCode?: string) {

        if(this.options.pauseSDK){
            return
        }

        if(userId == ''){
            new CfExceptionHandler().throwAndLogError(NavigationTypes.Identify, 'invalid-user-id')
            return
        }

        const { IdentifyProperties: IdentifyPropertiesChecker } = createCheckers(
            NavigationPropertiesTI,
            BloodPropertiesTI,
            ECommercePropertiesTI,
            OxygenPropertiesTI,
            CommonTypesTI)


        const userInfo = {
            action: action,
            referral_code: referralCode
        }

        IdentifyPropertiesChecker.check(userInfo)

        this.cfCore.login(userId);

        if (action === IdentityAction.Logout) {
            clearLocalStorageUserId();
        } else {
            this.runNudgeManager(userId);
            setLocalStorageUserId(userId);
            if (userProps) {
                this.catalogRepository.injectUser(userProps);
            }
        }
        Navigation.logIdentifyEvent({
            action: action,
            referral_code: referralCode
        })
    }

    public userBlockStatus(isBlocked: boolean, reason: string, remarks?: string,) {

        if(this.options.pauseSDK){
            return
        }

        if(isBlocked == undefined){
            new CfExceptionHandler().throwAndLogError(NavigationTypes.Identify, 'invalid-block-status')
        }else if(reason == ''){
            new CfExceptionHandler().throwAndLogError(NavigationTypes.Identify, 'invalid-reason')
        }

        const userIdentifyObject = {
            action: isBlocked? IdentityAction.Blocked : IdentityAction.UnBlocked,
            referral_code: "",
            blocked: {
                reason: reason,
                remarks: remarks,
            }
        }

        Navigation.logIdentifyEvent(userIdentifyObject)
    }

    public updateUserCatalog(userCatalog: UserCatalog) {

        if(this.options.pauseSDK){
            return
        }

        this.catalogRepository.injectUser(userCatalog)
    }

    public updateSiteCatalog(siteCatalog: SiteCatalog) {

        if(this.options.pauseSDK){
            return
        }
        console.log("siteHeelo")
        this.catalogRepository.injectSite(siteCatalog)
    }

    /**
     * When this SDK is embedded into a web application, which has been already logged
     * in another section, there is no need to call `identify` again, because the session
     * is already started. Use this function to let the SDK what is the userId, so it is able
     * to include in the subsequent events.
     *
     * An example of a scenario where this is useful is: a native application where
     * some section is implemented in HTML/JS with a WebView. In this case, the session
     * has already started in the native part, so do not call `identify` within the JS section,
     * just call `setUserId`
     *
     * @param userId the string that represents unambiguously this user
     */
    public setUserId(userId: string) {

        if(this.options.pauseSDK){
            return
        }

        if(!userId){
            new CfExceptionHandler().throwAndLogError(NavigationTypes.Identify, 'invalid-user-id')
            return
        }
        this.cfCore.login(userId)
    }
}
