import * as fs from 'node:fs';
import * as path from 'node:path';

import type { ILoadedMatrixPackageEnvironment } from './package-environment.js';

export interface IResolvedRunnerTransportPlan {
  readonly envName: string;
  readonly root: string;
  readonly mode: 'embedded' | 'external';
  readonly url: string;
  readonly wsUrl?: string;
  readonly credentialsRef?: string;
  readonly port?: number;
  readonly wsPort?: number;
  readonly binaryPath?: string;
  readonly dataDir?: string;
  readonly pidFile?: string;
}

export function resolveNatsBinary(baseDir: string, explicitBinaryPath?: string): string {
  const binaryOverride = readOptionalString(explicitBinaryPath);
  if (binaryOverride) {
    return path.resolve(baseDir, binaryOverride);
  }

  const envBinary = readOptionalString(process.env.MATRIX_NATS_SERVER_BINARY)
    ?? readOptionalString(process.env.NATS_SERVER_BINARY);
  if (envBinary) {
    return path.resolve(envBinary);
  }

  const binaryName = process.platform === 'win32' ? 'nats-server.exe' : 'nats-server';
  const searchRoots = new Set<string>([
    baseDir,
    path.resolve(baseDir, '..'),
    path.resolve(baseDir, '..', '..'),
    path.resolve(baseDir, '..', '..', '..'),
    path.resolve(baseDir, '..', '..', '..', '..'),
  ]);

  for (const root of searchRoots) {
    const candidates = [
      path.resolve(root, 'projects', 'matrix-3', 'packages', 'daemon', 'dist', 'bin', binaryName),
      path.resolve(root, 'projects', 'nats-server', binaryName),
      path.resolve(root, 'packages', 'daemon', 'dist', 'bin', binaryName),
      path.resolve(root, 'nats-server', binaryName),
    ];

    for (const candidate of candidates) {
      if (pathExists(candidate)) {
        return candidate;
      }
    }
  }

  const pathEntries = (process.env.PATH ?? '')
    .split(path.delimiter)
    .filter((entry) => entry.length > 0);
  for (const entry of pathEntries) {
    const candidate = path.join(entry, binaryName);
    if (pathExists(candidate)) {
      return candidate;
    }
  }

  return binaryName;
}

export function resolveRunnerTransportPlan(
  packageDir: string,
  selectedEnvironment: ILoadedMatrixPackageEnvironment,
  rootOverride?: string,
): IResolvedRunnerTransportPlan {
  const nats = selectedEnvironment.environment.nats ?? {};
  const root = readOptionalString(rootOverride) ?? selectedEnvironment.environment.runtime.root;
  const mode = inferMode(nats.mode, nats.url);

  if (mode === 'external') {
    const url = readOptionalString(nats.url);
    if (!url) {
      throw new Error(
        `Package environment "${selectedEnvironment.envName}" requires nats.url when nats.mode is external`,
      );
    }
    const wsUrl = readOptionalString(nats.wsUrl);
    const credentialsRef = readOptionalString(nats.credentialsRef);
    return {
      envName: selectedEnvironment.envName,
      root,
      mode: 'external',
      url,
      ...(wsUrl ? { wsUrl } : {}),
      ...(credentialsRef ? { credentialsRef } : {}),
    };
  }

  const port = readOptionalNumber(nats.port) ?? 4222;
  const wsPort = readOptionalNumber(nats.wsPort);
  const url = readOptionalString(nats.url) ?? `nats://127.0.0.1:${port}`;
  const wsUrl = readOptionalString(nats.wsUrl) ?? (wsPort ? `ws://127.0.0.1:${wsPort}` : undefined);
  const dataDir = path.resolve(
    packageDir,
    readOptionalString(nats.dataDir) ?? path.join('.matrix', 'state', selectedEnvironment.envName, 'nats'),
  );
  const pidFile = path.resolve(
    packageDir,
    readOptionalString(nats.pidFile) ?? path.join('.matrix', 'state', selectedEnvironment.envName, 'nats-server.pid'),
  );
  const binaryPath = resolveNatsBinary(packageDir, readOptionalString(nats.binaryPath));

  return {
    envName: selectedEnvironment.envName,
    root,
    mode: 'embedded',
    url,
    port,
    ...(wsUrl ? { wsUrl } : {}),
    ...(wsPort !== undefined ? { wsPort } : {}),
    binaryPath,
    dataDir,
    pidFile,
  };
}

function inferMode(mode: string | undefined, url: string | undefined): 'embedded' | 'external' {
  const normalizedMode = readOptionalString(mode)?.toLowerCase();
  if (normalizedMode === 'external' || readOptionalString(url)) {
    return 'external';
  }
  return 'embedded';
}

function readOptionalString(value: unknown): string | undefined {
  if (typeof value !== 'string') {
    return undefined;
  }
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : undefined;
}

function readOptionalNumber(value: unknown): number | undefined {
  if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
    return undefined;
  }
  return value;
}

function pathExists(filePath: string): boolean {
  return fs.existsSync(filePath);
}
