import * as NodeProcess from "node:process";

import * as semver from "@std/semver";
import { Effect, Option } from "effect";

import { Config } from "../config/Config.ts";
import { MINIMUM_MARIMO_KERNEL_VERSION } from "../constants.ts";
import { NotebookRuntime } from "../kernel/NotebookRuntime.ts";
import { BinarySource } from "../lib/binaryResolution.ts";
import { getExtensionVersion } from "../lib/getExtensionVersion.ts";
import { MarimoClient, type MarimoLspMode } from "../lsp/MarimoClient.ts";
import {
  RuffLanguageServer,
  RuffLanguageServerStatus,
} from "../lsp/RuffLanguageServer.ts";
import {
  TyLanguageServer,
  TyLanguageServerStatus,
} from "../lsp/TyLanguageServer.ts";
import { VsCode } from "../platform/VsCode.ts";
import { PythonExtension } from "../python/PythonExtension.ts";
import { Uv, UvBin } from "../python/Uv.ts";

/**
 * Provides health check and diagnostic information for the marimo extension.
 */
export class HealthService extends Effect.Service<HealthService>()(
  "HealthService",
  {
    dependencies: [Uv.Default],
    scoped: Effect.gen(function* () {
      const uv = yield* Uv;
      const code = yield* VsCode;
      const config = yield* Config;
      const marimo = yield* MarimoClient;
      const notebooks = yield* NotebookRuntime;
      const pyExt = yield* PythonExtension;
      const tyLsp = yield* TyLanguageServer;
      const ruffLsp = yield* RuffLanguageServer;

      const formatDiagnostics = () =>
        Effect.gen(function* () {
          const [lspCustomPath, uvDisabled, extVersion] = yield* Effect.all([
            config.lsp.executable,
            Effect.map(config.uv.enabled, (enabled) => !enabled),
            getExtensionVersion().pipe(Effect.provideService(VsCode, code)),
          ]);

          const lines: string[] = [];

          // Header
          lines.push("marimo VS Code Extension Diagnostics");
          lines.push("=====================================");
          lines.push("");

          // LSP Status
          lines.push("Language Server (LSP):");
          const uvBin =
            marimo.mode === "uv" ? Option.some(yield* uv.bin) : Option.none();
          lines.push(
            ...formatMarimoLspDiagnostics({
              mode: marimo.mode,
              customExecutable: lspCustomPath,
              uvBin,
            }),
          );

          lines.push("");

          // Python Extension Environment
          lines.push("Python Extension:");
          const pyEnvPath = yield* pyExt
            .getActiveEnvironmentPath()
            .pipe(Effect.option);
          if (Option.isNone(pyEnvPath)) {
            lines.push("\tNot available (Python extension not found)");
          } else {
            const resolved = yield* pyExt
              .resolveEnvironment(pyEnvPath.value)
              .pipe(Effect.option);
            const env = Option.flatten(resolved);
            if (Option.isNone(env)) {
              lines.push(`\tInterpreter: ${pyEnvPath.value.path}`);
              lines.push("\tVersion: Unknown");
            } else {
              const e = env.value;
              lines.push(
                `\tInterpreter: ${e.executable.uri?.fsPath ?? e.path ?? "Unknown"}`,
              );
              lines.push(`\tVersion: ${e.version?.sysVersion ?? "Unknown"}`);
              if (e.environment) {
                lines.push(
                  `\tEnvironment: ${e.environment.type} (${e.environment.name})`,
                );
              }
            }
          }

          lines.push("");

          // Python Language Server (ty) - only show if managed language features enabled
          const managedLanguageFeaturesEnabled =
            yield* config.getManagedLanguageFeaturesEnabled();

          if (managedLanguageFeaturesEnabled) {
            lines.push("Python Language Server (ty):");

            TyLanguageServerStatus.$match(yield* tyLsp.getHealthStatus(), {
              Disabled: ({ reason }) => {
                lines.push("\tStatus: disabled");
                lines.push(`\tReason: ${reason}`);
              },
              Starting: () => {
                lines.push("\tStatus: starting...");
              },
              Running: ({ serverVersion, binarySource, pythonEnvironment }) => {
                lines.push("\tStatus: running ✓");
                lines.push(`\tVersion: ${serverVersion}`);
                lines.push(`\tBinary: ${formatBinarySource(binarySource)}`);
                if (Option.isSome(pythonEnvironment)) {
                  const pyPath = pythonEnvironment.value.path;
                  const pyVersion = pythonEnvironment.value.version
                    ? ` (${pythonEnvironment.value.version})`
                    : "";
                  lines.push(`\tPython: ${pyPath}${pyVersion}`);
                }
              },
              Failed: ({ message }) => {
                lines.push("\tStatus: failed ✗");
                lines.push(`\tError: ${message}`);
              },
            });

            lines.push("");

            // Ruff Language Server
            lines.push("Ruff Language Server:");
            RuffLanguageServerStatus.$match(yield* ruffLsp.getHealthStatus(), {
              Disabled: ({ reason }) => {
                lines.push("\tStatus: disabled");
                lines.push(`\tReason: ${reason}`);
              },
              Starting: () => {
                lines.push("\tStatus: starting...");
              },
              Running: ({ serverVersion, binarySource }) => {
                lines.push("\tStatus: running ✓");
                lines.push(`\tVersion: ${serverVersion}`);
                lines.push(`\tBinary: ${formatBinarySource(binarySource)}`);
              },
              Failed: ({ message }) => {
                lines.push("\tStatus: failed ✗");
                lines.push(`\tError: ${message}`);
              },
            });

            lines.push("");
          }

          // Extension Configuration
          lines.push("Extension Configuration:");
          lines.push(
            `\tVersion: ${Option.getOrElse(extVersion, () => "unknown")} `,
          );
          lines.push(`\tUV integration disabled: ${uvDisabled} `);
          const activeEditor = yield* code.window.getActiveNotebookEditor();
          const configuredRoot = yield* config.notebookFileRoot(
            Option.isSome(activeEditor)
              ? activeEditor.value.notebook.uri
              : undefined,
          );
          const runtimeSession = yield* notebooks.activeRuntimeSession();
          lines.push(
            ...formatNotebookFileRootDiagnostics(
              configuredRoot,
              Option.map(
                runtimeSession,
                ({ workingDirectory }) => workingDirectory,
              ),
            ),
          );

          lines.push("");

          // System Information
          lines.push("System Information:");
          lines.push(`\tHost: ${code.env.appHost} `);
          lines.push(`\tIDE: ${code.env.appName} `);
          lines.push(`\tIDE version: ${code.version} `);
          lines.push(`\tPlatform: ${NodeProcess.platform} `);
          lines.push(`\tArchitecture: ${NodeProcess.arch} `);
          lines.push(`\tNode version: ${NodeProcess.version} `);
          lines.push("");

          if (Option.isSome(uvBin) && UvBin.$is("Default")(uvBin.value)) {
            // If using default UV (i.e., "uv"), show PATH for debugging

            // PATH (formatted for readability)
            lines.push("PATH:");
            const pathValue = NodeProcess.env.PATH;
            if (pathValue) {
              const separator = NodeProcess.platform === "win32" ? ";" : ":";
              for (const entry of pathValue.split(separator)) {
                lines.push(`\t${entry} `);
              }
            } else {
              lines.push("\t(not set)");
            }
            lines.push("");
          }

          // Troubleshooting
          lines.push("Common Issues:");
          lines.push("\t1. If notebooks won't open:");
          lines.push("\t\t- Check Python interpreter is selected");
          lines.push("\t\t- Ensure marimo is installed");
          lines.push("\t\t- Check 'marimo-lsp' output channel for errors");
          lines.push("\t2. If features are missing:");
          lines.push(
            `\t\t - Ensure marimo version is >= ${semver.format(MINIMUM_MARIMO_KERNEL_VERSION)}`,
          );
          lines.push("\t\t- Try reloading the window");

          return lines.join("\n");
        });

      return {
        /**
         * Shows a text document with comprehensive diagnostics about the extension
         * and environment setup.
         */
        showDiagnostics: Effect.fn(function* () {
          yield* Effect.logInfo("Showing diagnostics");

          const diagnosticText = yield* formatDiagnostics();

          const doc = yield* code.workspace.openUntitledTextDocument({
            content: diagnosticText,
            language: "plaintext",
          });

          yield* code.window.showTextDocument(doc);

          return doc;
        }),
      };
    }),
  },
) {}

function formatBinarySource(source: BinarySource): string {
  return BinarySource.$match(source, {
    UserConfigured: ({ path }) => `UserConfigured (${path})`,
    CompanionExtension: ({ extensionId, path, kind }) =>
      `CompanionExtension/${kind} (${extensionId}, ${path})`,
    UvInstalled: ({ path }) => `UvInstalled (${path})`,
  });
}

export function formatMarimoLspDiagnostics({
  mode,
  customExecutable,
  uvBin,
}: {
  mode: MarimoLspMode;
  customExecutable: Option.Option<{
    readonly command: string;
    readonly args?: readonly string[];
  }>;
  uvBin: Option.Option<UvBin>;
}): readonly string[] {
  if (mode === "wasm") {
    return ["\tMode: WASM (bundled Pyodide)"];
  }
  if (mode === "configured") {
    return [
      "\tMode: Native (configured)",
      Option.match(customExecutable, {
        onNone: () => "\tCustom path: unavailable",
        onSome: ({ command, args = [] }) =>
          `\tCustom path: ${command} ${args.join(" ")}`,
      }),
    ];
  }

  return Option.match(uvBin, {
    onNone: () => ["\tMode: Native (uv)", "\tUV: Not found ✗"],
    onSome: (bin) => {
      const lines = ["\tMode: Native (uv)"];
      UvBin.$match(bin, {
        Bundled: ({ executable }) =>
          lines.push(`\tUV Bin: Bundled (${executable})`),
        Default: ({ executable }) =>
          lines.push(`\tUV Bin: Default (${executable})`),
        Configured: ({ executable }) =>
          lines.push(`\tUV Bin: Configured (${executable})`),
        Discovered: ({ executable }) =>
          lines.push(`\tUV Bin: Discovered (${executable})`),
      });
      lines.push(
        Option.match(bin.version, {
          onSome: (version) => `\tUV: ${version.format()} ✓`,
          onNone: () => "\tUV: Version unknown",
        }),
      );
      lines.push("\tUsing bundled marimo-lsp via uvx");
      return lines;
    },
  });
}

function formatNotebookFileRootDiagnostics(
  configuredRoot: string,
  workingDirectory: Option.Option<string>,
): readonly string[] {
  return [
    `\tNotebook file root: ${configuredRoot}`,
    `\tResolved kernel working directory: ${Option.getOrElse(workingDirectory, () => "No active kernel")}`,
  ];
}
