import { EventEmitter } from 'events';
import { parse } from 'url';
import axios from 'axios';
import debug from 'debug';
import { TunnelOptions, TunnelInfo, TunnelEvents } from './types';
import TunnelCluster from './TunnelCluster';

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

class Tunnel extends EventEmitter {
  private readonly opts: TunnelOptions;
  private closed: boolean;
  public url?: string;
  public cachedUrl?: string;
  private tunnelCluster?: TunnelCluster;
  public clientId?: string;

  constructor(opts: TunnelOptions = {} as TunnelOptions) {
    super();
    this.opts = opts;
    this.closed = false;

    if (!this.opts.host) {
      this.opts.host = 'https://localtunnel.me';
    }
  }

  private _getInfo(body: any): TunnelInfo {
    const { id, ip, port, url, cached_url, max_conn_count } = body;
    const { host, port: local_port, local_host } = this.opts;
    const { local_https, local_cert, local_key, local_ca, allow_invalid_cert } = this.opts;

    return {
      name: id,
      url,
      cached_url,
      max_conn: max_conn_count || 1,
      remote_host: parse(host!).hostname!,
      remote_ip: ip,
      remote_port: port,
      local_port,
      local_host,
      local_https,
      local_cert,
      local_key,
      local_ca,
      allow_invalid_cert,
    };
  }

  private _init(callback: (err: Error | null, info?: TunnelInfo) => void): void {
    const opt = this.opts;
    const getInfo = this._getInfo.bind(this);

    const params = {
      responseType: 'json' as const,
    };

    const baseUri = `${opt.host}/`;
    // no subdomain at first, maybe use requested domain
    const assignedDomain = opt.subdomain;
    // where to quest
    const uri = baseUri + (assignedDomain || '?new') + 
        (opt.port ? `&port=${opt.port}` : '');

    debugLog('请求隧道服务器: %s', uri);
    const getUrl = () => {
      axios
        .get(uri, params)
        .then(res => {
          const body = res.data;
          debugLog('got tunnel information', body);
          if (res.status !== 200) {
            const err = new Error(
              (body && body.message) || 'localtunnel server returned an error, please try again'
            );
            return callback(err);
          }
          callback(null, getInfo(body));
        })
        .catch(err => {
          debugLog(`tunnel server offline: ${err.message}, retry 1s`);
          return setTimeout(getUrl, 1000);
        });
    };

    getUrl();
  }

  private _establish(info: TunnelInfo): void {
    // 为 max_conn 设置默认值
    const maxConn = info.max_conn || 1;  // 如果 max_conn 未定义，默认为 1

    // increase max event listeners so that localtunnel consumers don't get
    // warning messages as soon as they setup even one listener. See #71
    this.setMaxListeners(maxConn + (EventEmitter.defaultMaxListeners || 10));

    this.tunnelCluster = new TunnelCluster(info);

    // only emit the url the first time
    this.tunnelCluster.once('open', () => {
        this.emit('url', info.url);
    });

    // re-emit socket error
    this.tunnelCluster.on('error', err => {
        debugLog('got socket error', err.message);
        this.emit('error', err);
    });

    let tunnelCount = 0;

    // track open count
    this.tunnelCluster.on('open', tunnel => {
        tunnelCount++;
        debugLog('tunnel open [total: %d]', tunnelCount);

        const closeHandler = () => {
            tunnel.destroy();
        };

        if (this.closed) {
            return closeHandler();
        }

        this.once('close', closeHandler);
        tunnel.once('close', () => {
            this.removeListener('close', closeHandler);
        });
    });

    // when a tunnel dies, open a new one
    this.tunnelCluster.on('dead', () => {
        tunnelCount--;
        debugLog('tunnel dead [total: %d]', tunnelCount);
        if (this.closed) {
            return;
        }
        this.tunnelCluster?.open();
    });

    this.tunnelCluster.on('request', req => {
        this.emit('request', req);
    });

    // establish as many tunnels as allowed
    for (let count = 0; count < maxConn; ++count) {  // 使用 maxConn 变量
        this.tunnelCluster.open();
    }
  }

  open(callback: (err: Error | null) => void): void {
    this._init((err, info) => {
      if (err) {
        return callback(err);
      }

      this.clientId = info!.name;
      this.url = info!.url;

      // `cached_url` is only returned by proxy servers that support resource caching.
      if (info!.cached_url) {
        this.cachedUrl = info!.cached_url;
      }

      this._establish(info!);
      callback(null);
    });
  }

  close(): void {
    this.closed = true;
    this.emit('close');
  }
}

export default Tunnel; 