import { EventEmitter } from 'events';
import debug from 'debug';
import fs from 'fs';
import net, { Socket, NetConnectOpts } from 'net';
import tls from 'tls';
import { Transform, Duplex } from 'stream';
import { TunnelInfo, TunnelClusterEvents } from './types';
import HeaderHostTransformer from './HeaderHostTransformer';

const debugLog = debug('localtunnel:client');

class TunnelCluster extends EventEmitter {
  private readonly opts: TunnelInfo;
  private readonly remoteHost: string;
  private readonly remotePort: number;
  private readonly clientId: string;
  private isRegistered: boolean = false;

  constructor(opts: TunnelInfo = {} as TunnelInfo) {
    super();
    this.opts = opts;
    this.remoteHost = opts.remote_ip || opts.remote_host || 'localhost';
    this.remotePort = opts.remote_port || 7002;
    this.clientId = opts.name || '';
  }

  open(): void {
    const opt = this.opts;
    const remoteHostOrIp = opt.remote_ip || opt.remote_host;
    const remotePort = opt.remote_port;
    const localHost = opt.local_host || 'localhost';
    const localPort = opt.local_port;

    if (!localPort || typeof localPort !== 'number') {
      this.emit('error', new Error('Invalid local port configuration'));
      return;
    }

    const localProtocol = opt.local_https ? 'https' : 'http';

    if (!remoteHostOrIp || !remotePort) {
      this.emit('error', new Error('Missing remote host/port configuration'));
      return;
    }

    debugLog(
      'establishing tunnel %s://%s:%s <> %s:%s',
      localProtocol,
      localHost,
      localPort,
      remoteHostOrIp,
      remotePort
    );

    const remote = net.connect({
      host: remoteHostOrIp,
      port: remotePort
    });

    remote.setKeepAlive(true);

    const registrationTimeout = setTimeout(() => {
      if (!this.isRegistered) {
        debugLog('registration timeout');
        remote.destroy(new Error('Registration timeout'));
      }
    }, 5000);

    remote.once('connect', () => {
      debugLog('发送身份识别信息');
      remote.write(JSON.stringify({
        clientId: this.clientId,
        type: 'register'
      }) + '\n');

      let buffer = '';
      const messageHandler = (data: Buffer) => {
        buffer += data.toString();
        if (buffer.includes('\n')) {
          try {
            const response = JSON.parse(buffer);
            if (response.type === 'registered' && response.success) {
              clearTimeout(registrationTimeout);
              this.isRegistered = true;
              remote.removeListener('data', messageHandler);
              this.emit('open', remote);
              connLocal();
            } else if (response.type === 'registered' && !response.success) {
              remote.destroy(new Error(response.message || 'Registration failed'));
            }
          } catch (err) {
            debugLog('解析注册响应失败:', err);
            remote.destroy();
          }
          buffer = '';
        }
      };

      remote.on('data', messageHandler);
    });

    remote.on('error', (err: Error) => {
      clearTimeout(registrationTimeout);
      debugLog('remote connection error:', err.message);
      if ((err as NodeJS.ErrnoException).code === 'ECONNREFUSED') {
        this.emit('error', new Error(
          `Connection refused: ${remoteHostOrIp}:${remotePort} (check your firewall settings)`
        ));
      }
      remote.end();
    });

    const connLocal = () => {
      if (remote.destroyed) {
        debugLog('remote destroyed');
        this.emit('dead');
        return;
      }

      debugLog('connecting locally to %s://%s:%d', localProtocol, localHost, localPort);
      remote.pause();

      const local = this.createLocalConnection(localPort);

      local.once('error', (err: Error) => {
        debugLog('local error %s', err.message);
        local.end();

        if ((err as NodeJS.ErrnoException).code !== 'ECONNREFUSED' 
            && (err as NodeJS.ErrnoException).code !== 'ECONNRESET') {
          return remote.end();
        }

        setTimeout(connLocal, 1000);
      });

      local.once('connect', () => {
        debugLog('connected locally');
        remote.resume();
        this.setupPipes(remote, local);
      });
    };
  }

  private createLocalConnection(localPort: number): Socket {
    const { local_host, local_https, local_cert, local_key, local_ca, allow_invalid_cert } = this.opts;

    const localConnectOpts: NetConnectOpts = {
      host: local_host || 'localhost',
      port: localPort
    };

    if (!local_https) {
      return net.connect(localConnectOpts);
    }

    const tlsOpts = allow_invalid_cert ? 
      { rejectUnauthorized: false } : 
      {
        cert: local_cert ? fs.readFileSync(local_cert) : undefined,
        key: local_key ? fs.readFileSync(local_key) : undefined,
        ca: local_ca ? [fs.readFileSync(local_ca)] : undefined,
      };

    return tls.connect({ ...localConnectOpts, ...tlsOpts });
  }

  private setupPipes(remote: Socket, local: Socket): void {
    let stream: Duplex = remote;

    if (this.opts.local_host) {
      debugLog('transform Host header to %s', this.opts.local_host);
      const transformer = new HeaderHostTransformer({ host: this.opts.local_host });
      stream = remote.pipe(transformer);
    }

    stream.pipe(local).pipe(remote);

    local.once('close', (hadError: boolean) => {
      debugLog('local connection closed [%s]', hadError);
      if (!remote.destroyed) {
        remote.end();
      }
    });

    remote.once('close', () => {
      debugLog('remote close');
      this.emit('dead');
      if (!local.destroyed) {
        local.end();
      }
    });
  }
}

export default TunnelCluster; 