import { Logger } from "@perf-profiler/logger";
import { executeCommand } from "../shell";
import { CppProfilerName, UnixProfiler } from "./UnixProfiler";
import {
  COMPONENT_SHELL_CMD,
  DEFAULT_SHELL_CMD,
  SHELL,
  VPM_CLI_INSTALLER_ID_KEY,
  VPM_CLI_INSTALLER_ID_VALUE,
  VPM_CLI_PKG_ID_KEY,
} from "./DevMode";
abstract class KeplerProfiler extends UnixProfiler {
  protected user: string | undefined;
  protected vdaPath: string | undefined;
  constructor() {
    super();
    // vda check - find the actual binary path instead of wrapper script
    try {
      const vdaWrapperPath = executeCommand(`kepler which vda`).split("\n")[0].trim();
      this.vdaPath = this.getVdaBinaryPath(vdaWrapperPath);
    } catch {
      try {
        const vdaWrapperPath = executeCommand(`which vda`).split("\n")[0].trim();
        this.vdaPath = this.getVdaBinaryPath(vdaWrapperPath);
      } catch {
        throw new Error(
          "Path to vda cannot be resolved. Ensure vda is added to the PATH before running flashlight."
        );
      }
    }
    // shell user check
    this.user = executeCommand(this.getDeviceCommand(`whoami`)).split("\n")[0];
  }

  /**
   * Convert vda wrapper script path to actual binary path based on host architecture.
   * Example: /path/to/runtime/bin/vda -> /path/to/runtime/aarch64-Darwin/bin/vda
   */
  private getVdaBinaryPath(wrapperPath: string): string {
    try {
      // Get host architecture
      const hostArch = this.getHostArchitecture();

      // Extract the runtime directory from wrapper path
      // Example: /<sdk_path>/workspace/env/KeplerCLIVegaDeviceAdaptor-2.0/runtime/bin/vda
      // Should become: /<sdk_path>/workspace/env/KeplerCLIVegaDeviceAdaptor-2.0/runtime/aarch64-Darwin/bin/vda
      const runtimeMatch = wrapperPath.match(/^(.+\/runtime)\/bin\/vda$/);
      if (runtimeMatch) {
        const runtimeDir = runtimeMatch[1];
        const binaryPath = `${runtimeDir}/${hostArch}/bin/vda`;
        // Verify the binary exists
        try {
          executeCommand(`test -f "${binaryPath}"`);
          return binaryPath;
        } catch {
          Logger.debug(`vda binary not found at ${binaryPath}, falling back to wrapper script`);
          return wrapperPath;
        }
      } else {
        Logger.debug(`Could not parse vda runtime path from ${wrapperPath}, using wrapper script`);
        return wrapperPath;
      }
    } catch (error) {
      Logger.debug(`Error resolving vda binary path: ${error}, using wrapper script`);
      return wrapperPath;
    }
  }

  /**
   * Get host architecture string for vda binary path.
   * Matches the logic from vda wrapper script:
   * - Linux: x86_64-Linux
   * - Darwin x86_64: x86_64-Darwin
   * - Darwin arm64: aarch64-Darwin
   */
  private getHostArchitecture(): string {
    const sysArchitecture = executeCommand(`uname -m`).split("\n")[0].trim();
    const osName = executeCommand(`uname -s`).split("\n")[0].trim();

    if (osName === "Linux") {
      // Linux only supports x86_64
      return "x86_64-Linux";
    } else if (osName === "Darwin") {
      if (sysArchitecture === "x86_64") {
        return "x86_64-Darwin";
      } else if (sysArchitecture === "arm64") {
        return "aarch64-Darwin";
      } else {
        Logger.error(`Unsupported architecture ${sysArchitecture} on Darwin`);
        throw new Error(`Unsupported architecture: ${sysArchitecture}`);
      }
    } else {
      Logger.error(`OS not recognized: ${osName}`);
      throw new Error(`OS not recognized: ${osName}`);
    }
  }

  public detectCurrentBundleId(): string {
    return this.getBundleId(this.getVisibleAppComponentId(), this.user !== "root");
  }

  /**
   * Utility method to push an executable to the device/simulator.
   *
   * @param execSrcPath absolute source path of the executable on host.
   */
  public pushExecutable(execSrcPath: string): void {
    const deviceProfilerPath = this.getDeviceProfilerPath();
    executeCommand(`${this.vdaPath} push ${execSrcPath} ${deviceProfilerPath}`);
  }

  /**
   * Currently live FPS monitoring is not supported in Kepler.
   * @returns
   */
  supportFPS(): boolean {
    return false;
  }

  /**
   * Utility method to get visible app component id.
   * In case if there is no visible app, empty string is returned.
   *
   * @returns component id string.
   */
  getVisibleAppComponentId(): string {
    const componentId = executeCommand(
      this.getDeviceCommand(`vlcm list | grep " VISIBLE " | awk '{print $1}'`, SHELL.DEFAULT_SHELL)
    )
      .split("\n")
      .map((element) => element.trim())
      .filter(Boolean);
    if (componentId.length >= 1 && !componentId[componentId.length - 1]) {
      Logger.warn("No visible app detected");
      return "";
    }
    return componentId[componentId.length - 1];
  }

  /**
   * Utility method to get the bundle id from component id.
   * Incase for simulator, enforce_sideload_check is disabled.
   * For actual device, enforce_sideload_check is enabled.
   *
   * @param componentId
   * @param enforce_sideload_check
   * @returns bundle id string.
   */
  getBundleId(componentId: string, enforce_sideload_check: boolean = false): string {
    if (!componentId) {
      return "";
    }
    const vpminfo = executeCommand(
      this.getDeviceCommand(
        `vpm info ${componentId} --package | egrep '^(${VPM_CLI_INSTALLER_ID_KEY}|${VPM_CLI_PKG_ID_KEY})' | awk -F':' '{print $2}'`
      )
    )
      .split("\n")
      .map((element) => element.trim())
      .filter(Boolean);
    if (enforce_sideload_check && vpminfo[vpminfo.length - 1] !== VPM_CLI_INSTALLER_ID_VALUE) {
      Logger.warn("Not a sideloaded app.");
      return "";
    }
    return vpminfo[vpminfo.length - 2];
  }

  /**
   * Utility method to get the main interactive component id from bundle id.
   *
   * @param bundleId
   * @returns bundle id string.
   */
  getComponentId(bundleId: string): string {
    if (!bundleId) {
      return "";
    }
    const vlcminfo = executeCommand(
      this.getDeviceCommand(`vlcm list | grep -i ${bundleId} | grep -i "main" | awk '{print $1}'`)
    )
      .split("\n")
      .map((element) => element.trim())
      .filter(Boolean);
    if (vlcminfo.length >= 1) {
      return vlcminfo[vlcminfo.length - 1];
    }
    return "";
  }
}

export class KeplerSimulatorProfiler extends KeplerProfiler {
  /**
   * no-op: Dev Mode not supported in simulator yet.
   * So no action when id is updated by the user.
   */
  protected updateIds(): boolean {
    return false;
  }

  public getDeviceCommand(command: string): string {
    return `${this.vdaPath} ${DEFAULT_SHELL_CMD} ${command}`;
  }

  public getAbi(): string {
    const sysArchitecture = executeCommand(`uname -m`).split("\n")[0];
    if (sysArchitecture === "x86_64") {
      return "x86_64";
    } else if (sysArchitecture === "arm64") {
      return "aarch64";
    } else {
      Logger.error("Incompatible system architecture. Supported host - x86_64 and aarch64");
      return "";
    }
  }

  protected assertSupported(): void {
    // Not implemented
  }

  stop(): void {
    // Nothing to do
    // TODO needs to kill the SSH PID or send a sigterm/sigstop?
  }

  public getDeviceProfilerPath(): string {
    return `/tmp/${CppProfilerName}`;
  }

  /**
   * Same as profiler path as dev mode not supported in simulator.
   *
   * @returns sandboxed profiler path.
   */
  public getDeviceProfilerSandboxedPath(): string {
    return this.getDeviceProfilerPath();
  }
}

export class KeplerDeviceProfiler extends KeplerProfiler {
  private componentId: string | undefined;
  private bundleId: string | undefined;
  private isDeviceInDevMode: boolean = false;

  constructor() {
    super();
    try {
      const devModeResponse = executeCommand(
        this.getDeviceCommand(`vsm developer-mode`, SHELL.DEFAULT_SHELL)
      ).split("\n")[0];
      if (devModeResponse === "developer-mode: enable") {
        this.isDeviceInDevMode = true;
      }
    } catch (error: unknown) {
      Logger.debug(`Error while checking device's dev mode status: ${JSON.stringify(error)}`);
    }
  }

  /**
   * Detect visible app bundle id.
   * In case if device is in dev mode; Only sideloaded apps can be monitored.
   * This is due to VSH(sandboxed shell) which has restrictions and is accessible
   * only to sideloaded apps (with the exception to bash shell with root user).
   *
   * @returns bundle id if found else empty string.
   */
  public detectCurrentBundleId(): string {
    return this.getBundleId(this.getVisibleAppComponentId(), this.user !== "root");
  }

  protected updateIds(bundleId: string): boolean {
    if (!bundleId || bundleId === this.bundleId) {
      Logger.debug("Bundle id empty or same.");
      return false;
    }
    this.bundleId = bundleId;
    this.componentId = this.getComponentId(bundleId);
    return true;
  }

  public getDeviceCommand(command: string, shell: SHELL = SHELL.DEFAULT_SHELL): string {
    if (!this.isDeviceInDevMode) {
      return `${this.vdaPath} ${DEFAULT_SHELL_CMD} ${command}`;
    } else if (shell === SHELL.COMPONENT_SHELL) {
      return `${this.vdaPath} ${COMPONENT_SHELL_CMD} ${this.componentId} ${command}`;
    } else if (shell === SHELL.DEFAULT_SHELL) {
      return `${this.vdaPath} ${DEFAULT_SHELL_CMD} ${command}`;
    } else {
      return `${this.vdaPath} ${DEFAULT_SHELL_CMD} ${command}`;
    }
  }

  public getAbi(): string {
    // TODO: add proper function to pass correct abi
    return "armv7";
  }

  protected assertSupported(): void {
    // Not implemented
  }

  stop(): void {
    // Nothing to do
  }

  public getDeviceProfilerPath(): string {
    return this.isDeviceInDevMode && this.bundleId
      ? `/tmp/scratch/${this.bundleId}/${CppProfilerName}`
      : `/tmp/${CppProfilerName}`;
  }

  public getDeviceProfilerSandboxedPath(): string {
    return this.isDeviceInDevMode ? `/scratch/${CppProfilerName}` : this.getDeviceProfilerPath();
  }
}
