import * as vscode from 'vscode';
import TelemetryReporter from 'vscode-extension-telemetry';
import { ExtensionId } from './const/extension';
import { userInfo } from 'os';
import { sep } from 'path';

// telemetry reporter
let reporter: TelemetryReporter | undefined = undefined;
let commonProps: { [key: string]: string } = {};

/** Activate a telemetry reporting */
export function activate(context: vscode.ExtensionContext) {
  // create telemetry reporter on extension activation
  const extension = vscode.extensions.getExtension(ExtensionId)!;
  const extensionVersion = extension.packageJSON.version;

  // following key just allows you to send events to azure insights API
  // so it does not need to be protected
  // but obfuscating anyways - bots scan github for keys, but if you want my key you better work for it, damnit!
  const innocentKitten = Buffer.from('ODFhZGMzMGMtZDRmMC00NTg5LThlOGItMzYzNDdkZDU2ZGQ3', 'base64').toString();

  reporter = new TelemetryReporter(ExtensionId, extensionVersion, innocentKitten);
  // ensure it gets properly disposed
  context.subscriptions.push(reporter);
}

/** Deactivate a telemetry reporting */
export function deactivate() {
  // This will ensure all pending events get flushed
  reporter && reporter.dispose();
}

/** Add common properties that will be always sent when calling `sendEvent()` or `sendError()`. */
export function addCommonProperties(properties: { [name: string]: string | number | boolean | undefined | null }) {
  commonProps = { ...commonProps, ...convertToStrings(properties) };
}

/**
 * Sends an event to telemetry reporting.
 */
export function sendEvent(
  /** Event name.
   *
   * To allow proper grouping and useful metrics, choose a small number of separate event names. For example, don't use a separate name for each generated instance of an event.
   *
   * Max. lenght: 512 characters */
  eventName: string,
  /** Propeties to add to the common properties that will be sent for this event. */
  properties?: { [name: string]: string | number | boolean | undefined | null },
  /** Metrics to add to this event. */
  measurements?: {
    [key: string]: number;
  },
) {
  if (!reporter) {
    console.warn('Telemetry should be activated using activate() before being called.');
    return;
  }
  reporter.sendTelemetryEvent(eventName, { ...commonProps, ...convertToStrings(properties) }, measurements);
}

/**
 * Sends an exception to telemetry reporting.
 */
export function sendError(
  error: Error,
  properties?: { [name: string]: string | number | boolean | undefined | null },
  measurements?: {
    [key: string]: number;
  },
) {
  if (!reporter) {
    console.warn('Telemetry should be activated using activate() before being called.');
    return;
  }
  if (error.stack) {
    error.stack = anonymizePaths(error.stack);
  }
  reporter.sendTelemetryException(error, { ...commonProps, ...convertToStrings(properties) }, measurements);
}

/**
 * Sends an exception as an event to telemetry reporting.
 *
 * Note: Using `sendError()` is prefered unless you want it to appear in the list of custom events.
 */
export function sendErrorEvent(
  eventName: string,
  properties?: { [name: string]: string | number | boolean | undefined | null },
  measurements?: {
    [key: string]: number;
  },
  error?: Error,
) {
  if (!reporter) {
    console.warn('Telemetry should be activated using activate() before being called.');
    return;
  }
  if (error && error.stack) {
    error.stack = anonymizePaths(error.stack);
  }
  reporter.sendTelemetryErrorEvent(
    eventName,
    {
      ...commonProps,
      ...properties,
      errorStack: error?.stack || '',
      errorMessage: error?.message || `${error}`,
      errorName: error?.name || '',
    },
    measurements,
    [],
  );
}

/** converts non-string value properties on object to strings */
function convertToStrings(properties?: {
  [name: string]: string | number | boolean | undefined | null;
}): { [name: string]: string } {
  if (!properties) return {};
  return Object.keys(properties).reduce((prevVal, current) => {
    let propVal = properties[current];
    if (propVal === undefined) {
      propVal = '<undefined>';
    } else if (propVal === null) {
      propVal = '<null>';
    } else if (typeof propVal !== 'string') {
      propVal = propVal.toString();
    }
    prevVal[current] = propVal;
    return prevVal;
  }, {} as { [name: string]: string });
}

/** replace username with anon */
function anonymizePaths(input: string) {
  if (input == null) return input;
  return input.replace(new RegExp('\\' + sep + userInfo().username, 'g'), sep + 'anon');
}

export default { activate, deactivate, addCommonProperties, sendError, sendEvent, sendErrorEvent };
