import * as plugins from '../../plugins.js';
import { logger } from '../../core/utils/logger.js';
import type {
  IRustCertificateStatus,
  IRustChallengeOptions,
  IRustMetricsSnapshot,
  IRustProxyOptions,
  IRustRouteConfig,
  IRustStatistics,
} from './models/rust-types.js';
import type { IActiveConnectionSnapshot, IActiveConnectionSnapshotOptions, ISmartProxySecurityPolicy } from './models/interfaces.js';

/**
 * Type-safe command definitions for the Rust proxy IPC protocol.
 */
type TSmartProxyCommands = {
  start:                   { params: { config: IRustProxyOptions };      result: void };
  stop:                    { params: Record<string, never>;              result: void };
  updateRoutes:            { params: { routes: IRustRouteConfig[] };     result: void };
  setSecurityPolicy:       { params: { policy: ISmartProxySecurityPolicy }; result: void };
  getMetrics:              { params: Record<string, never>;              result: IRustMetricsSnapshot };
  getStatistics:           { params: Record<string, never>;              result: IRustStatistics };
  getActiveConnectionSnapshots: { params: IActiveConnectionSnapshotOptions; result: IActiveConnectionSnapshot[] };
  provisionCertificate:    { params: { routeName: string };              result: void };
  renewCertificate:        { params: { routeName: string };              result: void };
  getCertificateStatus:    { params: { routeName: string };              result: IRustCertificateStatus | null };
  getListeningPorts:       { params: Record<string, never>;              result: { ports: number[] } };
  setSocketHandlerRelay:   { params: { socketPath: string };             result: void };
  setChallengeProviderRelay: { params: { socketPath: string; options?: IRustChallengeOptions }; result: void };
  addListeningPort:        { params: { port: number };                   result: void };
  removeListeningPort:     { params: { port: number };                   result: void };
  loadCertificate:       { params: { domain: string; cert: string; key: string; ca?: string }; result: void };
  setDatagramHandlerRelay: { params: { socketPath: string };     result: void };
};

/**
 * Get the package root directory using import.meta.url.
 * This file is at ts/proxies/smart-proxy/, so package root is 3 levels up.
 */
function getPackageRoot(): string {
  const thisDir = plugins.path.dirname(plugins.url.fileURLToPath(import.meta.url));
  return plugins.path.resolve(thisDir, '..', '..', '..');
}

/**
 * Map Node.js process.platform/process.arch to tsrust's friendly name suffix.
 * tsrust names cross-compiled binaries as: rustproxy_linux_amd64, rustproxy_linux_arm64, etc.
 */
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 Rust binary, including dist_rust/ candidates
 * (built by tsrust) and local development build paths.
 */
function buildLocalPaths(): string[] {
  const packageRoot = getPackageRoot();
  const suffix = getTsrustPlatformSuffix();
  const paths: string[] = [];

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

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

  return paths;
}

/**
 * Bridge between TypeScript SmartProxy and the Rust binary.
 * Wraps @push.rocks/smartrust's RustBridge with type-safe command definitions.
 */
export class RustProxyBridge extends plugins.EventEmitter {
  private bridge: plugins.smartrust.RustBridge<TSmartProxyCommands>;

  constructor() {
    super();

    this.bridge = new plugins.smartrust.RustBridge<TSmartProxyCommands>({
      binaryName: 'rustproxy',
      envVarName: 'SMARTPROXY_RUST_BINARY',
      platformPackagePrefix: '@push.rocks/smartproxy',
      localPaths: buildLocalPaths(),
      maxPayloadSize: 100 * 1024 * 1024, // 100 MB – route configs with many entries can be large
      logger: {
        log: (level: string, message: string, data?: Record<string, any>) => {
          logger.log(level as any, message, data);
        },
      },
    });

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

  /**
   * Spawn the Rust binary in management mode.
   * Returns true if the binary was found and spawned successfully.
   */
  public async spawn(): Promise<boolean> {
    return this.bridge.spawn();
  }

  /**
   * Kill the Rust process and clean up.
   */
  public kill(): void {
    this.bridge.kill();
  }

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

  // --- Convenience methods for each management command ---

  public async startProxy(config: IRustProxyOptions): Promise<void> {
    await this.bridge.sendCommand('start', { config });
  }

  public async stopProxy(): Promise<void> {
    await this.bridge.sendCommand('stop', {} as Record<string, never>);
  }

  public async updateRoutes(routes: IRustRouteConfig[]): Promise<void> {
    await this.bridge.sendCommand('updateRoutes', { routes });
  }

  public async setSecurityPolicy(policy: ISmartProxySecurityPolicy): Promise<void> {
    await this.bridge.sendCommand('setSecurityPolicy', { policy });
  }

  public async getMetrics(): Promise<IRustMetricsSnapshot> {
    return this.bridge.sendCommand('getMetrics', {} as Record<string, never>);
  }

  public async getStatistics(): Promise<IRustStatistics> {
    return this.bridge.sendCommand('getStatistics', {} as Record<string, never>);
  }

  public async getActiveConnectionSnapshots(
    options: IActiveConnectionSnapshotOptions = {},
  ): Promise<IActiveConnectionSnapshot[]> {
    return this.bridge.sendCommand('getActiveConnectionSnapshots', options);
  }

  public async provisionCertificate(routeName: string): Promise<void> {
    await this.bridge.sendCommand('provisionCertificate', { routeName });
  }

  public async renewCertificate(routeName: string): Promise<void> {
    await this.bridge.sendCommand('renewCertificate', { routeName });
  }

  public async getCertificateStatus(routeName: string): Promise<IRustCertificateStatus | null> {
    return this.bridge.sendCommand('getCertificateStatus', { routeName });
  }

  public async getListeningPorts(): Promise<number[]> {
    const result = await this.bridge.sendCommand('getListeningPorts', {} as Record<string, never>);
    return result?.ports ?? [];
  }

  public async setSocketHandlerRelay(socketPath: string): Promise<void> {
    await this.bridge.sendCommand('setSocketHandlerRelay', { socketPath });
  }

  public async setChallengeProviderRelay(socketPath: string, options?: IRustChallengeOptions): Promise<void> {
    await this.bridge.sendCommand('setChallengeProviderRelay', { socketPath, options });
  }

  public async addListeningPort(port: number): Promise<void> {
    await this.bridge.sendCommand('addListeningPort', { port });
  }

  public async removeListeningPort(port: number): Promise<void> {
    await this.bridge.sendCommand('removeListeningPort', { port });
  }

  public async loadCertificate(domain: string, cert: string, key: string, ca?: string): Promise<void> {
    await this.bridge.sendCommand('loadCertificate', { domain, cert, key, ca });
  }

  public async setDatagramHandlerRelay(socketPath: string): Promise<void> {
    await this.bridge.sendCommand('setDatagramHandlerRelay', { socketPath });
  }
}
