import { EventEmitter } from "events";

import Navigation from "../Navigation";

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

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

import {
    CTA,
    INudgesRepository,
    NudgeCallBack,
} 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,
  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 EmergencyMgmt from "../EmergencyMgmt";
import {
  validateAndSaveSiteCatalog,
  validateAndSaveUserCatalog,
} from "../Navigation/CoreCatalogValidator";

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 DEFAULT_BASE_URL = "https://api.kenkai.io/v1";
  private DEFAULT_INGEST_PATH = "/ingest/events";
  private DEFAULT_DIMENSION_PATH = "/ingest/dimensions";
  private DEFAULT_NUDGE_FETCH_PATH = "/action/sdk/pull";
  private DEFAULT_CATALOG_PATH = "/ingest/dimensions";
  private DEFAULT_CDN_URL = "https://cdn.causalfoundry.ai/";

  private DEFAULT_FLUSH_INTERVAL = 10000;
  private DEFAULT_FLUSH_MAX_RETRIES = 2;
  private DEFAULT_CACHE_EVENTS_KEY = "cfEvents";
  private DEFAULT_CACHE_CATALOG_KEY = "cfCatalog";

  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,
      dimensionPath: this.DEFAULT_DIMENSION_PATH,
      cacheEventsInLocalstorage: true,
      cacheEventsKey: this.DEFAULT_CACHE_EVENTS_KEY,
      cacheCatalogKey: this.DEFAULT_CACHE_CATALOG_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
    );

    CfAppStateMonitor(this);

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

  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;
    }

    this.cfCore.login(userId);

    if (action === IdentityAction.Logout) {
      clearLocalStorageUserId();
    } else {
      this.runNudgeManager(userId);
      setLocalStorageUserId(userId);
      if (userProps) {
        validateAndSaveUserCatalog(userId, 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(userId: string, userCatalog: UserCatalog) {
    if (this.options.pauseSDK) {
      return;
    }
    validateAndSaveUserCatalog(userId, userCatalog);
  }

  public updateSiteCatalog(siteId: string, siteCatalog: SiteCatalog) {
    if (this.options.pauseSDK) {
      return;
    }
    validateAndSaveSiteCatalog(siteId, 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);
  }

  /**
   * @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)
  })

  }
}
