import * as plugins from './smartnetwork.plugins.js';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';

/**
 * Command map for the rustnetwork IPC binary.
 * Each key maps to { params, result } defining the typed IPC protocol.
 */
type TNetworkCommands = {
  healthPing: {
    params: Record<string, never>;
    result: { pong: boolean };
  };
  ping: {
    params: { host: string; count?: number; timeoutMs?: number };
    result: {
      alive: boolean;
      times: (number | null)[];
      min: number | null;
      max: number | null;
      avg: number | null;
      stddev: number | null;
      packetLoss: number;
    };
  };
  traceroute: {
    params: { host: string; maxHops?: number; timeoutMs?: number };
    result: {
      hops: Array<{ ttl: number; ip: string; rtt: number | null }>;
    };
  };
  tcpPortCheck: {
    params: { host: string; port: number; timeoutMs?: number };
    result: { isOpen: boolean; latencyMs: number | null };
  };
  isLocalPortFree: {
    params: { port: number };
    result: { free: boolean };
  };
  defaultGateway: {
    params: Record<string, never>;
    result: {
      interfaceName: string;
      addresses: Array<{ family: string; address: string }>;
    };
  };
};

function getPlatformSuffix(): string | null {
  const platform = process.platform;
  const arch = process.arch;

  const platformMap: Record<string, string> = {
    linux: 'linux',
    darwin: 'macos',
    win32: 'windows',
  };

  const archMap: Record<string, string> = {
    x64: 'amd64',
    arm64: 'arm64',
  };

  const p = platformMap[platform];
  const a = archMap[arch];

  if (p && a) {
    return `${p}_${a}`;
  }
  return null;
}

/**
 * Singleton bridge to the rustnetwork binary.
 * Manages the IPC lifecycle for network diagnostics operations.
 */
export class RustNetworkBridge {
  private static instance: RustNetworkBridge | null = null;

  public static getInstance(): RustNetworkBridge {
    if (!RustNetworkBridge.instance) {
      RustNetworkBridge.instance = new RustNetworkBridge();
    }
    return RustNetworkBridge.instance;
  }

  private bridge: InstanceType<typeof plugins.smartrust.RustBridge<TNetworkCommands>>;

  private constructor() {
    const packageDir = path.resolve(
      path.dirname(fileURLToPath(import.meta.url)),
      '..',
    );

    const platformSuffix = getPlatformSuffix();
    const localPaths: string[] = [];

    // Platform-specific cross-compiled binary
    if (platformSuffix) {
      localPaths.push(path.join(packageDir, 'dist_rust', `rustnetwork_${platformSuffix}`));
    }
    // Native build without suffix
    localPaths.push(path.join(packageDir, 'dist_rust', 'rustnetwork'));
    // Local dev paths
    localPaths.push(path.join(packageDir, 'rust', 'target', 'release', 'rustnetwork'));
    localPaths.push(path.join(packageDir, 'rust', 'target', 'debug', 'rustnetwork'));

    this.bridge = new plugins.smartrust.RustBridge<TNetworkCommands>({
      binaryName: 'rustnetwork',
      cliArgs: ['--management'],
      requestTimeoutMs: 30_000,
      readyTimeoutMs: 10_000,
      localPaths,
      searchSystemPath: false,
    });
  }

  /**
   * Spawn the Rust binary and wait for it to be ready.
   */
  public async start(): Promise<void> {
    const ok = await this.bridge.spawn();
    if (!ok) {
      throw new Error('Failed to spawn rustnetwork binary');
    }
  }

  /**
   * Kill the Rust binary.
   */
  public async stop(): Promise<void> {
    this.bridge.kill();
  }

  /**
   * Ensure the bridge is running before sending a command.
   */
  private async ensureRunning(): Promise<void> {
    // The bridge will throw if not spawned — we just call start() if not yet running
  }

  // ===== Command wrappers =====

  public async ping(
    host: string,
    count?: number,
    timeoutMs?: number,
  ): Promise<TNetworkCommands['ping']['result']> {
    return this.bridge.sendCommand('ping', { host, count, timeoutMs });
  }

  public async traceroute(
    host: string,
    maxHops?: number,
    timeoutMs?: number,
  ): Promise<TNetworkCommands['traceroute']['result']> {
    return this.bridge.sendCommand('traceroute', { host, maxHops, timeoutMs });
  }

  public async tcpPortCheck(
    host: string,
    port: number,
    timeoutMs?: number,
  ): Promise<TNetworkCommands['tcpPortCheck']['result']> {
    return this.bridge.sendCommand('tcpPortCheck', { host, port, timeoutMs });
  }

  public async isLocalPortFree(
    port: number,
  ): Promise<TNetworkCommands['isLocalPortFree']['result']> {
    return this.bridge.sendCommand('isLocalPortFree', { port });
  }

  public async defaultGateway(): Promise<TNetworkCommands['defaultGateway']['result']> {
    return this.bridge.sendCommand('defaultGateway', {} as Record<string, never>);
  }

  public async healthPing(): Promise<TNetworkCommands['healthPing']['result']> {
    return this.bridge.sendCommand('healthPing', {} as Record<string, never>);
  }
}
