import * as vscode from "vscode";
import * as path from "path";
import * as os from "os";
import type { LanguageClient } from "vscode-languageclient/node";
import { State as LanguageClientState } from "vscode-languageclient/node";
import { Logger } from "../utils";
import type { AnalysisService, NavigationService } from "../services";
import { Disposable } from "../utils/dispose";
import { GlobalStateKey, type GlobalState } from "../globalState";
import type {
  OutgoingWebviewMessage,
  QueueTaskItem,
  ViewPatchMessage,
} from "@codeflash/types";

interface OptimizationSuggestion {
  functionName: string;
  line: number;
  character: number;
  issues: string[];
  potentialImprovements: string[];
  severity: "info" | "warning" | "suggestion";
}
const CODELENS_PREFIX = "";
export class CodeflashCodeLensProvider
  extends Disposable
  implements vscode.CodeLensProvider
{
  private readonly logger = new Logger("Codeflash CodeLens");
  private _onDidChangeCodeLenses = new vscode.EventEmitter<void>();
  private _cachedSuggestions = new Map<string, OptimizationSuggestion[]>();
  private refreshTimeout?: NodeJS.Timeout;

  readonly onDidChangeCodeLenses: vscode.Event<void> =
    this._onDidChangeCodeLenses.event;

  constructor(
    private readonly lspClient: LanguageClient,
    private readonly analysisService: AnalysisService,
    private readonly navigationService: NavigationService,
    private readonly globalState: GlobalState,
  ) {
    super();

    // Refresh CodeLenses when LSP state changes
    this._disposables.push(
      this.lspClient.onDidChangeState((e) => {
        if (e.newState === LanguageClientState.Running) {
          this.scheduleRefresh(500);
        }
      }),

      vscode.workspace.onDidChangeTextDocument((e) => {
        if (e.document.languageId === "python") {
          this.scheduleRefresh(500);
        }
      }),
      vscode.window.onDidChangeActiveTextEditor((editor) => {
        if (editor && editor.document.languageId === "python") {
          this.scheduleRefresh(500);
        }
      }),
      vscode.window.onDidChangeTextEditorVisibleRanges((e) => {
        if (e.textEditor.document.languageId === "python") {
          this.scheduleRefresh(500);
        }
      }),
    );
  }

  async provideCodeLenses(
    document: vscode.TextDocument,
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    _token: vscode.CancellationToken,
  ): Promise<vscode.CodeLens[]> {
    if (document.languageId !== "python") {
      return [];
    }
    // codelens can't be generated until the lsp server optimizer is initialized, which is after the lsp validate the user api key.
    if (!this.globalState.get(GlobalStateKey.UserID)) {
      return [];
    }

    // don't provide codelens until user has tried the demo optimization once
    if (!this.globalState.get(GlobalStateKey.TriedCodeflashDemo, false)) {
      return [];
    }

    if (this.lspClient.state !== LanguageClientState.Running) {
      this.logger.warn(
        `CodeLens requested but LSP client not running. State: ${this.lspClient.state}`,
      );
      return [];
    }

    // check if it's inside the module root or not
    const moduleRootAbs = this.globalState.get(GlobalStateKey.ModuleRoot, "")!;
    if (!moduleRootAbs) {
      return [];
    }
    
    // Normalize paths for cross-platform compatibility
    // Use path.normalize to handle different separators and resolve relative paths
    let documentPath = path.normalize(document.uri.fsPath);
    let moduleRootPath = path.normalize(moduleRootAbs);
    
    // On Windows, file system is case-insensitive, so use case-insensitive comparison
    // On Linux/Mac, file system is case-sensitive, so use case-sensitive comparison
    const isWindows = os.platform() === "win32";
    if (isWindows) {
      documentPath = documentPath.toLowerCase();
      moduleRootPath = moduleRootPath.toLowerCase();
    }
    
    // Ensure module root path ends with a separator for proper comparison
    // This prevents false matches (e.g., /foo/bar matching /foo/barbaz)
    const normalizedModuleRoot = moduleRootPath.endsWith(path.sep)
      ? moduleRootPath
      : moduleRootPath + path.sep;
    
    if (!documentPath.startsWith(normalizedModuleRoot)) {
      return [];
    }

    try {
      // Get optimizable functions for this document
      const result = await this.analysisService.getOptimizableFunctions(
        document.uri,
      );

      if (
        result.status !== "success" ||
        !result.functions ||
        result.functions.length === 0
      ) {
        this.logger.warn(
          `No optimizable functions found for ${document.uri.fsPath} : ${result.message}`,
        );
        return [];
      }

      const codeLenses: vscode.CodeLens[] = [];

      // Create CodeLenses for optimizable functions
      for (const functionName of result.functions) {
        // Find function position in document
        const pos = await this.navigationService.manuallyFindTheFunctionInFile(
          functionName,
          document.uri,
        );
        if (!pos) {
          continue;
        }
        const functionPosition = { line: pos.line, character: pos.character };

        const range = new vscode.Range(
          functionPosition.line,
          functionPosition.character,
          functionPosition.line,
          functionPosition.character + functionName.length,
        );

        const optimizeCodeLens = new vscode.CodeLens(range);

        const existingTask = this.globalState
          .get(GlobalStateKey.QueueTasks, [])
          ?.find(
            (task) =>
              task.filepath === document.uri.fsPath &&
              task.functionName === functionName,
          );

        optimizeCodeLens.command = this.buildCodelensCommand(
          functionName,
          document.uri,
          existingTask,
        );

        codeLenses.push(optimizeCodeLens);
      }

      this.logger.debug(
        `Provided ${codeLenses.length} CodeLenses for ${document.fileName}`,
      );
      return codeLenses;
    } catch (error) {
      const errorMessage =
        error instanceof Error ? error.message : String(error);
      this.logger.error(
        "Error providing CodeLenses",
        error instanceof Error ? error : undefined,
      );

      // Categorize errors for better user feedback
      if (
        errorMessage.includes("timeout") ||
        errorMessage.includes("TIMEOUT")
      ) {
        vscode.window
          .showWarningMessage(
            "Codeflash analysis timed out. Try refreshing or check if the file is too large.",
            "Refresh Analysis",
          )
          .then((action) => {
            if (action === "Refresh Analysis") {
              this.scheduleRefresh();
            }
          });
      } else if (
        errorMessage.includes("connection") ||
        errorMessage.includes("socket")
      ) {
        vscode.window
          .showErrorMessage(
            "Lost connection to Codeflash Language Server.",
            "Restart Extension",
          )
          .then((action) => {
            if (action === "Restart Extension") {
              vscode.commands.executeCommand("workbench.action.reloadWindow");
            }
          });
      } else {
        // Generic error - only show to developer/debug mode
        this.logger.warn(`CodeLens analysis failed: ${errorMessage}`);
      }

      return [];
    }
  }

  public getSuggestions(uri: vscode.Uri): OptimizationSuggestion[] {
    return this._cachedSuggestions.get(uri.toString()) || [];
  }

  public scheduleRefresh(delay: number = 500): void {
    this._cachedSuggestions.clear();

    clearTimeout(this.refreshTimeout);
    this.refreshTimeout = setTimeout(() => {
      this._onDidChangeCodeLenses.fire();
    }, delay);
  }

  public override dispose(): void {
    super.dispose();
    this.logger.debug("Disposing CodeflashCodeLensProvider");
    this._onDidChangeCodeLenses.dispose();
    this.logger.dispose();
  }

  private buildCodelensCommand(
    functionName: string,
    path: vscode.Uri,
    task: QueueTaskItem | undefined,
  ): vscode.Command {
    let command: vscode.Command = {
      title: "Optimize",
      command: "codeflash.optimizeFunction",
      arguments: [path, functionName],
      tooltip: `Optimize ${functionName}\n\nClick to analyze and optimize this function`,
    };

    if (task?.status === "completed") {
      const payload: ViewPatchMessage["payload"] = {
        id: task.id,
        functionName: task.functionName,
        patchFile: task.patchFile!,
        explanation: task.explanation || "",
        speedupStr: task.speedupStr || "",
        optimizationReview: task.optimizationReview || "",
        originalRuntime: task.originalRuntime || 0,
        optimizedRuntime: task.optimizedRuntime || 0,
        originalLineProfiler: task.originalLineProfiler || "",
        optimizedLineProfiler: task.optimizedLineProfiler || "",
      };

      command = {
        title: "View Optimization",
        command: "codeflash.viewPatch",
        arguments: [payload],
        tooltip: `View optimization result for ${functionName}`,
      };
    } else if (task?.status === "optimizing") {
      const data: OutgoingWebviewMessage = {
        type: "setActiveSidebarTab",
        payload: {
          tab: "optimization",
        },
      };
      command = {
        title: "optimizing",
        command: "codeflash.sendMessage",
        arguments: [data],
        tooltip: `Optimization is currently running for function "${functionName}"`,
      };
    } else if (task?.status === "queued") {
      const data: OutgoingWebviewMessage = {
        type: "setActiveSidebarTab",
        payload: {
          tab: "tasks",
        },
      };
      command = {
        title: "queued",
        command: "codeflash.sendMessage",
        arguments: [data],
        tooltip: `Function "${functionName}" is queued for optimization`,
      };
    }

    command.title = `${CODELENS_PREFIX}${command.title}`;

    return command;
  }
}
