import * as plugins from './smartvpn.plugins.js';
import * as paths from './smartvpn.paths.js';
import type {
  TVpnTransportOptions,
  IVpnTransportSocket,
} from './smartvpn.interfaces.js';
import type { TCommandMap } from '@push.rocks/smartrust';

/**
 * Get the package root directory.
 */
function getPackageRoot(): string {
  const thisDir = plugins.path.dirname(plugins.url.fileURLToPath(import.meta.url));
  return plugins.path.resolve(thisDir, '..');
}

/**
 * Map Node.js platform/arch to tsrust's platform suffix.
 */
function getTsrustPlatformSuffix(): string | null {
  const archMap: Record<string, string> = { x64: 'amd64', arm64: 'arm64' };
  const osMap: Record<string, string> = { linux: 'linux', darwin: 'macos' };
  const os = osMap[process.platform];
  const arch = archMap[process.arch];
  if (os && arch) {
    return `${os}_${arch}`;
  }
  return null;
}

/**
 * Build local search paths for the smartvpn daemon binary.
 */
function buildLocalPaths(binaryName: string): string[] {
  const packageRoot = getPackageRoot();
  const suffix = getTsrustPlatformSuffix();
  const paths: string[] = [];

  // dist_rust/ (tsrust cross-compiled output)
  if (suffix) {
    paths.push(plugins.path.join(packageRoot, 'dist_rust', `${binaryName}_${suffix}`));
  }
  paths.push(plugins.path.join(packageRoot, 'dist_rust', binaryName));

  // Local dev build paths
  paths.push(plugins.path.resolve(process.cwd(), 'rust', 'target', 'release', binaryName));
  paths.push(plugins.path.resolve(process.cwd(), 'rust', 'target', 'debug', binaryName));

  return paths;
}

/**
 * Shared bridge wrapper around smartrust RustBridge.
 * Supports stdio mode (dev: spawn child process) and socket mode (production: connect to running daemon).
 */
export class VpnBridge<TCommands extends TCommandMap> extends plugins.events.EventEmitter {
  private bridge: plugins.smartrust.RustBridge<TCommands>;
  private transportOptions: TVpnTransportOptions;
  private mode: 'client' | 'server';

  constructor(options: {
    transport: TVpnTransportOptions;
    mode: 'client' | 'server';
    binaryName?: string;
  }) {
    super();

    const binaryName = options.binaryName || 'smartvpn_daemon';
    this.transportOptions = options.transport;
    this.mode = options.mode;

    this.bridge = new plugins.smartrust.RustBridge<TCommands>({
      binaryName,
      envVarName: 'SMARTVPN_RUST_BINARY',
      platformPackagePrefix: '@push.rocks/smartvpn',
      localPaths: buildLocalPaths(binaryName),
      cliArgs: ['--management', '--mode', this.mode],
      maxPayloadSize: 10 * 1024 * 1024, // 10 MB
    });

    // Forward events from inner bridge
    this.bridge.on('exit', (code: number | null, signal: string | null) => {
      this.emit('exit', code, signal);
    });
    this.bridge.on('reconnected', () => {
      this.emit('reconnected');
    });

    // Forward management events from the daemon
    // smartrust emits 'management:<eventName>' for unsolicited events
    this.bridge.on('management:status', (data: any) => {
      this.emit('status', data);
    });
    this.bridge.on('management:error', (data: any) => {
      this.emit('error', data);
    });
    this.bridge.on('management:client-connected', (data: any) => {
      this.emit('client-connected', data);
    });
    this.bridge.on('management:client-disconnected', (data: any) => {
      this.emit('client-disconnected', data);
    });
    this.bridge.on('management:started', (data: any) => {
      this.emit('started', data);
    });
    this.bridge.on('management:stopped', (data: any) => {
      this.emit('stopped', data);
    });
  }

  /**
   * Start the bridge: spawn in stdio mode or connect in socket mode.
   */
  public async start(): Promise<boolean> {
    if (this.transportOptions.transport === 'socket') {
      const socketOpts = this.transportOptions as IVpnTransportSocket;
      return this.bridge.connect(socketOpts.socketPath, {
        autoReconnect: socketOpts.autoReconnect,
        reconnectBaseDelayMs: socketOpts.reconnectBaseDelayMs,
        reconnectMaxDelayMs: socketOpts.reconnectMaxDelayMs,
        maxReconnectAttempts: socketOpts.maxReconnectAttempts,
      });
    } else {
      return this.bridge.spawn();
    }
  }

  /**
   * Stop the bridge. In socket mode, closes the socket (daemon stays alive).
   * In stdio mode, kills the child process.
   */
  public stop(): void {
    this.bridge.kill();
  }

  /**
   * Send a typed command to the daemon.
   */
  public async sendCommand<K extends string & keyof TCommands>(
    method: K,
    params: TCommands[K]['params'],
  ): Promise<TCommands[K]['result']> {
    return this.bridge.sendCommand(method, params);
  }

  /**
   * Whether the bridge is currently running/connected.
   */
  public get running(): boolean {
    return this.bridge.running;
  }
}
