import * as plugins from './plugins.js';
import { EventEmitter } from 'events';

// Command map for the hub side of remoteingress-bin
type THubCommands = {
  ping: {
    params: Record<string, never>;
    result: { pong: boolean };
  };
  startHub: {
    params: {
        tunnelPort: number;
        targetHost?: string;
        tlsCertPem?: string;
        tlsKeyPem?: string;
        performance?: IPerformanceConfig;
      };
    result: { started: boolean };
  };
  stopHub: {
    params: Record<string, never>;
    result: { stopped: boolean; wasRunning?: boolean };
  };
  updateAllowedEdges: {
    params: {
      edges: Array<{ id: string; secret: string; listenPorts?: number[]; listenPortsUdp?: number[]; stunIntervalSecs?: number; firewallConfig?: IFirewallConfig; performance?: IPerformanceConfig }>;
    };
    result: { updated: boolean };
  };
  getHubStatus: {
    params: Record<string, never>;
    result: {
      running: boolean;
      tunnelPort: number;
        connectedEdges: Array<{
          edgeId: string;
          connectedAt: number;
          activeStreams: number;
          peerAddr: string;
          transportMode: 'tcpTls' | 'quic' | 'quicWithFallback';
          fallbackUsed: boolean;
          performance: IEffectivePerformanceConfig;
          flowControl: IFlowControlStatus;
          queues: IQueueStatus;
          traffic: ITrafficStatus;
          udp: IUdpStatus;
        }>;
    };
  };
};

export interface IFirewallRateLimit {
  id: string;
  port: number;
  protocol?: 'tcp' | 'udp';
  rate: string;
  burst?: number;
  perSourceIP?: boolean;
}

export interface IFirewallRule {
  id: string;
  direction: 'input' | 'output' | 'forward';
  action: 'accept' | 'drop' | 'reject';
  sourceIP?: string;
  destPort?: number;
  protocol?: 'tcp' | 'udp';
  comment?: string;
}

export interface IFirewallConfig {
  blockedIps?: string[];
  rateLimits?: IFirewallRateLimit[];
  rules?: IFirewallRule[];
}

export interface IHubConfig {
  tunnelPort?: number;
  targetHost?: string;
  tls?: {
    certPem?: string;
    keyPem?: string;
  };
  performance?: IPerformanceConfig;
}

export type TPerformanceProfile = 'balanced' | 'throughput' | 'highConcurrency';

export interface IPerformanceConfig {
  profile?: TPerformanceProfile;
  maxStreamsPerEdge?: number;
  totalWindowBudgetBytes?: number;
  minStreamWindowBytes?: number;
  maxStreamWindowBytes?: number;
  sustainedStreamWindowBytes?: number;
  quicDatagramReceiveBufferBytes?: number;
}

export interface IEffectivePerformanceConfig {
  profile: TPerformanceProfile;
  maxStreamsPerEdge: number;
  totalWindowBudgetBytes: number;
  minStreamWindowBytes: number;
  maxStreamWindowBytes: number;
  sustainedStreamWindowBytes: number;
  quicDatagramReceiveBufferBytes: number;
}

export interface IFlowControlStatus {
  applies: boolean;
  currentWindowBytes: number;
  minWindowBytes: number;
  maxWindowBytes: number;
  totalWindowBudgetBytes: number;
  estimatedInFlightBytes: number;
  stalledStreams: number;
}

export interface IQueueStatus {
  ctrlQueueDepth: number;
  dataQueueDepth: number;
  sustainedQueueDepth: number;
}

export interface ITrafficStatus {
  bytesIn: number;
  bytesOut: number;
  streamsOpenedTotal: number;
  streamsClosedTotal: number;
  rejectedStreams: number;
}

export interface IUdpStatus {
  activeSessions: number;
  droppedDatagrams: number;
}

type TAllowedEdge = { id: string; secret: string; listenPorts?: number[]; listenPortsUdp?: number[]; stunIntervalSecs?: number; firewallConfig?: IFirewallConfig; performance?: IPerformanceConfig };

const MAX_RESTART_ATTEMPTS = 10;
const MAX_RESTART_BACKOFF_MS = 30_000;

export class RemoteIngressHub extends EventEmitter {
  private bridge: InstanceType<typeof plugins.smartrust.RustBridge<THubCommands>>;
  private started = false;
  private stopping = false;
  private savedConfig: IHubConfig | null = null;
  private savedEdges: TAllowedEdge[] = [];
  private restartBackoffMs = 1000;
  private restartAttempts = 0;

  constructor() {
    super();

    const packageDir = plugins.path.resolve(
      plugins.path.dirname(new URL(import.meta.url).pathname),
      '..',
    );

    this.bridge = new plugins.smartrust.RustBridge<THubCommands>({
      binaryName: 'remoteingress-bin',
      cliArgs: ['--management'],
      requestTimeoutMs: 30_000,
      readyTimeoutMs: 10_000,
      localPaths: [
        // Platform-suffixed binary in dist_rust (production)
        plugins.path.join(packageDir, 'dist_rust', `remoteingress-bin_${process.platform === 'win32' ? 'windows' : 'linux'}_${process.arch === 'x64' ? 'amd64' : process.arch}`),
        // Exact binaryName fallback in dist_rust
        plugins.path.join(packageDir, 'dist_rust', 'remoteingress-bin'),
        // Development build paths (cargo output uses exact name)
        plugins.path.join(packageDir, 'rust', 'target', 'release', 'remoteingress-bin'),
        plugins.path.join(packageDir, 'rust', 'target', 'debug', 'remoteingress-bin'),
      ],
      searchSystemPath: false,
      logger: {
        log: (level: string, message: string) => {
          if (level === 'error') {
            console.error(`[RemoteIngressHub] ${message}`);
          } else {
            console.log(`[RemoteIngressHub] ${message}`);
          }
        },
      },
    });

    // Forward events from Rust binary
    this.bridge.on('management:edgeConnected', (data: { edgeId: string; peerAddr: string }) => {
      this.emit('edgeConnected', data);
    });
    this.bridge.on('management:edgeDisconnected', (data: { edgeId: string; reason?: string }) => {
      const reason = data?.reason ?? 'unknown';
      console.log(`[RemoteIngressHub] Edge ${data.edgeId} disconnected: ${reason}`);
      this.emit('edgeDisconnected', data);
    });
    this.bridge.on('management:streamOpened', (data: { edgeId: string; streamId: number }) => {
      this.emit('streamOpened', data);
    });
    this.bridge.on('management:streamClosed', (data: { edgeId: string; streamId: number }) => {
      this.emit('streamClosed', data);
    });
  }

  /**
   * Start the hub — spawns the Rust binary and starts the tunnel server.
   */
  public async start(config: IHubConfig = {}): Promise<void> {
    this.savedConfig = config;
    this.stopping = false;

    const spawned = await this.bridge.spawn();
    if (!spawned) {
      throw new Error('Failed to spawn remoteingress-bin');
    }

    // Register crash recovery handler (remove first to avoid duplicates)
    this.bridge.removeListener('exit', this.handleCrashRecovery);
    this.bridge.on('exit', this.handleCrashRecovery);

    try {
      await this.bridge.sendCommand('startHub', {
        tunnelPort: config.tunnelPort ?? 8443,
        targetHost: config.targetHost ?? '127.0.0.1',
        ...(config.performance ? { performance: config.performance } : {}),
        ...(config.tls?.certPem && config.tls?.keyPem
          ? { tlsCertPem: config.tls.certPem, tlsKeyPem: config.tls.keyPem }
          : {}),
      });
    } catch (err) {
      // Clean up the spawned process to avoid orphaning it
      this.bridge.removeListener('exit', this.handleCrashRecovery);
      this.bridge.kill();
      throw err;
    }

    this.started = true;
    this.restartAttempts = 0;
    this.restartBackoffMs = 1000;
  }

  /**
   * Stop the hub and kill the Rust process.
   */
  public async stop(): Promise<void> {
    this.stopping = true;
    if (this.started) {
      try {
        await this.bridge.sendCommand('stopHub', {} as Record<string, never>);
      } catch {
        // Process may already be dead
      }
      this.bridge.removeListener('exit', this.handleCrashRecovery);
      this.bridge.kill();
      this.started = false;
    }
    this.savedConfig = null;
    this.savedEdges = [];
    // Remove all listeners to prevent memory buildup
    this.bridge.removeAllListeners();
    this.removeAllListeners();
  }

  /**
   * Update the list of allowed edges that can connect to this hub.
   */
  public async updateAllowedEdges(edges: TAllowedEdge[]): Promise<void> {
    this.savedEdges = edges;
    await this.bridge.sendCommand('updateAllowedEdges', { edges });
  }

  /**
   * Get the current hub status.
   */
  public async getStatus() {
    return this.bridge.sendCommand('getHubStatus', {} as Record<string, never>);
  }

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

  /**
   * Handle unexpected Rust binary crash — auto-restart with backoff.
   */
  private handleCrashRecovery = async (code: number | null, signal: string | null) => {
    if (this.stopping || !this.started || !this.savedConfig) {
      return;
    }

    console.error(
      `[RemoteIngressHub] Rust binary crashed (code=${code}, signal=${signal}), ` +
      `attempt ${this.restartAttempts + 1}/${MAX_RESTART_ATTEMPTS}`
    );

    this.started = false;

    if (this.restartAttempts >= MAX_RESTART_ATTEMPTS) {
      console.error('[RemoteIngressHub] Max restart attempts reached, giving up');
      this.emit('crashRecoveryFailed');
      return;
    }

    await new Promise(resolve => setTimeout(resolve, this.restartBackoffMs));
    // Re-check after backoff — stop() may have been called during the wait
    if (this.stopping || !this.savedConfig) {
      return;
    }
    this.restartBackoffMs = Math.min(this.restartBackoffMs * 2, MAX_RESTART_BACKOFF_MS);
    this.restartAttempts++;

    try {
      const spawned = await this.bridge.spawn();
      if (!spawned) {
        console.error('[RemoteIngressHub] Failed to respawn binary');
        return;
      }

      this.bridge.removeListener('exit', this.handleCrashRecovery);
      this.bridge.on('exit', this.handleCrashRecovery);

      const config = this.savedConfig;
      await this.bridge.sendCommand('startHub', {
        tunnelPort: config.tunnelPort ?? 8443,
        targetHost: config.targetHost ?? '127.0.0.1',
        ...(config.tls?.certPem && config.tls?.keyPem
          ? { tlsCertPem: config.tls.certPem, tlsKeyPem: config.tls.keyPem }
          : {}),
        ...(config.performance ? { performance: config.performance } : {}),
      });

      // Restore allowed edges
      if (this.savedEdges.length > 0) {
        await this.bridge.sendCommand('updateAllowedEdges', { edges: this.savedEdges });
      }

      this.started = true;
      this.restartAttempts = 0;
      this.restartBackoffMs = 1000;
      console.log('[RemoteIngressHub] Successfully recovered from crash');
      this.emit('crashRecovered');
    } catch (err) {
      console.error(`[RemoteIngressHub] Crash recovery failed: ${err}`);
    }
  };
}
