import os from "node:os";

import type { PostHog as PostHogType } from "posthog-node";
import type { IdeInfo } from "./types";
import { Logger } from "../utils";

const logger = new Logger("Posthog Telemetry");

/**
 * It's helpful to know what functions errors occur in, but absolute
 * file paths are sensitive information, so we want to remove them.
 * @param stack The stack trace to extract minimal information from.
 * @returns A string containing the minimal stack trace information.
 */
export function extractMinimalStackTraceInfo(stack: unknown): string {
  if (typeof stack !== "string") {
    return "";
  }
  const lines = stack
    .trim()
    .split("\n")
    .map((line) => line.trim());
  const minimalLines = lines.filter((line) => {
    return (
      line.startsWith("at ") &&
      !line.includes("node_modules") &&
      !line.includes("node:internal")
    );
  });
  return minimalLines
    .map((line) => line.replace("at ", "").split(" (").slice(0, 1))
    .flatMap((parts) =>
      parts.map(
        // to be safe, remove any lingering paths - anonymous function case
        (part) =>
          part.replace(/(?:[A-Za-z]:[\\/]|[\\/])[^\n]*?:\d+:\d+/g, "").trim(),
      ),
    )
    .filter((part) => !!part) // remove empty string parts (anonymous functions case)
    .join(", ");
}

export class EventTracker {
  // Set to undefined whenever telemetry is disabled
  static client: PostHogType | undefined = undefined;
  static uniqueId = "NOT_UNIQUE";
  static userId: string | undefined = undefined;
  static os: string | undefined = undefined;
  static ideInfo: IdeInfo | undefined = undefined;

  /**
   * Convenience method for capturing errors in a single event
   */
  static async captureError(errorName: string, error: unknown) {
    if (!(error instanceof Error)) {
      return;
    }
    await EventTracker.capture(
      "extension_error_caught",
      {
        errorName,
        message: error.message,
        stack: extractMinimalStackTraceInfo(error.stack),
      },
      false,
    );
  }

  static async capture(
    event: string,
    properties: { [key: string]: unknown },
    isExtensionActivationError: boolean = false,
  ) {
    try {
      const augmentedProperties = {
        ...properties,
        os: EventTracker.os,
        extensionVersion: EventTracker.ideInfo?.extensionVersion,
        ideName: EventTracker.ideInfo?.name,
      };
      const payload = {
        distinctId: EventTracker.uniqueId,
        event,
        properties: augmentedProperties,
        sendFeatureFlags: false,
      };
      if (process.env.NODE_ENV === "development") {
        return;
      }
      // In cases where an extremely early fatal error occurs, we may not have initialized yet
      if (isExtensionActivationError && !EventTracker.client) {
        const client = await EventTracker.getTelemetryClient();
        client?.capture(payload);
        return;
      }
      logger.info(
        `Capturing telemetry event: ${event} with properties: ${JSON.stringify(event)}`,
      );

      const client = EventTracker.client;
      if (!client) {
        logger.info(
          "Telemetry client not initialized, skipping event capture.",
        );
        return;
      }
      client.capture(payload);
    } catch (e) {
      console.error(`Failed to capture event: ${e}`);
    }
  }

  static shutdownPosthogClient() {
    EventTracker.client?.shutdown();
  }

  static async getTelemetryClient(): Promise<PostHogType | undefined> {
    try {
      const { PostHog } = await import("posthog-node");
      return new PostHog("phc_aUO790jHd7z1SXwsYCz8dRApxueplZlZWeDSpKc5hol", {
        host: "https://us.posthog.com",
      });
    } catch (e) {
      console.error(`Failed to setup telemetry: ${e}`);
      return undefined;
    }
  }

  static async setup(allow: boolean, ideInfo: IdeInfo) {
    EventTracker.os = os.platform();
    EventTracker.ideInfo = ideInfo;

    if (!allow || process.env.NODE_ENV === "development") {
      EventTracker.client = undefined;
    } else if (!EventTracker.client) {
      EventTracker.client = await EventTracker.getTelemetryClient();
    }
  }

  static async identify(uniqueId: string): Promise<void> {
    if (uniqueId === EventTracker.uniqueId) {
      return;
    }
    EventTracker.uniqueId = uniqueId;
    const client = await EventTracker.getTelemetryClient();
    client?.identify({
      distinctId: uniqueId,
      properties: {
        lastUsedExtensionVersion: EventTracker.ideInfo?.extensionVersion,
        lastUsedEditor: EventTracker.ideInfo?.name,
        $set_once: { first_login: new Date().toISOString() },
      },
    });
  }

  static async setUserId(userId: string): Promise<void> {
    if (EventTracker.uniqueId === "NOT_UNIQUE") {
      throw new Error("Telemetry uniqueId not set before setting userId");
    }
    if (userId === EventTracker.userId) {
      return;
    }
    EventTracker.userId = userId;
    const client = await EventTracker.getTelemetryClient();

    await client?.aliasImmediate({
      distinctId: EventTracker.uniqueId,
      alias: userId,
    });
    EventTracker.uniqueId = userId;
  }
}
