import type { LanguageClient } from "vscode-languageclient/node";
import { LSP_COMMANDS } from "../constants";
import type { GlobalState } from "../globalState";
import { GlobalStateKey } from "../globalState";
import type { OptimizationResponse } from "../types";
import { Logger } from "../utils";
import { getRootWorkspaceFolder } from "../utils/rootWorkspace";
import type { CodeflashInitResult, InitStepError } from "./baseStep";
import BaseStep from "./baseStep";
import {
  getCodeflashConfiguredPyprojectNotFound,
  getCodeflashPyprojectConfigNotDefined,
  getCodeflashPyprojectConfigNotValid,
  getCodeflashPyprojectNotFound,
} from "./stepErrors";
import type { Suggestion } from "@codeflash/types";
import { State as LanguageClientState } from "vscode-languageclient/node";
import type { ConfigService } from "../services/configService";
import { ConfigKey } from "../services/configService";
import { isAbsolute, join, relative, dirname } from "path";
import { existsSync } from "fs";
import { EventTracker } from "../telemetry/posthog";
import { POSTHOG_EVENTS } from "../telemetry/events";

export class CodeflashInitStep extends BaseStep {
  logger = new Logger("CodeflashInitStep");
  configSuggestions: Record<string, Suggestion> = {};
  pyprojectPath: string | undefined;
  constructor(
    private readonly lspClient: LanguageClient,
    private readonly optimizationClient: LanguageClient,
    private readonly globalState: GlobalState,
    private readonly configService: ConfigService,
  ) {
    super("Almost there!");
  }

  private resolveValidPath(inputPath: string): string | null {
    if (!inputPath || typeof inputPath !== "string") {
      return null;
    }

    // If absolute, check existence.
    if (isAbsolute(inputPath)) {
      return existsSync(inputPath) ? inputPath : null;
    }

    const workspaceFolder = getRootWorkspaceFolder();
    if (!workspaceFolder) {
      return null;
    }
    const resolved = join(workspaceFolder.uri.fsPath, inputPath);
    this.logger.info(`Resolving relative path: ${resolved}`);
    return existsSync(resolved) ? resolved : null;
  }

  public async run(): Promise<CodeflashInitResult | InitStepError> {
    const workspaceFolder = getRootWorkspaceFolder();
    const configuredConfigFileForThisWorkspace = this.configService.get(
      ConfigKey.configFilePath,
    ) as string | null;
    let config_file: string | null = null;
    if (configuredConfigFileForThisWorkspace) {
      config_file = this.resolveValidPath(configuredConfigFileForThisWorkspace);
      if (!config_file) {
        return getCodeflashConfiguredPyprojectNotFound(
          configuredConfigFileForThisWorkspace,
        );
      }
    }
    const { status, moduleRoot, message, pyprojectPath, root, existingConfig } =
      await this.lspClient.sendRequest<{
        status: string;
        message?: string;
        moduleRoot?: string;
        root?: string;
        pyprojectPath: string;
        existingConfig?: Record<string, unknown>; // raw parsed config from pyproject.toml, this means the paths like module root are not resolved yet.
      }>(LSP_COMMANDS.INIT_PROJECT, {
        root_path_abs: workspaceFolder?.uri?.fsPath,
        config_file,
      });

    this.globalState.set(GlobalStateKey.RootDir, root);
    this.globalState.set(GlobalStateKey.ModuleRoot, moduleRoot);
    this.pyprojectPath = pyprojectPath;

    try {
      await this.loadConfigSuggestions(existingConfig);
    } catch (e) {
      this.logger.error(`Failed to load config suggestions: ${e}`);
    }

    if (status !== "success") {
      const configFoundButNotValid =
        message?.toLowerCase()?.startsWith("reason:") || false;
      if (configFoundButNotValid && !!pyprojectPath) {
        const cleanMessage = message ? message.slice("reason:".length) : "";
        return getCodeflashPyprojectConfigNotValid(pyprojectPath, cleanMessage);
      } else if (!!pyprojectPath) {
        return getCodeflashPyprojectConfigNotDefined(pyprojectPath);
      } else {
        // no pyproject found
        if (config_file) {
          return getCodeflashConfiguredPyprojectNotFound(config_file);
        }
        return getCodeflashPyprojectNotFound();
      }
    }

    const [userId] = await Promise.all([
      this.checkApiKey(),
      this.optimizationClient.sendRequest(LSP_COMMANDS.INIT_PROJECT, {
        config_file: pyprojectPath,
        root_path_abs: workspaceFolder,
        skip_validation: true, // was already validated by the main client
      }),
    ]);

    return {
      userId,
      pyprojectPath,
      configSuggestions: this.configSuggestions,
    };
  }

  private getRelativePathIfAbsolute(path: string): string {
    // If path is not absolute, return as-is
    if (!isAbsolute(path)) {
      return path;
    }

    // Use pyproject.toml directory as the base for relative paths
    // If pyprojectPath is not set, fall back to workspace root
    let basePath: string;
    if (this.pyprojectPath) {
      basePath = dirname(this.pyprojectPath);
    } else {
      const workspaceFolder = getRootWorkspaceFolder();
      if (!workspaceFolder) {
        return path;
      }
      basePath = workspaceFolder.uri.fsPath;
    }

    try {
      // path.relative() handles cross-platform paths correctly
      const relativePath = relative(basePath, path);
      // Normalize separators to forward slashes for consistency
      const normalized = relativePath.replace(/\\/g, "/");
      return normalized === "" ? "." : normalized;
    } catch {
      // If relative() fails (e.g., different drives on Windows), return original
      return path;
    }
  }

  private loadConfigSuggestions = async (
    existingConfig?: Record<string, unknown>,
  ): Promise<void> => {
    if (this.lspClient.state !== LanguageClientState.Running) {
      return;
    }
    const paths_key = ["module_root", "tests_root"];
    const suggestions = await this.lspClient.sendRequest<
      Record<string, Suggestion>
    >(LSP_COMMANDS.GET_CONFIG_SUGGESTIONS, {});
    if (existingConfig) {
      // Keep existing config values as default suggestions
      for (const [key, value] of Object.entries(existingConfig)) {
        const suggestion = suggestions[key];
        if (!suggestion) {
          continue;
        }
        let existingValue = String(value);

        if (key === "formatter_cmds" && Array.isArray(value)) {
          existingValue = value.join(" && ");
        }

        if (paths_key.includes(key)) {
          existingValue = this.getRelativePathIfAbsolute(existingValue);
        }

        suggestion.default = existingValue;
      }
    }

    this.configSuggestions = suggestions;
  };

  private checkApiKey = async (): Promise<string | undefined> => {
    if (!this.lspClient) {
      return undefined;
    }

    const result = await this.lspClient.sendRequest<OptimizationResponse>(
      LSP_COMMANDS.CHECK_API_KEY,
      {},
    );

    if (result.status === "error") {
      this.logger.error(`Invalid API key, message: ${result.message}`);
      return undefined;
    } else if (result.status === "success") {
      const userId = result.user_id as string;
      this.globalState.set(GlobalStateKey.UserID, userId);
      EventTracker.setUserId(userId);
      await EventTracker.capture(POSTHOG_EVENTS.USER_LOGGED_IN, {});
      this.logger.info(`user id: ${result.user_id}`);
      return result.user_id as string;
    }

    return undefined;
  };
}
