import type { CheckEnvironmentStepResult } from "./baseStep";
import { InitStepError } from "./baseStep";
import BaseStep from "./baseStep";
import * as vscode from "vscode";
import { type GitExtension } from "../types/git";
import { getRootWorkspaceFolder } from "../utils/rootWorkspace";
import { PythonService } from "../services";
import { exec } from "child_process";
import { promisify } from "util";
import { Logger } from "../utils";
import {
  getBareRepoError,
  getCodeflashNotInstalledError,
  getCodeflashVersionIncompatibleError,
  getCodeflashVersionNotValidError,
  getIncorrectPythonInterpreterPath,
  getNoGitRepoError,
  getNoInitCommitError,
  getPythonNotSelectedError,
  GIT_ERRORS,
} from "./stepErrors";
import semver from "semver";
import { MIN_CODEFLASH_VERSION } from "../constants/cf_min_version";
import { EventTracker } from "../telemetry/posthog";
import { POSTHOG_EVENTS } from "../telemetry/events";
import { ConfigKey, type ConfigService } from "../services/configService";

const execPromise = promisify(exec);

export class CheckEnvironmentStep extends BaseStep {
  gitExtension =
    vscode.extensions.getExtension<GitExtension>("vscode.git")?.exports!;
  logger = new Logger("CheckEnvironmentStep");
  pythonService: PythonService | undefined = undefined;
  configuredPythonPath: string | undefined = undefined;

  private pythonPath?: string;

  get pythonInterpreter(): string | undefined {
    return this.pythonPath;
  }

  constructor(configService: ConfigService) {
    super("Checking your environment");
    this.configuredPythonPath = configService.get(
      ConfigKey.DefaultInterpreterPath,
    ) as string;
    this.pythonService = new PythonService(configService);
  }

  public override async run(
    ...args: unknown[]
  ): Promise<InitStepError | CheckEnvironmentStepResult> {
    const [] = args;
    const result: CheckEnvironmentStepResult = {
      pythonPath: "",
      codeflashVersion: "unknown",
    };

    const { pythonPath } = await this.validatePythonEnvironment();
    if (pythonPath === "") {
      return getPythonNotSelectedError();
    }

    result.pythonPath = pythonPath;

    const [gitError, installationCheckResult] = await Promise.all([
      this.isGitUsedInRepo(),
      this.checkCodeflashInstallation(pythonPath),
    ]);

    if (installationCheckResult instanceof InitStepError) {
      return installationCheckResult;
    } else {
      result.codeflashVersion = installationCheckResult.version;
    }

    if (gitError) {
      const message = gitError.message as keyof typeof GIT_ERRORS;
      if (message === GIT_ERRORS.NO_REPO) {
        return getNoGitRepoError();
      } else if (message === GIT_ERRORS.BARE_REPO) {
        return getBareRepoError();
      } else if (message === GIT_ERRORS.NO_INIT_COMMIT) {
        return getNoInitCommitError();
      }
    }

    await EventTracker.capture(POSTHOG_EVENTS.ENVIRONMENT_CHECK_COMPLETED, {});
    return result;
  }

  private isGitUsedInRepo = async (): Promise<Error | undefined> => {
    const workspaceFolder = getRootWorkspaceFolder();
    if (!workspaceFolder) {
      return new Error("No workspace folder found");
    }

    const cwd = workspaceFolder.uri.fsPath;

    try {
      // Combine all three git checks into a single command
      // This reduces overhead from spawning multiple processes
      const { stdout } = await execPromise(
        "git rev-parse --is-inside-work-tree --is-bare-repository HEAD",
        { cwd },
      );

      const lines = stdout.trim().split("\n");

      // lines[0] = "true" (is-inside-work-tree)
      // lines[1] = "true" or "false" (is-bare-repository)
      // lines[2] = commit hash

      if (lines.length < 2) {
        return new Error(GIT_ERRORS.NO_REPO);
      }

      const isBare = lines[1] === "true";
      if (isBare) {
        return new Error(GIT_ERRORS.BARE_REPO);
      }

      // Check if HEAD exists (lines[2] should be a commit hash)
      if (lines.length < 3 || !lines[2]) {
        return new Error(GIT_ERRORS.NO_INIT_COMMIT);
      }

      return undefined;
    } catch (error) {
      this.logger.error(
        "Error checking for git repository",
        error instanceof Error ? error : undefined,
      );
      // Check error message to determine specific git error
      const message = error instanceof Error ? error.message : String(error);
      if (
        message.includes("unknown revision") ||
        message.includes("bad revision")
      ) {
        return new Error(GIT_ERRORS.NO_INIT_COMMIT);
      }
      return new Error(GIT_ERRORS.NO_REPO);
    }
  };

  private validatePythonEnvironment = async (): Promise<{
    pythonPath: string;
  }> => {
    const workspaceFolder = getRootWorkspaceFolder();

    if (!workspaceFolder) {
      return {
        pythonPath: "",
      };
    }

    if (this.configuredPythonPath) {
      this.logger.debug(
        `Using configured Python interpreter: ${this.configuredPythonPath}`,
      );
      this.pythonPath = this.configuredPythonPath;
      return {
        pythonPath: this.configuredPythonPath,
      };
    }
    // fallback to python service to get the active interpreter using the Python extension
    const { errorConfig, pythonPath } =
      await this.pythonService!.validateDependencies();

    if (errorConfig) {
      return {
        pythonPath: "",
      };
    }

    return {
      pythonPath: pythonPath!,
    };
  };

  private checkCodeflashInstallation = async (
    pythonPath: string,
  ): Promise<InitStepError | { version: string }> => {
    const packageManager = this.pythonService!.getPackageManager();
    const installCmd = this.pythonService!.getInstallCommand(
      pythonPath,
      packageManager,
      false,
    );
    const upgradeCmd = this.pythonService!.getInstallCommand(
      pythonPath,
      packageManager,
      true,
    );

    const cmd = `${pythonPath} -c "from codeflash.version import __version__ as v; print(v)"`;
    try {
      const { stdout } = await execPromise(cmd);
      const version = stdout.trim();
      const semVer = semver.valid(version) || semver.coerce(version);
      if (!semVer) {
        return getCodeflashVersionNotValidError(pythonPath, upgradeCmd);
      }
      if (semver.lt(semVer, MIN_CODEFLASH_VERSION)) {
        return getCodeflashVersionIncompatibleError(
          version,
          pythonPath,
          true,
          upgradeCmd,
        );
      }
      return { version };
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      this.logger.debug("Error checking for codeflash installation " + message);
      if (
        message.includes("PackageNotFoundError") ||
        message.includes("ModuleNotFoundError")
      ) {
        return getCodeflashNotInstalledError(pythonPath, installCmd);
      }
      if (message.toLocaleLowerCase().includes("no such file or directory")) {
        return getIncorrectPythonInterpreterPath(pythonPath);
      }
      this.logger.error(
        "Unexpected error checking for codeflash installation",
        error instanceof Error ? error : undefined,
      );
      // for other unexpected errors, we don't block the user
      return { version: "unknown" };
    }
  };
}
